@gethmy/agent 1.10.9 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +867 -194
- package/dist/index.js +867 -194
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -385,11 +385,12 @@ var DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL
|
|
|
385
385
|
var init_types = __esm(() => {
|
|
386
386
|
init_plan_phase();
|
|
387
387
|
DEFAULT_AGENT_CONFIG = {
|
|
388
|
-
poolSize:
|
|
388
|
+
poolSize: 6,
|
|
389
389
|
maxTimeout: 1800000,
|
|
390
390
|
pickupColumns: ["To Do"],
|
|
391
391
|
priorityLabels: { urgent: 100, critical: 90, bug: 50 },
|
|
392
392
|
columnBoost: true,
|
|
393
|
+
runner: "cli",
|
|
393
394
|
completion: {
|
|
394
395
|
createPR: false,
|
|
395
396
|
moveToColumn: "Review",
|
|
@@ -431,7 +432,7 @@ var init_types = __esm(() => {
|
|
|
431
432
|
},
|
|
432
433
|
review: {
|
|
433
434
|
enabled: true,
|
|
434
|
-
poolSize:
|
|
435
|
+
poolSize: 3,
|
|
435
436
|
pickupColumns: ["Review"],
|
|
436
437
|
moveToColumn: "Done",
|
|
437
438
|
failColumn: "To Do",
|
|
@@ -563,6 +564,9 @@ function loadDaemonConfig() {
|
|
|
563
564
|
...agentOverrides.planning ?? {}
|
|
564
565
|
}
|
|
565
566
|
};
|
|
567
|
+
if (agent.runner !== "cli" && agent.runner !== "sdk") {
|
|
568
|
+
agent.runner = "cli";
|
|
569
|
+
}
|
|
566
570
|
return {
|
|
567
571
|
apiKey,
|
|
568
572
|
apiUrl,
|
|
@@ -2581,6 +2585,13 @@ function formatTokenCount(tokens) {
|
|
|
2581
2585
|
return `${(tokens / 1000).toFixed(1)}k`;
|
|
2582
2586
|
return String(tokens);
|
|
2583
2587
|
}
|
|
2588
|
+
function describeNoCommitFailure(numTurns, maxTurns) {
|
|
2589
|
+
const maxTurnsExhausted = maxTurns > 0 && numTurns >= maxTurns;
|
|
2590
|
+
return {
|
|
2591
|
+
maxTurnsExhausted,
|
|
2592
|
+
failureSummary: maxTurnsExhausted ? `Agent exhausted its ${maxTurns}-turn budget without committing any changes` : "Agent finished without making any changes to commit"
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2584
2595
|
function buildTokenPayload(stats) {
|
|
2585
2596
|
if (!stats?.cost)
|
|
2586
2597
|
return {};
|
|
@@ -2604,14 +2615,17 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2604
2615
|
};
|
|
2605
2616
|
const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
|
|
2606
2617
|
if (!hasCommits) {
|
|
2607
|
-
|
|
2618
|
+
const { maxTurnsExhausted, failureSummary } = describeNoCommitFailure(sessionStats?.cost?.numTurns ?? 0, config.claude.maxTurns);
|
|
2619
|
+
log.warn(TAG14, `No commits on branch ${branchName} — ${failureSummary}; counting as a failed attempt`);
|
|
2620
|
+
await moveCardToColumn(client, card, config.pickupColumns[0] ?? "To Do");
|
|
2608
2621
|
await client.endAgentSession(card.id, {
|
|
2609
|
-
status: "
|
|
2610
|
-
|
|
2622
|
+
status: "failed",
|
|
2623
|
+
failureReason: maxTurnsExhausted ? "timeout" : "other",
|
|
2624
|
+
failureSummary,
|
|
2611
2625
|
...buildTokenPayload(sessionStats)
|
|
2612
2626
|
});
|
|
2613
2627
|
cleanupWorktree(worktreePath, branchName);
|
|
2614
|
-
return
|
|
2628
|
+
return false;
|
|
2615
2629
|
}
|
|
2616
2630
|
log.info(TAG14, `Pushing branch ${branchName} (pre-verify)...`);
|
|
2617
2631
|
let lastPushedSha = null;
|
|
@@ -2878,6 +2892,20 @@ function signalGroup(proc, signal) {
|
|
|
2878
2892
|
}
|
|
2879
2893
|
}
|
|
2880
2894
|
}
|
|
2895
|
+
function reapGroup(pgid) {
|
|
2896
|
+
if (!pgid || pgid <= 1 || pgid === process.pid)
|
|
2897
|
+
return;
|
|
2898
|
+
if (process.platform === "win32")
|
|
2899
|
+
return;
|
|
2900
|
+
try {
|
|
2901
|
+
process.kill(-pgid, "SIGKILL");
|
|
2902
|
+
} catch (err) {
|
|
2903
|
+
const code = err.code;
|
|
2904
|
+
if (code !== "ESRCH") {
|
|
2905
|
+
log.warn(TAG15, `reapGroup(${pgid}) failed: ${err instanceof Error ? err.message : err}`);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2881
2909
|
async function terminateGroup(proc, opts) {
|
|
2882
2910
|
if (!proc.pid || proc.killed)
|
|
2883
2911
|
return;
|
|
@@ -2929,8 +2957,8 @@ class ProgressTracker {
|
|
|
2929
2957
|
filesEdited = new Set;
|
|
2930
2958
|
filesRead = new Set;
|
|
2931
2959
|
lastCost = null;
|
|
2932
|
-
|
|
2933
|
-
|
|
2960
|
+
runEventSink = null;
|
|
2961
|
+
lastEmittedProgress = -1;
|
|
2934
2962
|
lastAssistantText = "";
|
|
2935
2963
|
assistantTextBlocks = [];
|
|
2936
2964
|
constructor(client, cardId, workerId, subtasks, initialPhase = "exploring") {
|
|
@@ -2943,32 +2971,15 @@ class ProgressTracker {
|
|
|
2943
2971
|
this.phase = initialPhase;
|
|
2944
2972
|
this.progress = PHASES[initialPhase].min;
|
|
2945
2973
|
}
|
|
2946
|
-
|
|
2947
|
-
this.
|
|
2974
|
+
setRunEventSink(sink) {
|
|
2975
|
+
this.runEventSink = sink;
|
|
2948
2976
|
}
|
|
2949
2977
|
attach(parser) {
|
|
2950
2978
|
parser.on("tool_start", (name, input) => {
|
|
2951
2979
|
this.onToolStart(name, input);
|
|
2952
|
-
const desc = this.describeToolAction(name, input);
|
|
2953
|
-
if (desc) {
|
|
2954
|
-
this.pushLogEntry({
|
|
2955
|
-
phase: this.phase,
|
|
2956
|
-
eventType: "tool_start",
|
|
2957
|
-
toolName: name,
|
|
2958
|
-
description: desc,
|
|
2959
|
-
metadata: this.extractToolMetadata(name, input)
|
|
2960
|
-
});
|
|
2961
|
-
}
|
|
2962
2980
|
});
|
|
2963
2981
|
parser.on("tool_end", (name, _id, content) => {
|
|
2964
2982
|
this.onToolEnd(name, content);
|
|
2965
|
-
this.pushLogEntry({
|
|
2966
|
-
phase: this.phase,
|
|
2967
|
-
eventType: "tool_end",
|
|
2968
|
-
toolName: name,
|
|
2969
|
-
description: `Completed: ${name}`,
|
|
2970
|
-
metadata: {}
|
|
2971
|
-
});
|
|
2972
2983
|
});
|
|
2973
2984
|
parser.on("text", (content) => {
|
|
2974
2985
|
this.onText(content);
|
|
@@ -2978,6 +2989,37 @@ class ProgressTracker {
|
|
|
2978
2989
|
});
|
|
2979
2990
|
this.startHeartbeat();
|
|
2980
2991
|
}
|
|
2992
|
+
ingest(draft) {
|
|
2993
|
+
switch (draft.kind) {
|
|
2994
|
+
case "run_started":
|
|
2995
|
+
this.startHeartbeat();
|
|
2996
|
+
break;
|
|
2997
|
+
case "tool_started":
|
|
2998
|
+
this.onToolStart(draft.payload.toolName, draft.payload.input);
|
|
2999
|
+
break;
|
|
3000
|
+
case "tool_ended":
|
|
3001
|
+
this.onToolEnd(draft.payload.toolName, draft.payload.output);
|
|
3002
|
+
break;
|
|
3003
|
+
case "assistant_text":
|
|
3004
|
+
this.onText(draft.payload.text);
|
|
3005
|
+
break;
|
|
3006
|
+
case "cost_updated": {
|
|
3007
|
+
const p = draft.payload;
|
|
3008
|
+
this.lastCost = {
|
|
3009
|
+
totalCostUsd: p.totalCostUsd,
|
|
3010
|
+
totalInputTokens: p.inputTokens,
|
|
3011
|
+
totalOutputTokens: p.outputTokens,
|
|
3012
|
+
totalCacheCreationInputTokens: p.cacheCreationInputTokens,
|
|
3013
|
+
totalCacheReadInputTokens: p.cacheReadInputTokens,
|
|
3014
|
+
durationMs: p.durationMs ?? 0,
|
|
3015
|
+
durationApiMs: 0,
|
|
3016
|
+
numTurns: p.numTurns,
|
|
3017
|
+
modelName: p.modelName
|
|
3018
|
+
};
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
2981
3023
|
stop() {
|
|
2982
3024
|
this.stopped = true;
|
|
2983
3025
|
if (this.pendingUpdate) {
|
|
@@ -3074,16 +3116,11 @@ class ProgressTracker {
|
|
|
3074
3116
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
3075
3117
|
return;
|
|
3076
3118
|
log.info(TAG16, `Phase: ${this.phase} → ${newPhase}`);
|
|
3119
|
+
const previousPhase = this.phase;
|
|
3120
|
+
this.runEventSink?.recordPhaseChanged(newPhase, previousPhase);
|
|
3077
3121
|
this.phase = newPhase;
|
|
3078
3122
|
this.progress = Math.max(this.progress, PHASES[newPhase].min);
|
|
3079
3123
|
this.lastAction = "";
|
|
3080
|
-
this.pushLogEntry({
|
|
3081
|
-
phase: newPhase,
|
|
3082
|
-
eventType: "phase_change",
|
|
3083
|
-
toolName: null,
|
|
3084
|
-
description: `Entering ${newPhase} phase`,
|
|
3085
|
-
metadata: {}
|
|
3086
|
-
});
|
|
3087
3124
|
this.scheduleUpdate(PHASES[newPhase].label);
|
|
3088
3125
|
}
|
|
3089
3126
|
incrementProgress() {
|
|
@@ -3191,7 +3228,15 @@ class ProgressTracker {
|
|
|
3191
3228
|
}).catch((err) => {
|
|
3192
3229
|
log.warn(TAG16, `Failed to send progress update: ${err}`);
|
|
3193
3230
|
});
|
|
3194
|
-
this.
|
|
3231
|
+
if (this.runEventSink && this.progress !== this.lastEmittedProgress) {
|
|
3232
|
+
this.lastEmittedProgress = this.progress;
|
|
3233
|
+
this.runEventSink.recordProgress({
|
|
3234
|
+
progressPercent: this.progress,
|
|
3235
|
+
currentTask: truncate(currentTask, MAX_TASK_LENGTH),
|
|
3236
|
+
phase: this.phase,
|
|
3237
|
+
filesChanged: this.filesEdited.size
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3195
3240
|
}
|
|
3196
3241
|
startHeartbeat() {
|
|
3197
3242
|
if (this.heartbeatTimer) {
|
|
@@ -3205,55 +3250,6 @@ class ProgressTracker {
|
|
|
3205
3250
|
}
|
|
3206
3251
|
}, HEARTBEAT_MS);
|
|
3207
3252
|
}
|
|
3208
|
-
flushFinal() {
|
|
3209
|
-
this.flushActivityLog();
|
|
3210
|
-
}
|
|
3211
|
-
pushLogEntry(entry) {
|
|
3212
|
-
this.logBuffer.push({
|
|
3213
|
-
...entry,
|
|
3214
|
-
createdAt: new Date().toISOString()
|
|
3215
|
-
});
|
|
3216
|
-
if (this.logBuffer.length > MAX_LOG_BUFFER) {
|
|
3217
|
-
this.logBuffer.shift();
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
flushActivityLog() {
|
|
3221
|
-
if (!this.sessionId || this.logBuffer.length === 0)
|
|
3222
|
-
return;
|
|
3223
|
-
const raw = [...this.logBuffer];
|
|
3224
|
-
this.logBuffer = [];
|
|
3225
|
-
this.client.flushActivityLog(this.cardId, {
|
|
3226
|
-
sessionId: this.sessionId,
|
|
3227
|
-
entries: raw.map((e) => ({
|
|
3228
|
-
...e,
|
|
3229
|
-
phase: e.phase ?? undefined,
|
|
3230
|
-
toolName: e.toolName ?? undefined
|
|
3231
|
-
}))
|
|
3232
|
-
}).catch((err) => {
|
|
3233
|
-
log.warn(TAG16, `Failed to flush activity log: ${err}`);
|
|
3234
|
-
this.logBuffer.unshift(...raw);
|
|
3235
|
-
if (this.logBuffer.length > MAX_LOG_BUFFER) {
|
|
3236
|
-
this.logBuffer.length = MAX_LOG_BUFFER;
|
|
3237
|
-
}
|
|
3238
|
-
});
|
|
3239
|
-
}
|
|
3240
|
-
extractToolMetadata(_name, input) {
|
|
3241
|
-
const meta = {};
|
|
3242
|
-
const fp = this.extractString(input, "file_path");
|
|
3243
|
-
if (fp)
|
|
3244
|
-
meta.file_path = fp;
|
|
3245
|
-
const cmd = this.extractString(input, "command");
|
|
3246
|
-
if (cmd)
|
|
3247
|
-
meta.command = cmd.split(`
|
|
3248
|
-
`)[0].slice(0, 200);
|
|
3249
|
-
const pattern = this.extractString(input, "pattern");
|
|
3250
|
-
if (pattern)
|
|
3251
|
-
meta.pattern = pattern;
|
|
3252
|
-
const desc = this.extractString(input, "description");
|
|
3253
|
-
if (desc)
|
|
3254
|
-
meta.description = desc;
|
|
3255
|
-
return meta;
|
|
3256
|
-
}
|
|
3257
3253
|
extractString(input, key) {
|
|
3258
3254
|
if (typeof input === "object" && input !== null && key in input) {
|
|
3259
3255
|
return String(input[key]);
|
|
@@ -3261,7 +3257,7 @@ class ProgressTracker {
|
|
|
3261
3257
|
return null;
|
|
3262
3258
|
}
|
|
3263
3259
|
}
|
|
3264
|
-
var TAG16 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120,
|
|
3260
|
+
var TAG16 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
|
|
3265
3261
|
var init_progress_tracker = __esm(() => {
|
|
3266
3262
|
init_log();
|
|
3267
3263
|
init_types();
|
|
@@ -3691,7 +3687,6 @@ var init_review_completion = __esm(() => {
|
|
|
3691
3687
|
init_types();
|
|
3692
3688
|
init_worktree();
|
|
3693
3689
|
});
|
|
3694
|
-
|
|
3695
3690
|
// ../harmony-shared/dist/cardLinks.js
|
|
3696
3691
|
var init_cardLinks = () => {};
|
|
3697
3692
|
// ../harmony-shared/dist/classification.js
|
|
@@ -4298,6 +4293,10 @@ var init_stream_parser = __esm(() => {
|
|
|
4298
4293
|
toolNames = new Map;
|
|
4299
4294
|
hasEmittedText = false;
|
|
4300
4295
|
observedModel;
|
|
4296
|
+
capturedSessionId;
|
|
4297
|
+
get sessionId() {
|
|
4298
|
+
return this.capturedSessionId;
|
|
4299
|
+
}
|
|
4301
4300
|
attach(stream) {
|
|
4302
4301
|
if (this.attached) {
|
|
4303
4302
|
throw new Error("StreamParser already attached to a stream");
|
|
@@ -4352,6 +4351,9 @@ var init_stream_parser = __esm(() => {
|
|
|
4352
4351
|
this.observedModel = msg.message.model;
|
|
4353
4352
|
}
|
|
4354
4353
|
}
|
|
4354
|
+
if (!this.capturedSessionId && typeof msg.session_id === "string") {
|
|
4355
|
+
this.capturedSessionId = msg.session_id;
|
|
4356
|
+
}
|
|
4355
4357
|
switch (msg.type) {
|
|
4356
4358
|
case "assistant": {
|
|
4357
4359
|
const blocks = msg.message?.content;
|
|
@@ -4362,10 +4364,11 @@ var init_stream_parser = __esm(() => {
|
|
|
4362
4364
|
this.emit("text", block.text);
|
|
4363
4365
|
this.hasEmittedText = true;
|
|
4364
4366
|
} else if (block.type === "tool_use" && typeof block.name === "string") {
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
+
const toolUseId = typeof block.id === "string" ? block.id : undefined;
|
|
4368
|
+
if (toolUseId) {
|
|
4369
|
+
this.toolNames.set(toolUseId, block.name);
|
|
4367
4370
|
}
|
|
4368
|
-
this.emit("tool_start", block.name, block.input);
|
|
4371
|
+
this.emit("tool_start", block.name, block.input, toolUseId);
|
|
4369
4372
|
}
|
|
4370
4373
|
}
|
|
4371
4374
|
break;
|
|
@@ -5203,6 +5206,150 @@ var init_unblock = __esm(() => {
|
|
|
5203
5206
|
init_log();
|
|
5204
5207
|
});
|
|
5205
5208
|
|
|
5209
|
+
// src/cli-agent-runner.ts
|
|
5210
|
+
class CliAgentRunner {
|
|
5211
|
+
client;
|
|
5212
|
+
cardId;
|
|
5213
|
+
sessionId;
|
|
5214
|
+
buffer = [];
|
|
5215
|
+
flushTimer = null;
|
|
5216
|
+
flushing = false;
|
|
5217
|
+
stopped = false;
|
|
5218
|
+
constructor(client, cardId, sessionId) {
|
|
5219
|
+
this.client = client;
|
|
5220
|
+
this.cardId = cardId;
|
|
5221
|
+
this.sessionId = sessionId;
|
|
5222
|
+
}
|
|
5223
|
+
attach(parser) {
|
|
5224
|
+
if (this.stopped)
|
|
5225
|
+
return;
|
|
5226
|
+
parser.on("tool_start", (name, input, toolUseId) => {
|
|
5227
|
+
this.enqueue({
|
|
5228
|
+
kind: "tool_started",
|
|
5229
|
+
source: "agent",
|
|
5230
|
+
payload: { toolName: name, toolUseId, input }
|
|
5231
|
+
});
|
|
5232
|
+
});
|
|
5233
|
+
parser.on("tool_end", (name, toolUseId, content) => {
|
|
5234
|
+
this.enqueue({
|
|
5235
|
+
kind: "tool_ended",
|
|
5236
|
+
source: "agent",
|
|
5237
|
+
payload: {
|
|
5238
|
+
toolName: name,
|
|
5239
|
+
toolUseId,
|
|
5240
|
+
output: content === undefined ? undefined : content.slice(0, MAX_OUTPUT_LEN)
|
|
5241
|
+
}
|
|
5242
|
+
});
|
|
5243
|
+
});
|
|
5244
|
+
parser.on("text", (content) => {
|
|
5245
|
+
const text = content.trim();
|
|
5246
|
+
if (!text)
|
|
5247
|
+
return;
|
|
5248
|
+
this.enqueue({
|
|
5249
|
+
kind: "assistant_text",
|
|
5250
|
+
source: "agent",
|
|
5251
|
+
payload: { text: text.slice(0, MAX_TEXT_LEN) }
|
|
5252
|
+
});
|
|
5253
|
+
});
|
|
5254
|
+
parser.on("cost_update", (cost) => {
|
|
5255
|
+
this.enqueue({
|
|
5256
|
+
kind: "cost_updated",
|
|
5257
|
+
source: "agent",
|
|
5258
|
+
payload: mapCost(cost)
|
|
5259
|
+
});
|
|
5260
|
+
});
|
|
5261
|
+
this.startTimer();
|
|
5262
|
+
}
|
|
5263
|
+
recordRunStarted(payload) {
|
|
5264
|
+
this.enqueue({ kind: "run_started", source: "system", payload });
|
|
5265
|
+
this.startTimer();
|
|
5266
|
+
}
|
|
5267
|
+
recordPhaseChanged(phase, previousPhase) {
|
|
5268
|
+
this.enqueue({
|
|
5269
|
+
kind: "phase_changed",
|
|
5270
|
+
source: "system",
|
|
5271
|
+
payload: { phase, previousPhase }
|
|
5272
|
+
});
|
|
5273
|
+
}
|
|
5274
|
+
recordProgress(payload) {
|
|
5275
|
+
this.enqueue({ kind: "progress", source: "system", payload });
|
|
5276
|
+
}
|
|
5277
|
+
recordError(payload) {
|
|
5278
|
+
this.enqueue({ kind: "error", source: "system", payload });
|
|
5279
|
+
}
|
|
5280
|
+
recordFinished(payload) {
|
|
5281
|
+
this.enqueue({ kind: "run_finished", source: "system", payload });
|
|
5282
|
+
}
|
|
5283
|
+
record(body) {
|
|
5284
|
+
this.enqueue(body);
|
|
5285
|
+
this.startTimer();
|
|
5286
|
+
}
|
|
5287
|
+
enqueue(body) {
|
|
5288
|
+
if (this.stopped)
|
|
5289
|
+
return;
|
|
5290
|
+
this.buffer.push({ ...body, createdAt: new Date().toISOString() });
|
|
5291
|
+
if (this.buffer.length >= MAX_BUFFER)
|
|
5292
|
+
this.flush();
|
|
5293
|
+
}
|
|
5294
|
+
startTimer() {
|
|
5295
|
+
if (this.flushTimer || this.stopped)
|
|
5296
|
+
return;
|
|
5297
|
+
this.flushTimer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);
|
|
5298
|
+
this.flushTimer.unref?.();
|
|
5299
|
+
}
|
|
5300
|
+
async flush() {
|
|
5301
|
+
if (this.flushing || this.buffer.length === 0)
|
|
5302
|
+
return;
|
|
5303
|
+
this.flushing = true;
|
|
5304
|
+
const batch = this.buffer.splice(0, MAX_BUFFER);
|
|
5305
|
+
try {
|
|
5306
|
+
await this.client.appendAgentRunEvents(this.cardId, {
|
|
5307
|
+
sessionId: this.sessionId,
|
|
5308
|
+
events: batch
|
|
5309
|
+
});
|
|
5310
|
+
} catch (err) {
|
|
5311
|
+
log.warn(TAG24, `Failed to flush run events: ${err}`);
|
|
5312
|
+
this.buffer.unshift(...batch);
|
|
5313
|
+
if (this.buffer.length > MAX_BUFFER) {
|
|
5314
|
+
this.buffer.length = MAX_BUFFER;
|
|
5315
|
+
}
|
|
5316
|
+
} finally {
|
|
5317
|
+
this.flushing = false;
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
async flushFinal() {
|
|
5321
|
+
for (let i = 0;this.buffer.length > 0 && i < 5; i++) {
|
|
5322
|
+
const before = this.buffer.length;
|
|
5323
|
+
await this.flush();
|
|
5324
|
+
if (this.buffer.length >= before)
|
|
5325
|
+
break;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
stop() {
|
|
5329
|
+
this.stopped = true;
|
|
5330
|
+
if (this.flushTimer) {
|
|
5331
|
+
clearInterval(this.flushTimer);
|
|
5332
|
+
this.flushTimer = null;
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
function mapCost(cost) {
|
|
5337
|
+
return {
|
|
5338
|
+
totalCostUsd: cost.totalCostUsd,
|
|
5339
|
+
inputTokens: cost.totalInputTokens,
|
|
5340
|
+
outputTokens: cost.totalOutputTokens,
|
|
5341
|
+
cacheCreationInputTokens: cost.totalCacheCreationInputTokens,
|
|
5342
|
+
cacheReadInputTokens: cost.totalCacheReadInputTokens,
|
|
5343
|
+
numTurns: cost.numTurns,
|
|
5344
|
+
modelName: cost.modelName,
|
|
5345
|
+
durationMs: cost.durationMs
|
|
5346
|
+
};
|
|
5347
|
+
}
|
|
5348
|
+
var TAG24 = "cli-agent-runner", FLUSH_INTERVAL_MS = 2000, MAX_BUFFER = 1000, MAX_TEXT_LEN = 8000, MAX_OUTPUT_LEN = 4000;
|
|
5349
|
+
var init_cli_agent_runner = __esm(() => {
|
|
5350
|
+
init_log();
|
|
5351
|
+
});
|
|
5352
|
+
|
|
5206
5353
|
// src/model-tier.ts
|
|
5207
5354
|
function clampWithdrawn(model) {
|
|
5208
5355
|
return WITHDRAWN_MODEL.test(model) ? MAX_IMPLEMENT_MODEL : model;
|
|
@@ -5253,11 +5400,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
5253
5400
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
5254
5401
|
When finished, call harmony_end_agent_session with status="completed".`
|
|
5255
5402
|
});
|
|
5256
|
-
log.info(
|
|
5403
|
+
log.info(TAG25, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
5257
5404
|
return result.prompt + pastEpisodesSection;
|
|
5258
5405
|
} catch (err) {
|
|
5259
5406
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5260
|
-
log.warn(
|
|
5407
|
+
log.warn(TAG25, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
5261
5408
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
5262
5409
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
|
|
5263
5410
|
}
|
|
@@ -5275,7 +5422,7 @@ async function renderCommentsSection(client, cardId) {
|
|
|
5275
5422
|
|
|
5276
5423
|
${section}` : "";
|
|
5277
5424
|
} catch (err) {
|
|
5278
|
-
log.warn(
|
|
5425
|
+
log.warn(TAG25, "comment-thread fetch failed", {
|
|
5279
5426
|
event: "comment_fetch_failed",
|
|
5280
5427
|
error: err instanceof Error ? err.message : String(err)
|
|
5281
5428
|
});
|
|
@@ -5325,7 +5472,7 @@ ${description}`.trim();
|
|
|
5325
5472
|
## Similar past tasks
|
|
5326
5473
|
${bullets}`;
|
|
5327
5474
|
} catch (err) {
|
|
5328
|
-
log.warn(
|
|
5475
|
+
log.warn(TAG25, "past-episodes recall failed", {
|
|
5329
5476
|
event: "episode_recall_failed",
|
|
5330
5477
|
error: err instanceof Error ? err.message : String(err)
|
|
5331
5478
|
});
|
|
@@ -5366,13 +5513,346 @@ ${subtaskStr}
|
|
|
5366
5513
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
5367
5514
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
5368
5515
|
}
|
|
5369
|
-
var
|
|
5516
|
+
var TAG25 = "prompt";
|
|
5370
5517
|
var init_prompt = __esm(() => {
|
|
5371
5518
|
init_dist();
|
|
5372
5519
|
init_log();
|
|
5373
5520
|
});
|
|
5374
5521
|
|
|
5522
|
+
// src/sdk-agent-runner.ts
|
|
5523
|
+
import {
|
|
5524
|
+
query
|
|
5525
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
5526
|
+
function mapSdkErrorKind(e) {
|
|
5527
|
+
switch (e) {
|
|
5528
|
+
case "authentication_failed":
|
|
5529
|
+
case "oauth_org_not_allowed":
|
|
5530
|
+
return "auth";
|
|
5531
|
+
case "billing_error":
|
|
5532
|
+
return "out_of_credits";
|
|
5533
|
+
case "rate_limit":
|
|
5534
|
+
case "overloaded":
|
|
5535
|
+
return "rate_limit";
|
|
5536
|
+
default:
|
|
5537
|
+
return null;
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5540
|
+
|
|
5541
|
+
class SdkAgentRunner {
|
|
5542
|
+
cfg;
|
|
5543
|
+
abort = null;
|
|
5544
|
+
capturedSessionId;
|
|
5545
|
+
child = null;
|
|
5546
|
+
leaderPid;
|
|
5547
|
+
capturedStderr = "";
|
|
5548
|
+
toolNames = new Map;
|
|
5549
|
+
observedModel;
|
|
5550
|
+
effectiveModel;
|
|
5551
|
+
constructor(cfg = {}) {
|
|
5552
|
+
this.cfg = cfg;
|
|
5553
|
+
}
|
|
5554
|
+
get sessionId() {
|
|
5555
|
+
return this.capturedSessionId;
|
|
5556
|
+
}
|
|
5557
|
+
get capturedStderrText() {
|
|
5558
|
+
return this.capturedStderr;
|
|
5559
|
+
}
|
|
5560
|
+
start(input) {
|
|
5561
|
+
return this.run(input);
|
|
5562
|
+
}
|
|
5563
|
+
resume(input) {
|
|
5564
|
+
return this.run(input, input.resumeSessionId);
|
|
5565
|
+
}
|
|
5566
|
+
async send(_message) {
|
|
5567
|
+
throw new Error("SdkAgentRunner.send() (streaming-input steering) is not wired; the worker steers via stop→resume");
|
|
5568
|
+
}
|
|
5569
|
+
async stop(_reason) {
|
|
5570
|
+
this.abort?.abort();
|
|
5571
|
+
if (this.child) {
|
|
5572
|
+
await terminateGroup(this.child, {
|
|
5573
|
+
sigintTimeoutMs: STOP_SIGINT_MS,
|
|
5574
|
+
sigtermTimeoutMs: STOP_SIGTERM_MS
|
|
5575
|
+
});
|
|
5576
|
+
}
|
|
5577
|
+
reapGroup(this.leaderPid);
|
|
5578
|
+
}
|
|
5579
|
+
async* run(input, resumeSessionId) {
|
|
5580
|
+
this.abort = new AbortController;
|
|
5581
|
+
this.capturedStderr = "";
|
|
5582
|
+
this.child = null;
|
|
5583
|
+
this.leaderPid = undefined;
|
|
5584
|
+
this.toolNames.clear();
|
|
5585
|
+
this.observedModel = undefined;
|
|
5586
|
+
this.effectiveModel = input.model ?? this.cfg.model;
|
|
5587
|
+
yield {
|
|
5588
|
+
kind: "run_started",
|
|
5589
|
+
source: "system",
|
|
5590
|
+
payload: { runner: "sdk", model: input.model }
|
|
5591
|
+
};
|
|
5592
|
+
const allowed = this.cfg.allowedTools ?? SDK_ALLOWED_TOOLS;
|
|
5593
|
+
const builtinTools = allowed.filter((t) => !t.startsWith("mcp__") && !t.includes("*"));
|
|
5594
|
+
const options = {
|
|
5595
|
+
cwd: input.cwd,
|
|
5596
|
+
model: input.model ?? this.cfg.model,
|
|
5597
|
+
allowedTools: allowed,
|
|
5598
|
+
tools: builtinTools,
|
|
5599
|
+
permissionMode: "dontAsk",
|
|
5600
|
+
maxTurns: this.cfg.maxTurns,
|
|
5601
|
+
abortController: this.abort,
|
|
5602
|
+
...resumeSessionId ? { resume: resumeSessionId } : {},
|
|
5603
|
+
...this.cfg.maxBudgetUsd ? { maxBudgetUsd: this.cfg.maxBudgetUsd } : {},
|
|
5604
|
+
...this.cfg.settingSources ? { settingSources: this.cfg.settingSources } : {},
|
|
5605
|
+
...this.cfg.mcpServers ? { mcpServers: this.cfg.mcpServers } : {},
|
|
5606
|
+
...this.cfg.strictMcpConfig ? { strictMcpConfig: true } : {},
|
|
5607
|
+
stderr: (data) => {
|
|
5608
|
+
this.capturedStderr += data;
|
|
5609
|
+
},
|
|
5610
|
+
spawnClaudeCodeProcess: (spawnOpts) => this.spawn(spawnOpts)
|
|
5611
|
+
};
|
|
5612
|
+
try {
|
|
5613
|
+
const q = query({ prompt: input.prompt, options });
|
|
5614
|
+
let failureReason = null;
|
|
5615
|
+
for await (const msg of q) {
|
|
5616
|
+
for (const ev of this.mapMessage(msg)) {
|
|
5617
|
+
if (ev.kind === "error") {
|
|
5618
|
+
failureReason = ev.payload.errorKind ?? "crash";
|
|
5619
|
+
}
|
|
5620
|
+
yield ev;
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
yield {
|
|
5624
|
+
kind: "run_finished",
|
|
5625
|
+
source: "system",
|
|
5626
|
+
payload: failureReason ? { status: "failed", failureReason } : { status: "completed" }
|
|
5627
|
+
};
|
|
5628
|
+
} catch (err) {
|
|
5629
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5630
|
+
const cls = classifyRunError(`${message}
|
|
5631
|
+
${this.capturedStderr}`);
|
|
5632
|
+
yield {
|
|
5633
|
+
kind: "error",
|
|
5634
|
+
source: "system",
|
|
5635
|
+
payload: {
|
|
5636
|
+
message,
|
|
5637
|
+
errorKind: cls.kind,
|
|
5638
|
+
retryable: cls.kind !== "auth" && cls.kind !== null
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
yield {
|
|
5642
|
+
kind: "run_finished",
|
|
5643
|
+
source: "system",
|
|
5644
|
+
payload: { status: "failed", failureReason: cls.kind ?? "crash" }
|
|
5645
|
+
};
|
|
5646
|
+
} finally {
|
|
5647
|
+
reapGroup(this.leaderPid);
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
spawn(spawnOpts) {
|
|
5651
|
+
const child = spawnInGroup(spawnOpts.command, spawnOpts.args, {
|
|
5652
|
+
cwd: spawnOpts.cwd,
|
|
5653
|
+
env: spawnOpts.env,
|
|
5654
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5655
|
+
});
|
|
5656
|
+
this.child = child;
|
|
5657
|
+
this.leaderPid = child.pid;
|
|
5658
|
+
child.stderr?.on("data", (d) => {
|
|
5659
|
+
this.capturedStderr += d.toString();
|
|
5660
|
+
});
|
|
5661
|
+
this.cfg.onSpawn?.(child);
|
|
5662
|
+
return {
|
|
5663
|
+
stdin: child.stdin,
|
|
5664
|
+
stdout: child.stdout,
|
|
5665
|
+
get killed() {
|
|
5666
|
+
return child.killed;
|
|
5667
|
+
},
|
|
5668
|
+
get exitCode() {
|
|
5669
|
+
return child.exitCode;
|
|
5670
|
+
},
|
|
5671
|
+
kill: (signal) => {
|
|
5672
|
+
if (!child.pid)
|
|
5673
|
+
return false;
|
|
5674
|
+
try {
|
|
5675
|
+
process.kill(-child.pid, signal);
|
|
5676
|
+
return true;
|
|
5677
|
+
} catch {
|
|
5678
|
+
return child.kill(signal);
|
|
5679
|
+
}
|
|
5680
|
+
},
|
|
5681
|
+
on: (event, listener) => child.on(event, listener),
|
|
5682
|
+
once: (event, listener) => child.once(event, listener),
|
|
5683
|
+
off: (event, listener) => child.off(event, listener)
|
|
5684
|
+
};
|
|
5685
|
+
}
|
|
5686
|
+
*mapMessage(msg) {
|
|
5687
|
+
const sid = msg.session_id;
|
|
5688
|
+
if (sid && !this.capturedSessionId)
|
|
5689
|
+
this.capturedSessionId = sid;
|
|
5690
|
+
const model = msg.message?.model ?? msg.model;
|
|
5691
|
+
if (typeof model === "string" && !this.observedModel) {
|
|
5692
|
+
this.observedModel = model;
|
|
5693
|
+
}
|
|
5694
|
+
switch (msg.type) {
|
|
5695
|
+
case "assistant": {
|
|
5696
|
+
const am = msg;
|
|
5697
|
+
if (am.error) {
|
|
5698
|
+
yield {
|
|
5699
|
+
kind: "error",
|
|
5700
|
+
source: "system",
|
|
5701
|
+
payload: {
|
|
5702
|
+
message: `assistant error: ${am.error}`,
|
|
5703
|
+
errorKind: mapSdkErrorKind(am.error)
|
|
5704
|
+
}
|
|
5705
|
+
};
|
|
5706
|
+
}
|
|
5707
|
+
const blocks = am.message?.content;
|
|
5708
|
+
if (Array.isArray(blocks)) {
|
|
5709
|
+
for (const b of blocks) {
|
|
5710
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
5711
|
+
const text = b.text.trim();
|
|
5712
|
+
if (text) {
|
|
5713
|
+
yield {
|
|
5714
|
+
kind: "assistant_text",
|
|
5715
|
+
source: "agent",
|
|
5716
|
+
payload: { text: text.slice(0, MAX_TEXT_LEN2) }
|
|
5717
|
+
};
|
|
5718
|
+
}
|
|
5719
|
+
} else if (b.type === "tool_use" && typeof b.name === "string") {
|
|
5720
|
+
if (typeof b.id === "string")
|
|
5721
|
+
this.toolNames.set(b.id, b.name);
|
|
5722
|
+
yield {
|
|
5723
|
+
kind: "tool_started",
|
|
5724
|
+
source: "agent",
|
|
5725
|
+
payload: { toolName: b.name, toolUseId: b.id, input: b.input }
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
break;
|
|
5731
|
+
}
|
|
5732
|
+
case "user": {
|
|
5733
|
+
const um = msg;
|
|
5734
|
+
const blocks = um.message?.content;
|
|
5735
|
+
if (Array.isArray(blocks)) {
|
|
5736
|
+
for (const b of blocks) {
|
|
5737
|
+
if (b.type === "tool_result" && typeof b.tool_use_id === "string") {
|
|
5738
|
+
const toolName = this.toolNames.get(b.tool_use_id) ?? "";
|
|
5739
|
+
this.toolNames.delete(b.tool_use_id);
|
|
5740
|
+
yield {
|
|
5741
|
+
kind: "tool_ended",
|
|
5742
|
+
source: "agent",
|
|
5743
|
+
payload: {
|
|
5744
|
+
toolName,
|
|
5745
|
+
toolUseId: b.tool_use_id,
|
|
5746
|
+
output: normalize(b.content)?.slice(0, MAX_OUTPUT_LEN2),
|
|
5747
|
+
isError: b.is_error
|
|
5748
|
+
}
|
|
5749
|
+
};
|
|
5750
|
+
}
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5753
|
+
break;
|
|
5754
|
+
}
|
|
5755
|
+
case "result": {
|
|
5756
|
+
const r = msg;
|
|
5757
|
+
if (typeof r.total_cost_usd === "number") {
|
|
5758
|
+
yield {
|
|
5759
|
+
kind: "cost_updated",
|
|
5760
|
+
source: "agent",
|
|
5761
|
+
payload: {
|
|
5762
|
+
totalCostUsd: r.total_cost_usd,
|
|
5763
|
+
inputTokens: r.usage?.input_tokens ?? 0,
|
|
5764
|
+
outputTokens: r.usage?.output_tokens ?? 0,
|
|
5765
|
+
cacheCreationInputTokens: r.usage?.cache_creation_input_tokens ?? 0,
|
|
5766
|
+
cacheReadInputTokens: r.usage?.cache_read_input_tokens ?? 0,
|
|
5767
|
+
numTurns: r.num_turns ?? 0,
|
|
5768
|
+
durationMs: r.duration_ms,
|
|
5769
|
+
modelName: this.observedModel ?? this.effectiveModel
|
|
5770
|
+
}
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5773
|
+
if (r.subtype && r.subtype !== "success") {
|
|
5774
|
+
const joined = (r.errors ?? []).join(`
|
|
5775
|
+
`);
|
|
5776
|
+
const cls = classifyRunError(`${r.subtype}
|
|
5777
|
+
${joined}
|
|
5778
|
+
${this.capturedStderr}`);
|
|
5779
|
+
yield {
|
|
5780
|
+
kind: "error",
|
|
5781
|
+
source: "system",
|
|
5782
|
+
payload: {
|
|
5783
|
+
message: `result ${r.subtype}: ${joined || "(no detail)"}`,
|
|
5784
|
+
errorKind: cls.kind,
|
|
5785
|
+
retryable: cls.kind !== "auth" && cls.kind !== null
|
|
5786
|
+
}
|
|
5787
|
+
};
|
|
5788
|
+
}
|
|
5789
|
+
break;
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
function normalize(raw) {
|
|
5795
|
+
if (raw == null)
|
|
5796
|
+
return;
|
|
5797
|
+
if (typeof raw === "string")
|
|
5798
|
+
return raw;
|
|
5799
|
+
if (Array.isArray(raw)) {
|
|
5800
|
+
const parts = [];
|
|
5801
|
+
for (const b of raw) {
|
|
5802
|
+
if (b && typeof b === "object" && "text" in b && typeof b.text === "string") {
|
|
5803
|
+
parts.push(b.text);
|
|
5804
|
+
}
|
|
5805
|
+
}
|
|
5806
|
+
return parts.length ? parts.join("") : JSON.stringify(raw);
|
|
5807
|
+
}
|
|
5808
|
+
try {
|
|
5809
|
+
return JSON.stringify(raw);
|
|
5810
|
+
} catch {
|
|
5811
|
+
return String(raw);
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
var SDK_ALLOWED_TOOLS, MAX_TEXT_LEN2 = 8000, MAX_OUTPUT_LEN2 = 4000, STOP_SIGINT_MS = 2000, STOP_SIGTERM_MS = 2000;
|
|
5815
|
+
var init_sdk_agent_runner = __esm(() => {
|
|
5816
|
+
init_error_classifier();
|
|
5817
|
+
init_process_group();
|
|
5818
|
+
SDK_ALLOWED_TOOLS = [
|
|
5819
|
+
"Bash",
|
|
5820
|
+
"Read",
|
|
5821
|
+
"Write",
|
|
5822
|
+
"Edit",
|
|
5823
|
+
"Glob",
|
|
5824
|
+
"Grep",
|
|
5825
|
+
"Agent",
|
|
5826
|
+
"mcp__harmony__*"
|
|
5827
|
+
];
|
|
5828
|
+
});
|
|
5829
|
+
|
|
5375
5830
|
// src/worker.ts
|
|
5831
|
+
function sdkDraftLogLine(ev) {
|
|
5832
|
+
switch (ev.kind) {
|
|
5833
|
+
case "assistant_text":
|
|
5834
|
+
return ev.payload.text.slice(0, 200);
|
|
5835
|
+
case "tool_started":
|
|
5836
|
+
return ev.payload.toolName;
|
|
5837
|
+
case "tool_ended":
|
|
5838
|
+
return `${ev.payload.toolUseId ?? ""}${ev.payload.isError ? " (error)" : ""}`;
|
|
5839
|
+
case "cost_updated":
|
|
5840
|
+
return `$${ev.payload.totalCostUsd.toFixed(4)} turns=${ev.payload.numTurns}`;
|
|
5841
|
+
case "error":
|
|
5842
|
+
return ev.payload.message.slice(0, 200);
|
|
5843
|
+
case "run_finished":
|
|
5844
|
+
return ev.payload.status;
|
|
5845
|
+
default:
|
|
5846
|
+
return "";
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
function buildSteeringPrompt(messages) {
|
|
5850
|
+
if (messages.length === 1)
|
|
5851
|
+
return messages[0];
|
|
5852
|
+
return messages.map((m, i) => `${i + 1}. ${m}`).join(`
|
|
5853
|
+
`);
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5376
5856
|
class Worker {
|
|
5377
5857
|
config;
|
|
5378
5858
|
client;
|
|
@@ -5393,12 +5873,16 @@ class Worker {
|
|
|
5393
5873
|
timeoutTimer = null;
|
|
5394
5874
|
heartbeatTimer = null;
|
|
5395
5875
|
progressTracker = null;
|
|
5876
|
+
cliRunner = null;
|
|
5877
|
+
sdkRunner = null;
|
|
5396
5878
|
lastSessionStats;
|
|
5397
5879
|
aborted = false;
|
|
5398
5880
|
timedOut = false;
|
|
5399
5881
|
verificationFailed = false;
|
|
5400
5882
|
sessionId = null;
|
|
5401
5883
|
runId = null;
|
|
5884
|
+
cliSessionId = null;
|
|
5885
|
+
lastDrainedSeq = 0;
|
|
5402
5886
|
runCostCents = 0;
|
|
5403
5887
|
runTurns = 0;
|
|
5404
5888
|
constructor(id, config, client, agentId, onDone, workspaceId, projectId, stateStore, onCardCompleted, onApiError) {
|
|
@@ -5445,7 +5929,7 @@ class Worker {
|
|
|
5445
5929
|
}
|
|
5446
5930
|
}
|
|
5447
5931
|
get tag() {
|
|
5448
|
-
return `${
|
|
5932
|
+
return `${TAG26}:${this.id}`;
|
|
5449
5933
|
}
|
|
5450
5934
|
get isIdle() {
|
|
5451
5935
|
return this.state === "idle";
|
|
@@ -5467,6 +5951,8 @@ class Worker {
|
|
|
5467
5951
|
this.verificationFailed = false;
|
|
5468
5952
|
this.runCostCents = 0;
|
|
5469
5953
|
this.runTurns = 0;
|
|
5954
|
+
this.cliSessionId = null;
|
|
5955
|
+
this.lastDrainedSeq = 0;
|
|
5470
5956
|
this.cardId = card.id;
|
|
5471
5957
|
this.startedAt = Date.now();
|
|
5472
5958
|
this.runId = newRunId();
|
|
@@ -5504,9 +5990,16 @@ class Worker {
|
|
|
5504
5990
|
});
|
|
5505
5991
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
5506
5992
|
if (!sid) {
|
|
5507
|
-
log.warn(
|
|
5993
|
+
log.warn(TAG26, "startAgentSession returned no session id");
|
|
5508
5994
|
}
|
|
5509
5995
|
this.sessionId = sid;
|
|
5996
|
+
if (this.sessionId) {
|
|
5997
|
+
this.cliRunner = new CliAgentRunner(this.client, card.id, this.sessionId);
|
|
5998
|
+
this.cliRunner.recordRunStarted({
|
|
5999
|
+
runner: this.config.runner,
|
|
6000
|
+
model: this.config.claude.model
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
5510
6003
|
await this.recordPhase("preparing");
|
|
5511
6004
|
const moved = await moveCardAndAddLabel(this.client, card, IN_PROGRESS_COLUMN, "agent");
|
|
5512
6005
|
if (!moved) {
|
|
@@ -5553,6 +6046,9 @@ class Worker {
|
|
|
5553
6046
|
await this.spawnClaude(prompt, card, subtasks, {
|
|
5554
6047
|
model: this.selectImplementModel(card)
|
|
5555
6048
|
});
|
|
6049
|
+
if (this.aborted)
|
|
6050
|
+
return;
|
|
6051
|
+
await this.drainSteeringMessages(card, subtasks);
|
|
5556
6052
|
if (this.aborted)
|
|
5557
6053
|
return;
|
|
5558
6054
|
if (this.timeoutTimer) {
|
|
@@ -5579,9 +6075,17 @@ class Worker {
|
|
|
5579
6075
|
log.error(this.tag, `Error on #${card.short_id}: ${msg}`);
|
|
5580
6076
|
const rawStderr = err?.stderr;
|
|
5581
6077
|
const errClass = classifyRunError(typeof rawStderr === "string" && rawStderr ? rawStderr : msg);
|
|
6078
|
+
const sdkKind = err?.errorKind;
|
|
6079
|
+
if (errClass.kind === null && sdkKind != null)
|
|
6080
|
+
errClass.kind = sdkKind;
|
|
5582
6081
|
const apiError = errClass.kind !== null;
|
|
5583
6082
|
const baseError = err instanceof WorktreeBaseError;
|
|
5584
6083
|
const noBudgetBurn = apiError || baseError;
|
|
6084
|
+
this.cliRunner?.recordError({
|
|
6085
|
+
message: msg.slice(0, 500),
|
|
6086
|
+
errorKind: errClass.kind,
|
|
6087
|
+
retryable: noBudgetBurn
|
|
6088
|
+
});
|
|
5585
6089
|
if (apiError) {
|
|
5586
6090
|
try {
|
|
5587
6091
|
this.onApiError?.(errClass);
|
|
@@ -5660,6 +6164,16 @@ class Worker {
|
|
|
5660
6164
|
} catch {}
|
|
5661
6165
|
await this.recordOutcome(card.id, "failure");
|
|
5662
6166
|
} else if (this.runId && this.aborted) {
|
|
6167
|
+
try {
|
|
6168
|
+
await this.client.updateCard(card.id, { assignedAgentId: null });
|
|
6169
|
+
} catch (err) {
|
|
6170
|
+
log.warn(this.tag, `failed to release card after stop: ${err instanceof Error ? err.message : err}`);
|
|
6171
|
+
}
|
|
6172
|
+
try {
|
|
6173
|
+
await runTransition(this.client, card, { removeLabels: ["agent"] });
|
|
6174
|
+
} catch (tErr) {
|
|
6175
|
+
log.warn(this.tag, `stop label cleanup failed on #${card.short_id}: ${tErr instanceof TransitionError ? tErr.detail : tErr}`);
|
|
6176
|
+
}
|
|
5663
6177
|
try {
|
|
5664
6178
|
await this.stateStore.endRun(this.runId, "paused", {
|
|
5665
6179
|
errorMessage: "cancelled",
|
|
@@ -5675,6 +6189,26 @@ class Worker {
|
|
|
5675
6189
|
} catch {}
|
|
5676
6190
|
await this.recordOutcome(card.id, "failure");
|
|
5677
6191
|
}
|
|
6192
|
+
if (this.cliRunner) {
|
|
6193
|
+
if (succeeded) {
|
|
6194
|
+
this.cliRunner.recordFinished({ status: "completed" });
|
|
6195
|
+
} else if (this.timedOut) {
|
|
6196
|
+
this.cliRunner.recordFinished({
|
|
6197
|
+
status: "failed",
|
|
6198
|
+
failureReason: "timeout"
|
|
6199
|
+
});
|
|
6200
|
+
} else if (this.aborted) {
|
|
6201
|
+
this.cliRunner.recordFinished({
|
|
6202
|
+
status: "stopped",
|
|
6203
|
+
stopReason: "user_requested"
|
|
6204
|
+
});
|
|
6205
|
+
} else {
|
|
6206
|
+
this.cliRunner.recordFinished({ status: "failed" });
|
|
6207
|
+
}
|
|
6208
|
+
try {
|
|
6209
|
+
await this.cliRunner.flushFinal();
|
|
6210
|
+
} catch {}
|
|
6211
|
+
}
|
|
5678
6212
|
this.cleanup();
|
|
5679
6213
|
this.state = "idle";
|
|
5680
6214
|
this.onDone(this);
|
|
@@ -5764,7 +6298,9 @@ class Worker {
|
|
|
5764
6298
|
this.aborted = true;
|
|
5765
6299
|
this.state = "cancelling";
|
|
5766
6300
|
log.info(this.tag, `Cancelling work on ${this.cardId}`);
|
|
5767
|
-
if (this.
|
|
6301
|
+
if (this.sdkRunner) {
|
|
6302
|
+
await this.sdkRunner.stop(this.timedOut ? "timeout" : "user_requested");
|
|
6303
|
+
} else if (this.process && !this.process.killed) {
|
|
5768
6304
|
await terminateGroup(this.process, {
|
|
5769
6305
|
sigintTimeoutMs: CANCEL_SIGINT_TIMEOUT2,
|
|
5770
6306
|
sigtermTimeoutMs: CANCEL_SIGTERM_TIMEOUT2
|
|
@@ -5799,7 +6335,9 @@ class Worker {
|
|
|
5799
6335
|
const planTimeout = setTimeout(() => {
|
|
5800
6336
|
planTimedOut = true;
|
|
5801
6337
|
log.warn(this.tag, "Planning pass exceeded timeout — abandoning, implementing directly");
|
|
5802
|
-
if (this.
|
|
6338
|
+
if (this.sdkRunner) {
|
|
6339
|
+
this.sdkRunner.stop("timeout").catch(() => {});
|
|
6340
|
+
} else if (this.process && !this.process.killed) {
|
|
5803
6341
|
terminateGroup(this.process, {
|
|
5804
6342
|
sigintTimeoutMs: 1e4,
|
|
5805
6343
|
sigtermTimeoutMs: 5000
|
|
@@ -5893,7 +6431,40 @@ class Worker {
|
|
|
5893
6431
|
}
|
|
5894
6432
|
return false;
|
|
5895
6433
|
}
|
|
5896
|
-
async
|
|
6434
|
+
async drainSteeringMessages(card, subtasks) {
|
|
6435
|
+
if (!this.cliSessionId || !this.sessionId || !this.cardId)
|
|
6436
|
+
return;
|
|
6437
|
+
for (let i = 0;i < MAX_STEERING_ITERATIONS && !this.aborted; i++) {
|
|
6438
|
+
let messages;
|
|
6439
|
+
try {
|
|
6440
|
+
const res = await this.client.getPendingUserMessages(this.cardId, this.sessionId, this.lastDrainedSeq);
|
|
6441
|
+
messages = res.messages ?? [];
|
|
6442
|
+
} catch (err) {
|
|
6443
|
+
log.warn(this.tag, `Failed to fetch steering messages (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
6444
|
+
return;
|
|
6445
|
+
}
|
|
6446
|
+
if (messages.length === 0)
|
|
6447
|
+
return;
|
|
6448
|
+
this.lastDrainedSeq = Math.max(this.lastDrainedSeq, ...messages.map((m) => m.seq));
|
|
6449
|
+
log.info(this.tag, `Steering #${card.short_id}: resuming with ${messages.length} queued message(s)`);
|
|
6450
|
+
this.state = "running";
|
|
6451
|
+
await this.recordPhase("running");
|
|
6452
|
+
try {
|
|
6453
|
+
await this.spawnClaude(buildSteeringPrompt(messages.map((m) => m.text)), card, subtasks, {
|
|
6454
|
+
model: this.selectImplementModel(card),
|
|
6455
|
+
maxTurns: STEERING_MAX_TURNS,
|
|
6456
|
+
resumeSessionId: this.cliSessionId
|
|
6457
|
+
});
|
|
6458
|
+
} catch (err) {
|
|
6459
|
+
log.warn(this.tag, `Steering resume failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
6460
|
+
return;
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
}
|
|
6464
|
+
spawnClaude(prompt, card, subtasks, opts = {}) {
|
|
6465
|
+
return this.config.runner === "sdk" ? this.spawnClaudeSdk(prompt, card, subtasks, opts) : this.spawnClaudeCli(prompt, card, subtasks, opts);
|
|
6466
|
+
}
|
|
6467
|
+
async spawnClaudeCli(prompt, card, subtasks, opts = {}) {
|
|
5897
6468
|
const model = opts.model ?? this.config.claude.model;
|
|
5898
6469
|
const maxTurns = opts.maxTurns ?? this.config.claude.maxTurns;
|
|
5899
6470
|
const allowedTools = opts.allowedTools ?? IMPLEMENT_ALLOWED_TOOLS;
|
|
@@ -5910,6 +6481,7 @@ class Worker {
|
|
|
5910
6481
|
String(maxTurns),
|
|
5911
6482
|
"--allowedTools",
|
|
5912
6483
|
allowedTools,
|
|
6484
|
+
...opts.resumeSessionId ? ["--resume", opts.resumeSessionId] : [],
|
|
5913
6485
|
...this.config.claude.additionalArgs,
|
|
5914
6486
|
"--",
|
|
5915
6487
|
prompt
|
|
@@ -5929,10 +6501,11 @@ class Worker {
|
|
|
5929
6501
|
});
|
|
5930
6502
|
const parser = new StreamParser;
|
|
5931
6503
|
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
|
|
5932
|
-
if (this.sessionId) {
|
|
5933
|
-
this.progressTracker.setSessionId(this.sessionId);
|
|
5934
|
-
}
|
|
5935
6504
|
this.progressTracker.attach(parser);
|
|
6505
|
+
this.cliRunner?.attach(parser);
|
|
6506
|
+
if (this.cliRunner) {
|
|
6507
|
+
this.progressTracker.setRunEventSink(this.cliRunner);
|
|
6508
|
+
}
|
|
5936
6509
|
if (this.process.stdout) {
|
|
5937
6510
|
parser.attach(this.process.stdout);
|
|
5938
6511
|
if (runLog) {
|
|
@@ -5956,14 +6529,16 @@ class Worker {
|
|
|
5956
6529
|
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
5957
6530
|
});
|
|
5958
6531
|
this.process.on("close", (code) => {
|
|
6532
|
+
const leaderPid = this.process?.pid;
|
|
5959
6533
|
this.process = null;
|
|
6534
|
+
if (parser.sessionId)
|
|
6535
|
+
this.cliSessionId = parser.sessionId;
|
|
5960
6536
|
this.lastSessionStats = this.progressTracker?.stats;
|
|
5961
6537
|
const spawnCost = this.lastSessionStats?.cost;
|
|
5962
6538
|
if (spawnCost) {
|
|
5963
6539
|
this.runCostCents += Math.round(spawnCost.totalCostUsd * 100);
|
|
5964
6540
|
this.runTurns += spawnCost.numTurns;
|
|
5965
6541
|
}
|
|
5966
|
-
this.progressTracker?.flushFinal();
|
|
5967
6542
|
this.progressTracker?.stop();
|
|
5968
6543
|
this.progressTracker = null;
|
|
5969
6544
|
if (runLog) {
|
|
@@ -5973,6 +6548,7 @@ class Worker {
|
|
|
5973
6548
|
`);
|
|
5974
6549
|
runLog.stream.end();
|
|
5975
6550
|
}
|
|
6551
|
+
reapGroup(leaderPid);
|
|
5976
6552
|
if (this.aborted) {
|
|
5977
6553
|
resolve3();
|
|
5978
6554
|
} else if (code === 0) {
|
|
@@ -5985,11 +6561,101 @@ class Worker {
|
|
|
5985
6561
|
});
|
|
5986
6562
|
});
|
|
5987
6563
|
}
|
|
6564
|
+
async spawnClaudeSdk(prompt, card, subtasks, opts = {}) {
|
|
6565
|
+
const model = opts.model ?? this.config.claude.model;
|
|
6566
|
+
const maxTurns = opts.maxTurns ?? this.config.claude.maxTurns;
|
|
6567
|
+
const allowedTools = (opts.allowedTools ?? IMPLEMENT_ALLOWED_TOOLS).split(",").map((t) => t.trim()).filter(Boolean);
|
|
6568
|
+
const initialPhase = opts.initialPhase ?? "exploring";
|
|
6569
|
+
const sdkCfg = this.config.sdk;
|
|
6570
|
+
log.info(this.tag, `Spawning Agent SDK runner (model=${model}, maxTurns=${maxTurns}${opts.resumeSessionId ? ", resume" : ""})`);
|
|
6571
|
+
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
6572
|
+
if (runLog) {
|
|
6573
|
+
log.info(this.tag, `Run log: ${runLog.path}`);
|
|
6574
|
+
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} runner=sdk started=${new Date().toISOString()}
|
|
6575
|
+
` + `# model=${model} maxTurns=${maxTurns} <prompt:${prompt.length} chars>
|
|
6576
|
+
|
|
6577
|
+
`);
|
|
6578
|
+
}
|
|
6579
|
+
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
|
|
6580
|
+
if (this.cliRunner) {
|
|
6581
|
+
this.progressTracker.setRunEventSink(this.cliRunner);
|
|
6582
|
+
}
|
|
6583
|
+
const runner = new SdkAgentRunner({
|
|
6584
|
+
model,
|
|
6585
|
+
maxTurns,
|
|
6586
|
+
allowedTools,
|
|
6587
|
+
maxBudgetUsd: sdkCfg?.maxBudgetUsd,
|
|
6588
|
+
settingSources: sdkCfg?.settingSources,
|
|
6589
|
+
mcpServers: sdkCfg?.mcpServers,
|
|
6590
|
+
strictMcpConfig: sdkCfg?.strictMcpConfig,
|
|
6591
|
+
onSpawn: (child) => {
|
|
6592
|
+
this.process = child;
|
|
6593
|
+
}
|
|
6594
|
+
});
|
|
6595
|
+
this.sdkRunner = runner;
|
|
6596
|
+
const baseInput = {
|
|
6597
|
+
sessionId: this.sessionId ?? "",
|
|
6598
|
+
cardId: card.id,
|
|
6599
|
+
workspaceId: this.workspaceId,
|
|
6600
|
+
prompt,
|
|
6601
|
+
cwd: this.worktreePath,
|
|
6602
|
+
model
|
|
6603
|
+
};
|
|
6604
|
+
const stream = opts.resumeSessionId ? runner.resume({ ...baseInput, resumeSessionId: opts.resumeSessionId }) : runner.start(baseInput);
|
|
6605
|
+
let failure = null;
|
|
6606
|
+
let failureKind = null;
|
|
6607
|
+
try {
|
|
6608
|
+
for await (const ev of stream) {
|
|
6609
|
+
this.progressTracker?.ingest(ev);
|
|
6610
|
+
if (ev.source === "agent")
|
|
6611
|
+
this.cliRunner?.record(ev);
|
|
6612
|
+
if (ev.kind === "error") {
|
|
6613
|
+
failure = ev.payload.message;
|
|
6614
|
+
if (ev.payload.errorKind != null)
|
|
6615
|
+
failureKind = ev.payload.errorKind;
|
|
6616
|
+
}
|
|
6617
|
+
runLog?.stream.write(`[${ev.kind}] ${sdkDraftLogLine(ev)}
|
|
6618
|
+
`);
|
|
6619
|
+
}
|
|
6620
|
+
} finally {
|
|
6621
|
+
this.cliSessionId = runner.sessionId ?? this.cliSessionId;
|
|
6622
|
+
this.lastSessionStats = this.progressTracker?.stats;
|
|
6623
|
+
const spawnCost = this.lastSessionStats?.cost;
|
|
6624
|
+
if (spawnCost) {
|
|
6625
|
+
this.runCostCents += Math.round(spawnCost.totalCostUsd * 100);
|
|
6626
|
+
this.runTurns += spawnCost.numTurns;
|
|
6627
|
+
}
|
|
6628
|
+
if (runLog) {
|
|
6629
|
+
const stats = this.lastSessionStats;
|
|
6630
|
+
runLog.stream.write(`
|
|
6631
|
+
# runner=sdk aborted=${this.aborted} ` + `toolCalls=${stats?.toolCalls ?? 0} filesEdited=${stats?.filesEdited ?? 0} ` + `cost=$${stats?.cost?.totalCostUsd.toFixed(4) ?? "0"} ` + `ended=${new Date().toISOString()}
|
|
6632
|
+
`);
|
|
6633
|
+
runLog.stream.end();
|
|
6634
|
+
}
|
|
6635
|
+
this.progressTracker?.stop();
|
|
6636
|
+
this.progressTracker = null;
|
|
6637
|
+
this.process = null;
|
|
6638
|
+
this.sdkRunner = null;
|
|
6639
|
+
}
|
|
6640
|
+
if (this.aborted)
|
|
6641
|
+
return;
|
|
6642
|
+
if (failure) {
|
|
6643
|
+
const err = new Error(failure);
|
|
6644
|
+
err.stderr = runner.capturedStderrText;
|
|
6645
|
+
err.errorKind = failureKind;
|
|
6646
|
+
throw err;
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
5988
6649
|
cleanup() {
|
|
5989
6650
|
if (this.progressTracker) {
|
|
5990
6651
|
this.progressTracker.stop();
|
|
5991
6652
|
this.progressTracker = null;
|
|
5992
6653
|
}
|
|
6654
|
+
if (this.cliRunner) {
|
|
6655
|
+
this.cliRunner.stop();
|
|
6656
|
+
this.cliRunner = null;
|
|
6657
|
+
}
|
|
6658
|
+
this.sdkRunner = null;
|
|
5993
6659
|
this.stopHeartbeat();
|
|
5994
6660
|
this.lastSessionStats = undefined;
|
|
5995
6661
|
if (this.timeoutTimer) {
|
|
@@ -6014,9 +6680,10 @@ class Worker {
|
|
|
6014
6680
|
this.runTurns = 0;
|
|
6015
6681
|
}
|
|
6016
6682
|
}
|
|
6017
|
-
var
|
|
6683
|
+
var TAG26 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, STEERING_MAX_TURNS = 15, MAX_STEERING_ITERATIONS = 10, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
|
|
6018
6684
|
var init_worker = __esm(() => {
|
|
6019
6685
|
init_board_helpers();
|
|
6686
|
+
init_cli_agent_runner();
|
|
6020
6687
|
init_completion();
|
|
6021
6688
|
init_error_classifier();
|
|
6022
6689
|
init_log();
|
|
@@ -6026,6 +6693,7 @@ var init_worker = __esm(() => {
|
|
|
6026
6693
|
init_progress_tracker();
|
|
6027
6694
|
init_prompt();
|
|
6028
6695
|
init_run_log();
|
|
6696
|
+
init_sdk_agent_runner();
|
|
6029
6697
|
init_state_store();
|
|
6030
6698
|
init_stream_parser();
|
|
6031
6699
|
init_transitions();
|
|
@@ -6084,39 +6752,39 @@ class Pool {
|
|
|
6084
6752
|
}
|
|
6085
6753
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
6086
6754
|
if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
|
|
6087
|
-
log.debug(
|
|
6755
|
+
log.debug(TAG27, `Card ${card.id} already queued or active, skipping`);
|
|
6088
6756
|
return;
|
|
6089
6757
|
}
|
|
6090
6758
|
if (mode === "implement") {
|
|
6091
6759
|
if (this.authPaused) {
|
|
6092
|
-
log.debug(
|
|
6760
|
+
log.debug(TAG27, `#${card.short_id} held — agent paused (auth error)`);
|
|
6093
6761
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
6094
6762
|
return;
|
|
6095
6763
|
}
|
|
6096
6764
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
6097
6765
|
if (cooldownMs > 0) {
|
|
6098
|
-
log.debug(
|
|
6766
|
+
log.debug(TAG27, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
6099
6767
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
6100
6768
|
return;
|
|
6101
6769
|
}
|
|
6102
6770
|
const decision = this.budget.check(card.id);
|
|
6103
6771
|
if (!decision.allow) {
|
|
6104
6772
|
if (decision.reason === "daily_budget") {
|
|
6105
|
-
log.warn(
|
|
6773
|
+
log.warn(TAG27, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
6106
6774
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
6107
6775
|
} else {
|
|
6108
|
-
log.debug(
|
|
6776
|
+
log.debug(TAG27, `#${card.short_id} gave up: ${decision.detail}`);
|
|
6109
6777
|
}
|
|
6110
6778
|
return;
|
|
6111
6779
|
}
|
|
6112
6780
|
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
6113
6781
|
if (blockers === null) {
|
|
6114
|
-
log.warn(
|
|
6782
|
+
log.warn(TAG27, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
6115
6783
|
return;
|
|
6116
6784
|
}
|
|
6117
6785
|
if (blockers.length > 0) {
|
|
6118
6786
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
6119
|
-
log.info(
|
|
6787
|
+
log.info(TAG27, `#${card.short_id} blocked by ${list} — waiting`);
|
|
6120
6788
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
6121
6789
|
return;
|
|
6122
6790
|
}
|
|
@@ -6145,7 +6813,7 @@ class Pool {
|
|
|
6145
6813
|
});
|
|
6146
6814
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
6147
6815
|
} catch (err) {
|
|
6148
|
-
log.debug(
|
|
6816
|
+
log.debug(TAG27, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
6149
6817
|
}
|
|
6150
6818
|
}
|
|
6151
6819
|
noteApiError(err) {
|
|
@@ -6153,7 +6821,7 @@ class Pool {
|
|
|
6153
6821
|
return;
|
|
6154
6822
|
if (err.kind === "auth") {
|
|
6155
6823
|
if (!this.authPaused) {
|
|
6156
|
-
log.error(
|
|
6824
|
+
log.error(TAG27, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
6157
6825
|
}
|
|
6158
6826
|
this.authPaused = true;
|
|
6159
6827
|
return;
|
|
@@ -6162,7 +6830,7 @@ class Pool {
|
|
|
6162
6830
|
const until = Date.now() + cooldownMs;
|
|
6163
6831
|
if (until > this.apiCooldownUntil) {
|
|
6164
6832
|
this.apiCooldownUntil = until;
|
|
6165
|
-
log.warn(
|
|
6833
|
+
log.warn(TAG27, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
6166
6834
|
}
|
|
6167
6835
|
}
|
|
6168
6836
|
apiCooldownRemainingMs() {
|
|
@@ -6175,13 +6843,13 @@ class Pool {
|
|
|
6175
6843
|
const removed = queue.remove(cardId);
|
|
6176
6844
|
if (removed) {
|
|
6177
6845
|
this.cardDataCache.delete(cardId);
|
|
6178
|
-
log.info(
|
|
6846
|
+
log.info(TAG27, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
6179
6847
|
return;
|
|
6180
6848
|
}
|
|
6181
6849
|
}
|
|
6182
6850
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
6183
6851
|
if (worker) {
|
|
6184
|
-
log.info(
|
|
6852
|
+
log.info(TAG27, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
6185
6853
|
await worker.cancel();
|
|
6186
6854
|
}
|
|
6187
6855
|
}
|
|
@@ -6214,10 +6882,10 @@ class Pool {
|
|
|
6214
6882
|
async handleAgentCommand(cardId, command) {
|
|
6215
6883
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
6216
6884
|
if (!worker) {
|
|
6217
|
-
log.debug(
|
|
6885
|
+
log.debug(TAG27, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
6218
6886
|
return;
|
|
6219
6887
|
}
|
|
6220
|
-
log.info(
|
|
6888
|
+
log.info(TAG27, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
6221
6889
|
switch (command) {
|
|
6222
6890
|
case "pause":
|
|
6223
6891
|
await worker.pause();
|
|
@@ -6265,7 +6933,7 @@ class Pool {
|
|
|
6265
6933
|
};
|
|
6266
6934
|
}
|
|
6267
6935
|
async shutdown() {
|
|
6268
|
-
log.info(
|
|
6936
|
+
log.info(TAG27, "Shutting down pool...");
|
|
6269
6937
|
this.shuttingDown = true;
|
|
6270
6938
|
const active = [
|
|
6271
6939
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -6273,7 +6941,7 @@ class Pool {
|
|
|
6273
6941
|
];
|
|
6274
6942
|
await Promise.all(active.map((w) => w.cancel()));
|
|
6275
6943
|
this.sleepGuard.stop();
|
|
6276
|
-
log.info(
|
|
6944
|
+
log.info(TAG27, "Pool shutdown complete");
|
|
6277
6945
|
}
|
|
6278
6946
|
cardDataCache = new Map;
|
|
6279
6947
|
tryDispatchFor(workers, queue, label) {
|
|
@@ -6281,7 +6949,7 @@ class Pool {
|
|
|
6281
6949
|
return false;
|
|
6282
6950
|
const idle = workers.find((w) => w.isIdle);
|
|
6283
6951
|
if (!idle) {
|
|
6284
|
-
log.debug(
|
|
6952
|
+
log.debug(TAG27, `No idle ${label} workers (queue: ${queue.length})`);
|
|
6285
6953
|
return false;
|
|
6286
6954
|
}
|
|
6287
6955
|
const next = queue.dequeue();
|
|
@@ -6289,18 +6957,18 @@ class Pool {
|
|
|
6289
6957
|
return false;
|
|
6290
6958
|
const data = this.cardDataCache.get(next.cardId);
|
|
6291
6959
|
if (!data) {
|
|
6292
|
-
log.warn(
|
|
6960
|
+
log.warn(TAG27, `No cached data for card ${next.cardId}, skipping`);
|
|
6293
6961
|
return false;
|
|
6294
6962
|
}
|
|
6295
6963
|
this.cardDataCache.delete(next.cardId);
|
|
6296
6964
|
this.lastWaitingEmit.delete(next.cardId);
|
|
6297
|
-
log.info(
|
|
6965
|
+
log.info(TAG27, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
6298
6966
|
this.sleepGuard.acquire();
|
|
6299
6967
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
6300
6968
|
return true;
|
|
6301
6969
|
}
|
|
6302
6970
|
}
|
|
6303
|
-
var
|
|
6971
|
+
var TAG27 = "pool";
|
|
6304
6972
|
var init_pool = __esm(() => {
|
|
6305
6973
|
init_error_classifier();
|
|
6306
6974
|
init_log();
|
|
@@ -6342,7 +7010,7 @@ function load(path) {
|
|
|
6342
7010
|
return parsed;
|
|
6343
7011
|
return {};
|
|
6344
7012
|
} catch (err) {
|
|
6345
|
-
log.warn(
|
|
7013
|
+
log.warn(TAG28, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
6346
7014
|
return {};
|
|
6347
7015
|
}
|
|
6348
7016
|
}
|
|
@@ -6360,7 +7028,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
6360
7028
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
6361
7029
|
save(path, registry);
|
|
6362
7030
|
} catch (err) {
|
|
6363
|
-
log.warn(
|
|
7031
|
+
log.warn(TAG28, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6364
7032
|
}
|
|
6365
7033
|
}
|
|
6366
7034
|
function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
|
|
@@ -6376,10 +7044,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
6376
7044
|
delete registry[projectId];
|
|
6377
7045
|
save(path, registry);
|
|
6378
7046
|
} catch (err) {
|
|
6379
|
-
log.warn(
|
|
7047
|
+
log.warn(TAG28, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6380
7048
|
}
|
|
6381
7049
|
}
|
|
6382
|
-
var
|
|
7050
|
+
var TAG28 = "port-registry";
|
|
6383
7051
|
var init_port_registry = __esm(() => {
|
|
6384
7052
|
init_log();
|
|
6385
7053
|
});
|
|
@@ -6400,7 +7068,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
6400
7068
|
const { card } = await client.getCard(cardId);
|
|
6401
7069
|
return card;
|
|
6402
7070
|
} catch (err) {
|
|
6403
|
-
log.warn(
|
|
7071
|
+
log.warn(TAG29, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
6404
7072
|
return null;
|
|
6405
7073
|
}
|
|
6406
7074
|
}
|
|
@@ -6410,7 +7078,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
6410
7078
|
return [];
|
|
6411
7079
|
}
|
|
6412
7080
|
const outcomes = [];
|
|
6413
|
-
log.info(
|
|
7081
|
+
log.info(TAG29, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
6414
7082
|
for (const run of active) {
|
|
6415
7083
|
const outcome = {
|
|
6416
7084
|
runId: run.runId,
|
|
@@ -6422,11 +7090,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
6422
7090
|
};
|
|
6423
7091
|
outcomes.push(outcome);
|
|
6424
7092
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
6425
|
-
log.warn(
|
|
7093
|
+
log.warn(TAG29, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
6426
7094
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
6427
7095
|
continue;
|
|
6428
7096
|
}
|
|
6429
|
-
log.info(
|
|
7097
|
+
log.info(TAG29, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
6430
7098
|
await recoverRun(run, store, client, config, outcome);
|
|
6431
7099
|
}
|
|
6432
7100
|
return outcomes;
|
|
@@ -6444,7 +7112,7 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6444
7112
|
} catch (err) {
|
|
6445
7113
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6446
7114
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
6447
|
-
log.warn(
|
|
7115
|
+
log.warn(TAG29, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
6448
7116
|
}
|
|
6449
7117
|
const card = await fetchCardSafely(client, run.cardId);
|
|
6450
7118
|
if (card) {
|
|
@@ -6487,9 +7155,9 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6487
7155
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6488
7156
|
outcome.errors.push(`endRun: ${msg}`);
|
|
6489
7157
|
}
|
|
6490
|
-
log.info(
|
|
7158
|
+
log.info(TAG29, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
6491
7159
|
}
|
|
6492
|
-
var
|
|
7160
|
+
var TAG29 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
6493
7161
|
var init_recovery = __esm(() => {
|
|
6494
7162
|
init_board_helpers();
|
|
6495
7163
|
init_log();
|
|
@@ -6537,7 +7205,7 @@ class Reconciler {
|
|
|
6537
7205
|
clearInterval(this.timer);
|
|
6538
7206
|
this.timer = null;
|
|
6539
7207
|
}
|
|
6540
|
-
log.info(
|
|
7208
|
+
log.info(TAG30, "Heartbeat stopped");
|
|
6541
7209
|
}
|
|
6542
7210
|
async recoverStaleRuns() {
|
|
6543
7211
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -6554,7 +7222,7 @@ class Reconciler {
|
|
|
6554
7222
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
6555
7223
|
continue;
|
|
6556
7224
|
const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
|
|
6557
|
-
log.warn(
|
|
7225
|
+
log.warn(TAG30, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
6558
7226
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
6559
7227
|
runId: run.runId,
|
|
6560
7228
|
cardId: run.cardId,
|
|
@@ -6581,11 +7249,11 @@ class Reconciler {
|
|
|
6581
7249
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
6582
7250
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
6583
7251
|
continue;
|
|
6584
|
-
log.warn(
|
|
7252
|
+
log.warn(TAG30, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
6585
7253
|
try {
|
|
6586
7254
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6587
7255
|
} catch (err) {
|
|
6588
|
-
log.error(
|
|
7256
|
+
log.error(TAG30, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6589
7257
|
}
|
|
6590
7258
|
}
|
|
6591
7259
|
}
|
|
@@ -6608,11 +7276,11 @@ class Reconciler {
|
|
|
6608
7276
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
6609
7277
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
6610
7278
|
continue;
|
|
6611
|
-
log.warn(
|
|
7279
|
+
log.warn(TAG30, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
6612
7280
|
try {
|
|
6613
7281
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6614
7282
|
} catch (err) {
|
|
6615
|
-
log.error(
|
|
7283
|
+
log.error(TAG30, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6616
7284
|
}
|
|
6617
7285
|
}
|
|
6618
7286
|
}
|
|
@@ -6641,18 +7309,18 @@ class Reconciler {
|
|
|
6641
7309
|
const subtasks = card.subtasks ?? [];
|
|
6642
7310
|
const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
|
|
6643
7311
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
6644
|
-
log.debug(
|
|
7312
|
+
log.debug(TAG30, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
6645
7313
|
continue;
|
|
6646
7314
|
}
|
|
6647
7315
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
6648
|
-
log.debug(
|
|
7316
|
+
log.debug(TAG30, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
6649
7317
|
continue;
|
|
6650
7318
|
}
|
|
6651
7319
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
6652
|
-
log.debug(
|
|
7320
|
+
log.debug(TAG30, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
6653
7321
|
continue;
|
|
6654
7322
|
}
|
|
6655
|
-
log.info(
|
|
7323
|
+
log.info(TAG30, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
6656
7324
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
6657
7325
|
}
|
|
6658
7326
|
}
|
|
@@ -6662,18 +7330,18 @@ class Reconciler {
|
|
|
6662
7330
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
6663
7331
|
for (const knownId of knownCardIds) {
|
|
6664
7332
|
if (!allAgentCardIds.has(knownId)) {
|
|
6665
|
-
log.info(
|
|
7333
|
+
log.info(TAG30, `Missed unassign: ${knownId} — removing`);
|
|
6666
7334
|
await this.pool.removeCard(knownId);
|
|
6667
7335
|
}
|
|
6668
7336
|
}
|
|
6669
7337
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
6670
|
-
log.debug(
|
|
7338
|
+
log.debug(TAG30, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
6671
7339
|
} catch (err) {
|
|
6672
|
-
log.error(
|
|
7340
|
+
log.error(TAG30, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
6673
7341
|
}
|
|
6674
7342
|
}
|
|
6675
7343
|
}
|
|
6676
|
-
var
|
|
7344
|
+
var TAG30 = "reconcile";
|
|
6677
7345
|
var init_reconcile = __esm(() => {
|
|
6678
7346
|
init_board_helpers();
|
|
6679
7347
|
init_log();
|
|
@@ -6711,7 +7379,7 @@ function prettyBanner(config, version) {
|
|
|
6711
7379
|
checks.push({ kind: "ok", message });
|
|
6712
7380
|
},
|
|
6713
7381
|
warn(message) {
|
|
6714
|
-
log.warn(
|
|
7382
|
+
log.warn(TAG31, message);
|
|
6715
7383
|
checks.push({ kind: "warn", message: message.split(`
|
|
6716
7384
|
`, 1)[0] });
|
|
6717
7385
|
},
|
|
@@ -6736,25 +7404,25 @@ function prettyBanner(config, version) {
|
|
|
6736
7404
|
};
|
|
6737
7405
|
}
|
|
6738
7406
|
function jsonBanner(config, version) {
|
|
6739
|
-
log.info(
|
|
6740
|
-
log.info(
|
|
7407
|
+
log.info(TAG31, `Harmony Agent Daemon v${version} starting...`);
|
|
7408
|
+
log.info(TAG31, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
6741
7409
|
if (config.agent.review.enabled) {
|
|
6742
|
-
log.info(
|
|
7410
|
+
log.info(TAG31, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
6743
7411
|
}
|
|
6744
7412
|
let failed = false;
|
|
6745
7413
|
return {
|
|
6746
7414
|
setProjectName(_name) {},
|
|
6747
7415
|
setGitProvider(provider) {
|
|
6748
|
-
log.info(
|
|
7416
|
+
log.info(TAG31, `Git provider: ${provider}`);
|
|
6749
7417
|
},
|
|
6750
7418
|
setHttpPort(port) {
|
|
6751
|
-
log.info(
|
|
7419
|
+
log.info(TAG31, `HTTP server on port ${port}`);
|
|
6752
7420
|
},
|
|
6753
7421
|
check(message) {
|
|
6754
|
-
log.info(
|
|
7422
|
+
log.info(TAG31, message);
|
|
6755
7423
|
},
|
|
6756
7424
|
warn(message) {
|
|
6757
|
-
log.warn(
|
|
7425
|
+
log.warn(TAG31, message);
|
|
6758
7426
|
},
|
|
6759
7427
|
fail() {
|
|
6760
7428
|
failed = true;
|
|
@@ -6762,7 +7430,7 @@ function jsonBanner(config, version) {
|
|
|
6762
7430
|
async ready(message) {
|
|
6763
7431
|
if (failed)
|
|
6764
7432
|
return;
|
|
6765
|
-
log.info(
|
|
7433
|
+
log.info(TAG31, message);
|
|
6766
7434
|
}
|
|
6767
7435
|
};
|
|
6768
7436
|
}
|
|
@@ -6808,6 +7476,7 @@ function configRows(config, projectName, gitProvider, httpPort) {
|
|
|
6808
7476
|
label: "Model",
|
|
6809
7477
|
value: `${modelDesc} · ${poolDesc} · ${flowDesc}`
|
|
6810
7478
|
});
|
|
7479
|
+
rows.push({ label: "Runner", value: runnerDesc(config.agent.runner) });
|
|
6811
7480
|
const tail = [];
|
|
6812
7481
|
if (gitProvider)
|
|
6813
7482
|
tail.push(gitProvider);
|
|
@@ -6825,6 +7494,9 @@ function titleRule(title) {
|
|
|
6825
7494
|
const suffix = "─".repeat(Math.max(3, RULE_WIDTH - prefix.length - title.length - surround.length));
|
|
6826
7495
|
return dim(`${prefix}${title}${surround}${suffix}`);
|
|
6827
7496
|
}
|
|
7497
|
+
function runnerDesc(runner) {
|
|
7498
|
+
return runner === "sdk" ? "sdk (Agent SDK)" : "cli (Claude CLI)";
|
|
7499
|
+
}
|
|
6828
7500
|
function shortenId(id) {
|
|
6829
7501
|
if (id.length <= 8)
|
|
6830
7502
|
return id;
|
|
@@ -6839,7 +7511,7 @@ function cyan(s) {
|
|
|
6839
7511
|
function yellow(s) {
|
|
6840
7512
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
6841
7513
|
}
|
|
6842
|
-
var
|
|
7514
|
+
var TAG31 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
6843
7515
|
var init_startup_banner = __esm(() => {
|
|
6844
7516
|
init_log();
|
|
6845
7517
|
ANSI = {
|
|
@@ -6986,18 +7658,18 @@ class Watcher {
|
|
|
6986
7658
|
}
|
|
6987
7659
|
async start() {
|
|
6988
7660
|
if (!isPretty()) {
|
|
6989
|
-
log.info(
|
|
7661
|
+
log.info(TAG32, "Connecting to Supabase realtime (broadcast)...");
|
|
6990
7662
|
}
|
|
6991
7663
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
6992
7664
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
6993
7665
|
const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
6994
|
-
log.debug(
|
|
7666
|
+
log.debug(TAG32, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
6995
7667
|
this.onCardBroadcast({
|
|
6996
7668
|
event: "card_update",
|
|
6997
7669
|
payload: msg.payload ?? {}
|
|
6998
7670
|
});
|
|
6999
7671
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
7000
|
-
log.debug(
|
|
7672
|
+
log.debug(TAG32, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
7001
7673
|
this.onCardBroadcast({
|
|
7002
7674
|
event: "card_created",
|
|
7003
7675
|
payload: msg.payload ?? {}
|
|
@@ -7007,29 +7679,29 @@ class Watcher {
|
|
|
7007
7679
|
const cardId = payload.card_id;
|
|
7008
7680
|
const command = payload.command;
|
|
7009
7681
|
if (cardId && command) {
|
|
7010
|
-
log.info(
|
|
7682
|
+
log.info(TAG32, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
7011
7683
|
this.onAgentCommand?.({ cardId, command });
|
|
7012
7684
|
}
|
|
7013
7685
|
}).subscribe((status) => {
|
|
7014
7686
|
if (status === "SUBSCRIBED") {
|
|
7015
7687
|
this.connected = true;
|
|
7016
7688
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
7017
|
-
log.info(
|
|
7689
|
+
log.info(TAG32, "Broadcast subscription active");
|
|
7018
7690
|
}
|
|
7019
7691
|
this.maybeResolveReady();
|
|
7020
7692
|
} else if (status === "CHANNEL_ERROR") {
|
|
7021
7693
|
this.connected = false;
|
|
7022
|
-
log.error(
|
|
7694
|
+
log.error(TAG32, "Broadcast channel error — will rely on reconciliation");
|
|
7023
7695
|
} else if (status === "TIMED_OUT") {
|
|
7024
7696
|
this.connected = false;
|
|
7025
|
-
log.warn(
|
|
7697
|
+
log.warn(TAG32, "Broadcast subscription timed out — retrying...");
|
|
7026
7698
|
} else if (status === "CLOSED") {
|
|
7027
7699
|
this.connected = false;
|
|
7028
7700
|
}
|
|
7029
7701
|
});
|
|
7030
7702
|
this.channel = channel;
|
|
7031
7703
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
7032
|
-
log.debug(
|
|
7704
|
+
log.debug(TAG32, "Presence sync");
|
|
7033
7705
|
}).subscribe(async (status) => {
|
|
7034
7706
|
if (status === "SUBSCRIBED") {
|
|
7035
7707
|
await presenceChannel.track({
|
|
@@ -7042,7 +7714,7 @@ class Watcher {
|
|
|
7042
7714
|
agentName: this.identity.agentName
|
|
7043
7715
|
});
|
|
7044
7716
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
7045
|
-
log.info(
|
|
7717
|
+
log.info(TAG32, "Presence tracked on board-presence channel");
|
|
7046
7718
|
}
|
|
7047
7719
|
this.presenceTracked = true;
|
|
7048
7720
|
this.maybeResolveReady();
|
|
@@ -7064,10 +7736,10 @@ class Watcher {
|
|
|
7064
7736
|
this.supabase = null;
|
|
7065
7737
|
}
|
|
7066
7738
|
this.connected = false;
|
|
7067
|
-
log.info(
|
|
7739
|
+
log.info(TAG32, "Broadcast subscription stopped");
|
|
7068
7740
|
}
|
|
7069
7741
|
}
|
|
7070
|
-
var
|
|
7742
|
+
var TAG32 = "watcher";
|
|
7071
7743
|
var init_watcher = __esm(() => {
|
|
7072
7744
|
init_log();
|
|
7073
7745
|
});
|
|
@@ -7154,10 +7826,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
7154
7826
|
});
|
|
7155
7827
|
} catch {}
|
|
7156
7828
|
if (result.removed.length > 0) {
|
|
7157
|
-
log.info(
|
|
7829
|
+
log.info(TAG33, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
7158
7830
|
}
|
|
7159
7831
|
if (result.errors.length > 0) {
|
|
7160
|
-
log.warn(
|
|
7832
|
+
log.warn(TAG33, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
7161
7833
|
}
|
|
7162
7834
|
return result;
|
|
7163
7835
|
}
|
|
@@ -7187,7 +7859,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
7187
7859
|
} catch (err) {
|
|
7188
7860
|
const detail = gitErrorDetail(err);
|
|
7189
7861
|
if (isTransientGitNetworkError(detail)) {
|
|
7190
|
-
log.debug(
|
|
7862
|
+
log.debug(TAG33, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
7191
7863
|
return result;
|
|
7192
7864
|
}
|
|
7193
7865
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -7226,7 +7898,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
7226
7898
|
continue;
|
|
7227
7899
|
}
|
|
7228
7900
|
if (clock() > sweepDeadline) {
|
|
7229
|
-
log.debug(
|
|
7901
|
+
log.debug(TAG33, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
7230
7902
|
break;
|
|
7231
7903
|
}
|
|
7232
7904
|
try {
|
|
@@ -7239,17 +7911,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
7239
7911
|
} catch (err) {
|
|
7240
7912
|
const detail = gitErrorDetail(err);
|
|
7241
7913
|
if (isTransientGitNetworkError(detail)) {
|
|
7242
|
-
log.debug(
|
|
7914
|
+
log.debug(TAG33, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
7243
7915
|
break;
|
|
7244
7916
|
}
|
|
7245
7917
|
result.errors.push({ ref, error: detail });
|
|
7246
7918
|
}
|
|
7247
7919
|
}
|
|
7248
7920
|
if (result.removed.length > 0) {
|
|
7249
|
-
log.info(
|
|
7921
|
+
log.info(TAG33, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
7250
7922
|
}
|
|
7251
7923
|
if (result.errors.length > 0) {
|
|
7252
|
-
log.warn(
|
|
7924
|
+
log.warn(TAG33, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
7253
7925
|
}
|
|
7254
7926
|
return result;
|
|
7255
7927
|
}
|
|
@@ -7280,13 +7952,13 @@ class WorktreeGc {
|
|
|
7280
7952
|
try {
|
|
7281
7953
|
runWorktreeGc(this.basePath, this.store);
|
|
7282
7954
|
} catch (err) {
|
|
7283
|
-
log.warn(
|
|
7955
|
+
log.warn(TAG33, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
7284
7956
|
}
|
|
7285
7957
|
if (this.remoteOpts) {
|
|
7286
7958
|
try {
|
|
7287
7959
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
7288
7960
|
} catch (err) {
|
|
7289
|
-
log.warn(
|
|
7961
|
+
log.warn(TAG33, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
7290
7962
|
}
|
|
7291
7963
|
}
|
|
7292
7964
|
}
|
|
@@ -7300,7 +7972,7 @@ function getRepoRoot2() {
|
|
|
7300
7972
|
return null;
|
|
7301
7973
|
}
|
|
7302
7974
|
}
|
|
7303
|
-
var
|
|
7975
|
+
var TAG33 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
|
|
7304
7976
|
var init_worktree_gc = __esm(() => {
|
|
7305
7977
|
init_log();
|
|
7306
7978
|
init_worktree();
|
|
@@ -7404,7 +8076,7 @@ async function main() {
|
|
|
7404
8076
|
} catch (err) {
|
|
7405
8077
|
if (err instanceof ConfigValidationError) {
|
|
7406
8078
|
banner.fail();
|
|
7407
|
-
log.error(
|
|
8079
|
+
log.error(TAG34, err.message);
|
|
7408
8080
|
process.exit(1);
|
|
7409
8081
|
}
|
|
7410
8082
|
throw err;
|
|
@@ -7472,6 +8144,7 @@ async function main() {
|
|
|
7472
8144
|
daemonPid: process.pid,
|
|
7473
8145
|
startedAt,
|
|
7474
8146
|
uptimeMs: Date.now() - startedAt,
|
|
8147
|
+
runner: config.agent.runner,
|
|
7475
8148
|
workers: pool.snapshotWorkers().map((w) => ({
|
|
7476
8149
|
...w,
|
|
7477
8150
|
phase: null
|
|
@@ -7513,7 +8186,7 @@ async function main() {
|
|
|
7513
8186
|
if (shuttingDown)
|
|
7514
8187
|
return;
|
|
7515
8188
|
shuttingDown = true;
|
|
7516
|
-
log.info(
|
|
8189
|
+
log.info(TAG34, `Received ${signal}, shutting down gracefully...`);
|
|
7517
8190
|
reconciler.stop();
|
|
7518
8191
|
mergeMonitor?.stop();
|
|
7519
8192
|
worktreeGc.stop();
|
|
@@ -7523,18 +8196,18 @@ async function main() {
|
|
|
7523
8196
|
}
|
|
7524
8197
|
await watcher.stop();
|
|
7525
8198
|
await pool.shutdown();
|
|
7526
|
-
log.info(
|
|
8199
|
+
log.info(TAG34, "Daemon stopped.");
|
|
7527
8200
|
process.exit(exitCode);
|
|
7528
8201
|
};
|
|
7529
8202
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
7530
8203
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
7531
8204
|
process.on("uncaughtException", (err) => {
|
|
7532
|
-
log.error(
|
|
8205
|
+
log.error(TAG34, `Uncaught exception: ${err.message}`);
|
|
7533
8206
|
exitCode = 1;
|
|
7534
8207
|
shutdown("uncaughtException");
|
|
7535
8208
|
});
|
|
7536
8209
|
process.on("unhandledRejection", (reason) => {
|
|
7537
|
-
log.error(
|
|
8210
|
+
log.error(TAG34, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
7538
8211
|
exitCode = 1;
|
|
7539
8212
|
shutdown("unhandledRejection");
|
|
7540
8213
|
});
|
|
@@ -7587,35 +8260,35 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
7587
8260
|
if (assignedAgentId === undefined)
|
|
7588
8261
|
return;
|
|
7589
8262
|
if (assignedAgentId === agentId) {
|
|
7590
|
-
log.info(
|
|
8263
|
+
log.info(TAG34, `Broadcast: card ${cardId} assigned to agent`);
|
|
7591
8264
|
try {
|
|
7592
8265
|
await pool.resetAttemptsForReassign(cardId);
|
|
7593
8266
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
7594
8267
|
} catch (err) {
|
|
7595
|
-
log.error(
|
|
8268
|
+
log.error(TAG34, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
7596
8269
|
}
|
|
7597
8270
|
} else if (pool.isCardKnown(cardId)) {
|
|
7598
|
-
log.info(
|
|
8271
|
+
log.info(TAG34, `Broadcast: card ${cardId} unassigned from agent`);
|
|
7599
8272
|
await pool.removeCard(cardId);
|
|
7600
8273
|
}
|
|
7601
8274
|
}
|
|
7602
8275
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
7603
8276
|
const { card } = await client.getCard(cardId);
|
|
7604
8277
|
if (card.assigned_agent_id !== agentId) {
|
|
7605
|
-
log.debug(
|
|
8278
|
+
log.debug(TAG34, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
7606
8279
|
return;
|
|
7607
8280
|
}
|
|
7608
8281
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
7609
8282
|
const columns = board.columns;
|
|
7610
8283
|
const column = columns.find((c) => c.id === card.column_id);
|
|
7611
8284
|
if (!column) {
|
|
7612
|
-
log.warn(
|
|
8285
|
+
log.warn(TAG34, `Column not found for card ${cardId}`);
|
|
7613
8286
|
return;
|
|
7614
8287
|
}
|
|
7615
8288
|
const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7616
8289
|
const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7617
8290
|
if (!isPickupColumn && !isReviewColumn) {
|
|
7618
|
-
log.info(
|
|
8291
|
+
log.info(TAG34, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
|
|
7619
8292
|
return;
|
|
7620
8293
|
}
|
|
7621
8294
|
const mode = isReviewColumn ? "review" : "implement";
|
|
@@ -7623,16 +8296,16 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
7623
8296
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
7624
8297
|
const subtasks = card.subtasks ?? [];
|
|
7625
8298
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
7626
|
-
log.debug(
|
|
8299
|
+
log.debug(TAG34, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
7627
8300
|
return;
|
|
7628
8301
|
}
|
|
7629
8302
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
7630
|
-
log.info(
|
|
8303
|
+
log.info(TAG34, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
7631
8304
|
return;
|
|
7632
8305
|
}
|
|
7633
8306
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
7634
8307
|
}
|
|
7635
|
-
var
|
|
8308
|
+
var TAG34 = "daemon", PKG_VERSION;
|
|
7636
8309
|
var init_src = __esm(() => {
|
|
7637
8310
|
init_board_helpers();
|
|
7638
8311
|
init_config();
|