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