@gethmy/agent 1.10.9 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +857 -194
- package/dist/index.js +857 -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);
|
|
@@ -5675,6 +6179,26 @@ class Worker {
|
|
|
5675
6179
|
} catch {}
|
|
5676
6180
|
await this.recordOutcome(card.id, "failure");
|
|
5677
6181
|
}
|
|
6182
|
+
if (this.cliRunner) {
|
|
6183
|
+
if (succeeded) {
|
|
6184
|
+
this.cliRunner.recordFinished({ status: "completed" });
|
|
6185
|
+
} else if (this.timedOut) {
|
|
6186
|
+
this.cliRunner.recordFinished({
|
|
6187
|
+
status: "failed",
|
|
6188
|
+
failureReason: "timeout"
|
|
6189
|
+
});
|
|
6190
|
+
} else if (this.aborted) {
|
|
6191
|
+
this.cliRunner.recordFinished({
|
|
6192
|
+
status: "stopped",
|
|
6193
|
+
stopReason: "user_requested"
|
|
6194
|
+
});
|
|
6195
|
+
} else {
|
|
6196
|
+
this.cliRunner.recordFinished({ status: "failed" });
|
|
6197
|
+
}
|
|
6198
|
+
try {
|
|
6199
|
+
await this.cliRunner.flushFinal();
|
|
6200
|
+
} catch {}
|
|
6201
|
+
}
|
|
5678
6202
|
this.cleanup();
|
|
5679
6203
|
this.state = "idle";
|
|
5680
6204
|
this.onDone(this);
|
|
@@ -5764,7 +6288,9 @@ class Worker {
|
|
|
5764
6288
|
this.aborted = true;
|
|
5765
6289
|
this.state = "cancelling";
|
|
5766
6290
|
log.info(this.tag, `Cancelling work on ${this.cardId}`);
|
|
5767
|
-
if (this.
|
|
6291
|
+
if (this.sdkRunner) {
|
|
6292
|
+
await this.sdkRunner.stop(this.timedOut ? "timeout" : "user_requested");
|
|
6293
|
+
} else if (this.process && !this.process.killed) {
|
|
5768
6294
|
await terminateGroup(this.process, {
|
|
5769
6295
|
sigintTimeoutMs: CANCEL_SIGINT_TIMEOUT2,
|
|
5770
6296
|
sigtermTimeoutMs: CANCEL_SIGTERM_TIMEOUT2
|
|
@@ -5799,7 +6325,9 @@ class Worker {
|
|
|
5799
6325
|
const planTimeout = setTimeout(() => {
|
|
5800
6326
|
planTimedOut = true;
|
|
5801
6327
|
log.warn(this.tag, "Planning pass exceeded timeout — abandoning, implementing directly");
|
|
5802
|
-
if (this.
|
|
6328
|
+
if (this.sdkRunner) {
|
|
6329
|
+
this.sdkRunner.stop("timeout").catch(() => {});
|
|
6330
|
+
} else if (this.process && !this.process.killed) {
|
|
5803
6331
|
terminateGroup(this.process, {
|
|
5804
6332
|
sigintTimeoutMs: 1e4,
|
|
5805
6333
|
sigtermTimeoutMs: 5000
|
|
@@ -5893,7 +6421,40 @@ class Worker {
|
|
|
5893
6421
|
}
|
|
5894
6422
|
return false;
|
|
5895
6423
|
}
|
|
5896
|
-
async
|
|
6424
|
+
async drainSteeringMessages(card, subtasks) {
|
|
6425
|
+
if (!this.cliSessionId || !this.sessionId || !this.cardId)
|
|
6426
|
+
return;
|
|
6427
|
+
for (let i = 0;i < MAX_STEERING_ITERATIONS && !this.aborted; i++) {
|
|
6428
|
+
let messages;
|
|
6429
|
+
try {
|
|
6430
|
+
const res = await this.client.getPendingUserMessages(this.cardId, this.sessionId, this.lastDrainedSeq);
|
|
6431
|
+
messages = res.messages ?? [];
|
|
6432
|
+
} catch (err) {
|
|
6433
|
+
log.warn(this.tag, `Failed to fetch steering messages (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
if (messages.length === 0)
|
|
6437
|
+
return;
|
|
6438
|
+
this.lastDrainedSeq = Math.max(this.lastDrainedSeq, ...messages.map((m) => m.seq));
|
|
6439
|
+
log.info(this.tag, `Steering #${card.short_id}: resuming with ${messages.length} queued message(s)`);
|
|
6440
|
+
this.state = "running";
|
|
6441
|
+
await this.recordPhase("running");
|
|
6442
|
+
try {
|
|
6443
|
+
await this.spawnClaude(buildSteeringPrompt(messages.map((m) => m.text)), card, subtasks, {
|
|
6444
|
+
model: this.selectImplementModel(card),
|
|
6445
|
+
maxTurns: STEERING_MAX_TURNS,
|
|
6446
|
+
resumeSessionId: this.cliSessionId
|
|
6447
|
+
});
|
|
6448
|
+
} catch (err) {
|
|
6449
|
+
log.warn(this.tag, `Steering resume failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
6450
|
+
return;
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
spawnClaude(prompt, card, subtasks, opts = {}) {
|
|
6455
|
+
return this.config.runner === "sdk" ? this.spawnClaudeSdk(prompt, card, subtasks, opts) : this.spawnClaudeCli(prompt, card, subtasks, opts);
|
|
6456
|
+
}
|
|
6457
|
+
async spawnClaudeCli(prompt, card, subtasks, opts = {}) {
|
|
5897
6458
|
const model = opts.model ?? this.config.claude.model;
|
|
5898
6459
|
const maxTurns = opts.maxTurns ?? this.config.claude.maxTurns;
|
|
5899
6460
|
const allowedTools = opts.allowedTools ?? IMPLEMENT_ALLOWED_TOOLS;
|
|
@@ -5910,6 +6471,7 @@ class Worker {
|
|
|
5910
6471
|
String(maxTurns),
|
|
5911
6472
|
"--allowedTools",
|
|
5912
6473
|
allowedTools,
|
|
6474
|
+
...opts.resumeSessionId ? ["--resume", opts.resumeSessionId] : [],
|
|
5913
6475
|
...this.config.claude.additionalArgs,
|
|
5914
6476
|
"--",
|
|
5915
6477
|
prompt
|
|
@@ -5929,10 +6491,11 @@ class Worker {
|
|
|
5929
6491
|
});
|
|
5930
6492
|
const parser = new StreamParser;
|
|
5931
6493
|
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
|
|
5932
|
-
if (this.sessionId) {
|
|
5933
|
-
this.progressTracker.setSessionId(this.sessionId);
|
|
5934
|
-
}
|
|
5935
6494
|
this.progressTracker.attach(parser);
|
|
6495
|
+
this.cliRunner?.attach(parser);
|
|
6496
|
+
if (this.cliRunner) {
|
|
6497
|
+
this.progressTracker.setRunEventSink(this.cliRunner);
|
|
6498
|
+
}
|
|
5936
6499
|
if (this.process.stdout) {
|
|
5937
6500
|
parser.attach(this.process.stdout);
|
|
5938
6501
|
if (runLog) {
|
|
@@ -5956,14 +6519,16 @@ class Worker {
|
|
|
5956
6519
|
reject(new Error(`Failed to spawn claude: ${err.message}`));
|
|
5957
6520
|
});
|
|
5958
6521
|
this.process.on("close", (code) => {
|
|
6522
|
+
const leaderPid = this.process?.pid;
|
|
5959
6523
|
this.process = null;
|
|
6524
|
+
if (parser.sessionId)
|
|
6525
|
+
this.cliSessionId = parser.sessionId;
|
|
5960
6526
|
this.lastSessionStats = this.progressTracker?.stats;
|
|
5961
6527
|
const spawnCost = this.lastSessionStats?.cost;
|
|
5962
6528
|
if (spawnCost) {
|
|
5963
6529
|
this.runCostCents += Math.round(spawnCost.totalCostUsd * 100);
|
|
5964
6530
|
this.runTurns += spawnCost.numTurns;
|
|
5965
6531
|
}
|
|
5966
|
-
this.progressTracker?.flushFinal();
|
|
5967
6532
|
this.progressTracker?.stop();
|
|
5968
6533
|
this.progressTracker = null;
|
|
5969
6534
|
if (runLog) {
|
|
@@ -5973,6 +6538,7 @@ class Worker {
|
|
|
5973
6538
|
`);
|
|
5974
6539
|
runLog.stream.end();
|
|
5975
6540
|
}
|
|
6541
|
+
reapGroup(leaderPid);
|
|
5976
6542
|
if (this.aborted) {
|
|
5977
6543
|
resolve3();
|
|
5978
6544
|
} else if (code === 0) {
|
|
@@ -5985,11 +6551,101 @@ class Worker {
|
|
|
5985
6551
|
});
|
|
5986
6552
|
});
|
|
5987
6553
|
}
|
|
6554
|
+
async spawnClaudeSdk(prompt, card, subtasks, opts = {}) {
|
|
6555
|
+
const model = opts.model ?? this.config.claude.model;
|
|
6556
|
+
const maxTurns = opts.maxTurns ?? this.config.claude.maxTurns;
|
|
6557
|
+
const allowedTools = (opts.allowedTools ?? IMPLEMENT_ALLOWED_TOOLS).split(",").map((t) => t.trim()).filter(Boolean);
|
|
6558
|
+
const initialPhase = opts.initialPhase ?? "exploring";
|
|
6559
|
+
const sdkCfg = this.config.sdk;
|
|
6560
|
+
log.info(this.tag, `Spawning Agent SDK runner (model=${model}, maxTurns=${maxTurns}${opts.resumeSessionId ? ", resume" : ""})`);
|
|
6561
|
+
const runLog = openRunLog(this.tag, this.runId, card.short_id);
|
|
6562
|
+
if (runLog) {
|
|
6563
|
+
log.info(this.tag, `Run log: ${runLog.path}`);
|
|
6564
|
+
runLog.stream.write(`# run=${this.runId} card=#${card.short_id} runner=sdk started=${new Date().toISOString()}
|
|
6565
|
+
` + `# model=${model} maxTurns=${maxTurns} <prompt:${prompt.length} chars>
|
|
6566
|
+
|
|
6567
|
+
`);
|
|
6568
|
+
}
|
|
6569
|
+
this.progressTracker = new ProgressTracker(this.client, card.id, this.id, subtasks, initialPhase);
|
|
6570
|
+
if (this.cliRunner) {
|
|
6571
|
+
this.progressTracker.setRunEventSink(this.cliRunner);
|
|
6572
|
+
}
|
|
6573
|
+
const runner = new SdkAgentRunner({
|
|
6574
|
+
model,
|
|
6575
|
+
maxTurns,
|
|
6576
|
+
allowedTools,
|
|
6577
|
+
maxBudgetUsd: sdkCfg?.maxBudgetUsd,
|
|
6578
|
+
settingSources: sdkCfg?.settingSources,
|
|
6579
|
+
mcpServers: sdkCfg?.mcpServers,
|
|
6580
|
+
strictMcpConfig: sdkCfg?.strictMcpConfig,
|
|
6581
|
+
onSpawn: (child) => {
|
|
6582
|
+
this.process = child;
|
|
6583
|
+
}
|
|
6584
|
+
});
|
|
6585
|
+
this.sdkRunner = runner;
|
|
6586
|
+
const baseInput = {
|
|
6587
|
+
sessionId: this.sessionId ?? "",
|
|
6588
|
+
cardId: card.id,
|
|
6589
|
+
workspaceId: this.workspaceId,
|
|
6590
|
+
prompt,
|
|
6591
|
+
cwd: this.worktreePath,
|
|
6592
|
+
model
|
|
6593
|
+
};
|
|
6594
|
+
const stream = opts.resumeSessionId ? runner.resume({ ...baseInput, resumeSessionId: opts.resumeSessionId }) : runner.start(baseInput);
|
|
6595
|
+
let failure = null;
|
|
6596
|
+
let failureKind = null;
|
|
6597
|
+
try {
|
|
6598
|
+
for await (const ev of stream) {
|
|
6599
|
+
this.progressTracker?.ingest(ev);
|
|
6600
|
+
if (ev.source === "agent")
|
|
6601
|
+
this.cliRunner?.record(ev);
|
|
6602
|
+
if (ev.kind === "error") {
|
|
6603
|
+
failure = ev.payload.message;
|
|
6604
|
+
if (ev.payload.errorKind != null)
|
|
6605
|
+
failureKind = ev.payload.errorKind;
|
|
6606
|
+
}
|
|
6607
|
+
runLog?.stream.write(`[${ev.kind}] ${sdkDraftLogLine(ev)}
|
|
6608
|
+
`);
|
|
6609
|
+
}
|
|
6610
|
+
} finally {
|
|
6611
|
+
this.cliSessionId = runner.sessionId ?? this.cliSessionId;
|
|
6612
|
+
this.lastSessionStats = this.progressTracker?.stats;
|
|
6613
|
+
const spawnCost = this.lastSessionStats?.cost;
|
|
6614
|
+
if (spawnCost) {
|
|
6615
|
+
this.runCostCents += Math.round(spawnCost.totalCostUsd * 100);
|
|
6616
|
+
this.runTurns += spawnCost.numTurns;
|
|
6617
|
+
}
|
|
6618
|
+
if (runLog) {
|
|
6619
|
+
const stats = this.lastSessionStats;
|
|
6620
|
+
runLog.stream.write(`
|
|
6621
|
+
# runner=sdk aborted=${this.aborted} ` + `toolCalls=${stats?.toolCalls ?? 0} filesEdited=${stats?.filesEdited ?? 0} ` + `cost=$${stats?.cost?.totalCostUsd.toFixed(4) ?? "0"} ` + `ended=${new Date().toISOString()}
|
|
6622
|
+
`);
|
|
6623
|
+
runLog.stream.end();
|
|
6624
|
+
}
|
|
6625
|
+
this.progressTracker?.stop();
|
|
6626
|
+
this.progressTracker = null;
|
|
6627
|
+
this.process = null;
|
|
6628
|
+
this.sdkRunner = null;
|
|
6629
|
+
}
|
|
6630
|
+
if (this.aborted)
|
|
6631
|
+
return;
|
|
6632
|
+
if (failure) {
|
|
6633
|
+
const err = new Error(failure);
|
|
6634
|
+
err.stderr = runner.capturedStderrText;
|
|
6635
|
+
err.errorKind = failureKind;
|
|
6636
|
+
throw err;
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
5988
6639
|
cleanup() {
|
|
5989
6640
|
if (this.progressTracker) {
|
|
5990
6641
|
this.progressTracker.stop();
|
|
5991
6642
|
this.progressTracker = null;
|
|
5992
6643
|
}
|
|
6644
|
+
if (this.cliRunner) {
|
|
6645
|
+
this.cliRunner.stop();
|
|
6646
|
+
this.cliRunner = null;
|
|
6647
|
+
}
|
|
6648
|
+
this.sdkRunner = null;
|
|
5993
6649
|
this.stopHeartbeat();
|
|
5994
6650
|
this.lastSessionStats = undefined;
|
|
5995
6651
|
if (this.timeoutTimer) {
|
|
@@ -6014,9 +6670,10 @@ class Worker {
|
|
|
6014
6670
|
this.runTurns = 0;
|
|
6015
6671
|
}
|
|
6016
6672
|
}
|
|
6017
|
-
var
|
|
6673
|
+
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
6674
|
var init_worker = __esm(() => {
|
|
6019
6675
|
init_board_helpers();
|
|
6676
|
+
init_cli_agent_runner();
|
|
6020
6677
|
init_completion();
|
|
6021
6678
|
init_error_classifier();
|
|
6022
6679
|
init_log();
|
|
@@ -6026,6 +6683,7 @@ var init_worker = __esm(() => {
|
|
|
6026
6683
|
init_progress_tracker();
|
|
6027
6684
|
init_prompt();
|
|
6028
6685
|
init_run_log();
|
|
6686
|
+
init_sdk_agent_runner();
|
|
6029
6687
|
init_state_store();
|
|
6030
6688
|
init_stream_parser();
|
|
6031
6689
|
init_transitions();
|
|
@@ -6084,39 +6742,39 @@ class Pool {
|
|
|
6084
6742
|
}
|
|
6085
6743
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
6086
6744
|
if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
|
|
6087
|
-
log.debug(
|
|
6745
|
+
log.debug(TAG27, `Card ${card.id} already queued or active, skipping`);
|
|
6088
6746
|
return;
|
|
6089
6747
|
}
|
|
6090
6748
|
if (mode === "implement") {
|
|
6091
6749
|
if (this.authPaused) {
|
|
6092
|
-
log.debug(
|
|
6750
|
+
log.debug(TAG27, `#${card.short_id} held — agent paused (auth error)`);
|
|
6093
6751
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
6094
6752
|
return;
|
|
6095
6753
|
}
|
|
6096
6754
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
6097
6755
|
if (cooldownMs > 0) {
|
|
6098
|
-
log.debug(
|
|
6756
|
+
log.debug(TAG27, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
6099
6757
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
6100
6758
|
return;
|
|
6101
6759
|
}
|
|
6102
6760
|
const decision = this.budget.check(card.id);
|
|
6103
6761
|
if (!decision.allow) {
|
|
6104
6762
|
if (decision.reason === "daily_budget") {
|
|
6105
|
-
log.warn(
|
|
6763
|
+
log.warn(TAG27, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
6106
6764
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
6107
6765
|
} else {
|
|
6108
|
-
log.debug(
|
|
6766
|
+
log.debug(TAG27, `#${card.short_id} gave up: ${decision.detail}`);
|
|
6109
6767
|
}
|
|
6110
6768
|
return;
|
|
6111
6769
|
}
|
|
6112
6770
|
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
6113
6771
|
if (blockers === null) {
|
|
6114
|
-
log.warn(
|
|
6772
|
+
log.warn(TAG27, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
6115
6773
|
return;
|
|
6116
6774
|
}
|
|
6117
6775
|
if (blockers.length > 0) {
|
|
6118
6776
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
6119
|
-
log.info(
|
|
6777
|
+
log.info(TAG27, `#${card.short_id} blocked by ${list} — waiting`);
|
|
6120
6778
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
6121
6779
|
return;
|
|
6122
6780
|
}
|
|
@@ -6145,7 +6803,7 @@ class Pool {
|
|
|
6145
6803
|
});
|
|
6146
6804
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
6147
6805
|
} catch (err) {
|
|
6148
|
-
log.debug(
|
|
6806
|
+
log.debug(TAG27, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
6149
6807
|
}
|
|
6150
6808
|
}
|
|
6151
6809
|
noteApiError(err) {
|
|
@@ -6153,7 +6811,7 @@ class Pool {
|
|
|
6153
6811
|
return;
|
|
6154
6812
|
if (err.kind === "auth") {
|
|
6155
6813
|
if (!this.authPaused) {
|
|
6156
|
-
log.error(
|
|
6814
|
+
log.error(TAG27, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
6157
6815
|
}
|
|
6158
6816
|
this.authPaused = true;
|
|
6159
6817
|
return;
|
|
@@ -6162,7 +6820,7 @@ class Pool {
|
|
|
6162
6820
|
const until = Date.now() + cooldownMs;
|
|
6163
6821
|
if (until > this.apiCooldownUntil) {
|
|
6164
6822
|
this.apiCooldownUntil = until;
|
|
6165
|
-
log.warn(
|
|
6823
|
+
log.warn(TAG27, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
6166
6824
|
}
|
|
6167
6825
|
}
|
|
6168
6826
|
apiCooldownRemainingMs() {
|
|
@@ -6175,13 +6833,13 @@ class Pool {
|
|
|
6175
6833
|
const removed = queue.remove(cardId);
|
|
6176
6834
|
if (removed) {
|
|
6177
6835
|
this.cardDataCache.delete(cardId);
|
|
6178
|
-
log.info(
|
|
6836
|
+
log.info(TAG27, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
6179
6837
|
return;
|
|
6180
6838
|
}
|
|
6181
6839
|
}
|
|
6182
6840
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
6183
6841
|
if (worker) {
|
|
6184
|
-
log.info(
|
|
6842
|
+
log.info(TAG27, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
6185
6843
|
await worker.cancel();
|
|
6186
6844
|
}
|
|
6187
6845
|
}
|
|
@@ -6214,10 +6872,10 @@ class Pool {
|
|
|
6214
6872
|
async handleAgentCommand(cardId, command) {
|
|
6215
6873
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
6216
6874
|
if (!worker) {
|
|
6217
|
-
log.debug(
|
|
6875
|
+
log.debug(TAG27, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
6218
6876
|
return;
|
|
6219
6877
|
}
|
|
6220
|
-
log.info(
|
|
6878
|
+
log.info(TAG27, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
6221
6879
|
switch (command) {
|
|
6222
6880
|
case "pause":
|
|
6223
6881
|
await worker.pause();
|
|
@@ -6265,7 +6923,7 @@ class Pool {
|
|
|
6265
6923
|
};
|
|
6266
6924
|
}
|
|
6267
6925
|
async shutdown() {
|
|
6268
|
-
log.info(
|
|
6926
|
+
log.info(TAG27, "Shutting down pool...");
|
|
6269
6927
|
this.shuttingDown = true;
|
|
6270
6928
|
const active = [
|
|
6271
6929
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -6273,7 +6931,7 @@ class Pool {
|
|
|
6273
6931
|
];
|
|
6274
6932
|
await Promise.all(active.map((w) => w.cancel()));
|
|
6275
6933
|
this.sleepGuard.stop();
|
|
6276
|
-
log.info(
|
|
6934
|
+
log.info(TAG27, "Pool shutdown complete");
|
|
6277
6935
|
}
|
|
6278
6936
|
cardDataCache = new Map;
|
|
6279
6937
|
tryDispatchFor(workers, queue, label) {
|
|
@@ -6281,7 +6939,7 @@ class Pool {
|
|
|
6281
6939
|
return false;
|
|
6282
6940
|
const idle = workers.find((w) => w.isIdle);
|
|
6283
6941
|
if (!idle) {
|
|
6284
|
-
log.debug(
|
|
6942
|
+
log.debug(TAG27, `No idle ${label} workers (queue: ${queue.length})`);
|
|
6285
6943
|
return false;
|
|
6286
6944
|
}
|
|
6287
6945
|
const next = queue.dequeue();
|
|
@@ -6289,18 +6947,18 @@ class Pool {
|
|
|
6289
6947
|
return false;
|
|
6290
6948
|
const data = this.cardDataCache.get(next.cardId);
|
|
6291
6949
|
if (!data) {
|
|
6292
|
-
log.warn(
|
|
6950
|
+
log.warn(TAG27, `No cached data for card ${next.cardId}, skipping`);
|
|
6293
6951
|
return false;
|
|
6294
6952
|
}
|
|
6295
6953
|
this.cardDataCache.delete(next.cardId);
|
|
6296
6954
|
this.lastWaitingEmit.delete(next.cardId);
|
|
6297
|
-
log.info(
|
|
6955
|
+
log.info(TAG27, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
6298
6956
|
this.sleepGuard.acquire();
|
|
6299
6957
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
6300
6958
|
return true;
|
|
6301
6959
|
}
|
|
6302
6960
|
}
|
|
6303
|
-
var
|
|
6961
|
+
var TAG27 = "pool";
|
|
6304
6962
|
var init_pool = __esm(() => {
|
|
6305
6963
|
init_error_classifier();
|
|
6306
6964
|
init_log();
|
|
@@ -6342,7 +7000,7 @@ function load(path) {
|
|
|
6342
7000
|
return parsed;
|
|
6343
7001
|
return {};
|
|
6344
7002
|
} catch (err) {
|
|
6345
|
-
log.warn(
|
|
7003
|
+
log.warn(TAG28, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
6346
7004
|
return {};
|
|
6347
7005
|
}
|
|
6348
7006
|
}
|
|
@@ -6360,7 +7018,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
6360
7018
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
6361
7019
|
save(path, registry);
|
|
6362
7020
|
} catch (err) {
|
|
6363
|
-
log.warn(
|
|
7021
|
+
log.warn(TAG28, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6364
7022
|
}
|
|
6365
7023
|
}
|
|
6366
7024
|
function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
|
|
@@ -6376,10 +7034,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
6376
7034
|
delete registry[projectId];
|
|
6377
7035
|
save(path, registry);
|
|
6378
7036
|
} catch (err) {
|
|
6379
|
-
log.warn(
|
|
7037
|
+
log.warn(TAG28, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6380
7038
|
}
|
|
6381
7039
|
}
|
|
6382
|
-
var
|
|
7040
|
+
var TAG28 = "port-registry";
|
|
6383
7041
|
var init_port_registry = __esm(() => {
|
|
6384
7042
|
init_log();
|
|
6385
7043
|
});
|
|
@@ -6400,7 +7058,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
6400
7058
|
const { card } = await client.getCard(cardId);
|
|
6401
7059
|
return card;
|
|
6402
7060
|
} catch (err) {
|
|
6403
|
-
log.warn(
|
|
7061
|
+
log.warn(TAG29, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
6404
7062
|
return null;
|
|
6405
7063
|
}
|
|
6406
7064
|
}
|
|
@@ -6410,7 +7068,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
6410
7068
|
return [];
|
|
6411
7069
|
}
|
|
6412
7070
|
const outcomes = [];
|
|
6413
|
-
log.info(
|
|
7071
|
+
log.info(TAG29, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
6414
7072
|
for (const run of active) {
|
|
6415
7073
|
const outcome = {
|
|
6416
7074
|
runId: run.runId,
|
|
@@ -6422,11 +7080,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
6422
7080
|
};
|
|
6423
7081
|
outcomes.push(outcome);
|
|
6424
7082
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
6425
|
-
log.warn(
|
|
7083
|
+
log.warn(TAG29, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
6426
7084
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
6427
7085
|
continue;
|
|
6428
7086
|
}
|
|
6429
|
-
log.info(
|
|
7087
|
+
log.info(TAG29, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
6430
7088
|
await recoverRun(run, store, client, config, outcome);
|
|
6431
7089
|
}
|
|
6432
7090
|
return outcomes;
|
|
@@ -6444,7 +7102,7 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6444
7102
|
} catch (err) {
|
|
6445
7103
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6446
7104
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
6447
|
-
log.warn(
|
|
7105
|
+
log.warn(TAG29, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
6448
7106
|
}
|
|
6449
7107
|
const card = await fetchCardSafely(client, run.cardId);
|
|
6450
7108
|
if (card) {
|
|
@@ -6487,9 +7145,9 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6487
7145
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6488
7146
|
outcome.errors.push(`endRun: ${msg}`);
|
|
6489
7147
|
}
|
|
6490
|
-
log.info(
|
|
7148
|
+
log.info(TAG29, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
6491
7149
|
}
|
|
6492
|
-
var
|
|
7150
|
+
var TAG29 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
6493
7151
|
var init_recovery = __esm(() => {
|
|
6494
7152
|
init_board_helpers();
|
|
6495
7153
|
init_log();
|
|
@@ -6537,7 +7195,7 @@ class Reconciler {
|
|
|
6537
7195
|
clearInterval(this.timer);
|
|
6538
7196
|
this.timer = null;
|
|
6539
7197
|
}
|
|
6540
|
-
log.info(
|
|
7198
|
+
log.info(TAG30, "Heartbeat stopped");
|
|
6541
7199
|
}
|
|
6542
7200
|
async recoverStaleRuns() {
|
|
6543
7201
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -6554,7 +7212,7 @@ class Reconciler {
|
|
|
6554
7212
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
6555
7213
|
continue;
|
|
6556
7214
|
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(
|
|
7215
|
+
log.warn(TAG30, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
6558
7216
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
6559
7217
|
runId: run.runId,
|
|
6560
7218
|
cardId: run.cardId,
|
|
@@ -6581,11 +7239,11 @@ class Reconciler {
|
|
|
6581
7239
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
6582
7240
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
6583
7241
|
continue;
|
|
6584
|
-
log.warn(
|
|
7242
|
+
log.warn(TAG30, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
6585
7243
|
try {
|
|
6586
7244
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6587
7245
|
} catch (err) {
|
|
6588
|
-
log.error(
|
|
7246
|
+
log.error(TAG30, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6589
7247
|
}
|
|
6590
7248
|
}
|
|
6591
7249
|
}
|
|
@@ -6608,11 +7266,11 @@ class Reconciler {
|
|
|
6608
7266
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
6609
7267
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
6610
7268
|
continue;
|
|
6611
|
-
log.warn(
|
|
7269
|
+
log.warn(TAG30, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
6612
7270
|
try {
|
|
6613
7271
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6614
7272
|
} catch (err) {
|
|
6615
|
-
log.error(
|
|
7273
|
+
log.error(TAG30, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6616
7274
|
}
|
|
6617
7275
|
}
|
|
6618
7276
|
}
|
|
@@ -6641,18 +7299,18 @@ class Reconciler {
|
|
|
6641
7299
|
const subtasks = card.subtasks ?? [];
|
|
6642
7300
|
const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
|
|
6643
7301
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
6644
|
-
log.debug(
|
|
7302
|
+
log.debug(TAG30, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
6645
7303
|
continue;
|
|
6646
7304
|
}
|
|
6647
7305
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
6648
|
-
log.debug(
|
|
7306
|
+
log.debug(TAG30, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
6649
7307
|
continue;
|
|
6650
7308
|
}
|
|
6651
7309
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
6652
|
-
log.debug(
|
|
7310
|
+
log.debug(TAG30, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
6653
7311
|
continue;
|
|
6654
7312
|
}
|
|
6655
|
-
log.info(
|
|
7313
|
+
log.info(TAG30, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
6656
7314
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
6657
7315
|
}
|
|
6658
7316
|
}
|
|
@@ -6662,18 +7320,18 @@ class Reconciler {
|
|
|
6662
7320
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
6663
7321
|
for (const knownId of knownCardIds) {
|
|
6664
7322
|
if (!allAgentCardIds.has(knownId)) {
|
|
6665
|
-
log.info(
|
|
7323
|
+
log.info(TAG30, `Missed unassign: ${knownId} — removing`);
|
|
6666
7324
|
await this.pool.removeCard(knownId);
|
|
6667
7325
|
}
|
|
6668
7326
|
}
|
|
6669
7327
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
6670
|
-
log.debug(
|
|
7328
|
+
log.debug(TAG30, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
6671
7329
|
} catch (err) {
|
|
6672
|
-
log.error(
|
|
7330
|
+
log.error(TAG30, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
6673
7331
|
}
|
|
6674
7332
|
}
|
|
6675
7333
|
}
|
|
6676
|
-
var
|
|
7334
|
+
var TAG30 = "reconcile";
|
|
6677
7335
|
var init_reconcile = __esm(() => {
|
|
6678
7336
|
init_board_helpers();
|
|
6679
7337
|
init_log();
|
|
@@ -6711,7 +7369,7 @@ function prettyBanner(config, version) {
|
|
|
6711
7369
|
checks.push({ kind: "ok", message });
|
|
6712
7370
|
},
|
|
6713
7371
|
warn(message) {
|
|
6714
|
-
log.warn(
|
|
7372
|
+
log.warn(TAG31, message);
|
|
6715
7373
|
checks.push({ kind: "warn", message: message.split(`
|
|
6716
7374
|
`, 1)[0] });
|
|
6717
7375
|
},
|
|
@@ -6736,25 +7394,25 @@ function prettyBanner(config, version) {
|
|
|
6736
7394
|
};
|
|
6737
7395
|
}
|
|
6738
7396
|
function jsonBanner(config, version) {
|
|
6739
|
-
log.info(
|
|
6740
|
-
log.info(
|
|
7397
|
+
log.info(TAG31, `Harmony Agent Daemon v${version} starting...`);
|
|
7398
|
+
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
7399
|
if (config.agent.review.enabled) {
|
|
6742
|
-
log.info(
|
|
7400
|
+
log.info(TAG31, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
6743
7401
|
}
|
|
6744
7402
|
let failed = false;
|
|
6745
7403
|
return {
|
|
6746
7404
|
setProjectName(_name) {},
|
|
6747
7405
|
setGitProvider(provider) {
|
|
6748
|
-
log.info(
|
|
7406
|
+
log.info(TAG31, `Git provider: ${provider}`);
|
|
6749
7407
|
},
|
|
6750
7408
|
setHttpPort(port) {
|
|
6751
|
-
log.info(
|
|
7409
|
+
log.info(TAG31, `HTTP server on port ${port}`);
|
|
6752
7410
|
},
|
|
6753
7411
|
check(message) {
|
|
6754
|
-
log.info(
|
|
7412
|
+
log.info(TAG31, message);
|
|
6755
7413
|
},
|
|
6756
7414
|
warn(message) {
|
|
6757
|
-
log.warn(
|
|
7415
|
+
log.warn(TAG31, message);
|
|
6758
7416
|
},
|
|
6759
7417
|
fail() {
|
|
6760
7418
|
failed = true;
|
|
@@ -6762,7 +7420,7 @@ function jsonBanner(config, version) {
|
|
|
6762
7420
|
async ready(message) {
|
|
6763
7421
|
if (failed)
|
|
6764
7422
|
return;
|
|
6765
|
-
log.info(
|
|
7423
|
+
log.info(TAG31, message);
|
|
6766
7424
|
}
|
|
6767
7425
|
};
|
|
6768
7426
|
}
|
|
@@ -6808,6 +7466,7 @@ function configRows(config, projectName, gitProvider, httpPort) {
|
|
|
6808
7466
|
label: "Model",
|
|
6809
7467
|
value: `${modelDesc} · ${poolDesc} · ${flowDesc}`
|
|
6810
7468
|
});
|
|
7469
|
+
rows.push({ label: "Runner", value: runnerDesc(config.agent.runner) });
|
|
6811
7470
|
const tail = [];
|
|
6812
7471
|
if (gitProvider)
|
|
6813
7472
|
tail.push(gitProvider);
|
|
@@ -6825,6 +7484,9 @@ function titleRule(title) {
|
|
|
6825
7484
|
const suffix = "─".repeat(Math.max(3, RULE_WIDTH - prefix.length - title.length - surround.length));
|
|
6826
7485
|
return dim(`${prefix}${title}${surround}${suffix}`);
|
|
6827
7486
|
}
|
|
7487
|
+
function runnerDesc(runner) {
|
|
7488
|
+
return runner === "sdk" ? "sdk (Agent SDK)" : "cli (Claude CLI)";
|
|
7489
|
+
}
|
|
6828
7490
|
function shortenId(id) {
|
|
6829
7491
|
if (id.length <= 8)
|
|
6830
7492
|
return id;
|
|
@@ -6839,7 +7501,7 @@ function cyan(s) {
|
|
|
6839
7501
|
function yellow(s) {
|
|
6840
7502
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
6841
7503
|
}
|
|
6842
|
-
var
|
|
7504
|
+
var TAG31 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
6843
7505
|
var init_startup_banner = __esm(() => {
|
|
6844
7506
|
init_log();
|
|
6845
7507
|
ANSI = {
|
|
@@ -6986,18 +7648,18 @@ class Watcher {
|
|
|
6986
7648
|
}
|
|
6987
7649
|
async start() {
|
|
6988
7650
|
if (!isPretty()) {
|
|
6989
|
-
log.info(
|
|
7651
|
+
log.info(TAG32, "Connecting to Supabase realtime (broadcast)...");
|
|
6990
7652
|
}
|
|
6991
7653
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
6992
7654
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
6993
7655
|
const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
6994
|
-
log.debug(
|
|
7656
|
+
log.debug(TAG32, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
6995
7657
|
this.onCardBroadcast({
|
|
6996
7658
|
event: "card_update",
|
|
6997
7659
|
payload: msg.payload ?? {}
|
|
6998
7660
|
});
|
|
6999
7661
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
7000
|
-
log.debug(
|
|
7662
|
+
log.debug(TAG32, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
7001
7663
|
this.onCardBroadcast({
|
|
7002
7664
|
event: "card_created",
|
|
7003
7665
|
payload: msg.payload ?? {}
|
|
@@ -7007,29 +7669,29 @@ class Watcher {
|
|
|
7007
7669
|
const cardId = payload.card_id;
|
|
7008
7670
|
const command = payload.command;
|
|
7009
7671
|
if (cardId && command) {
|
|
7010
|
-
log.info(
|
|
7672
|
+
log.info(TAG32, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
7011
7673
|
this.onAgentCommand?.({ cardId, command });
|
|
7012
7674
|
}
|
|
7013
7675
|
}).subscribe((status) => {
|
|
7014
7676
|
if (status === "SUBSCRIBED") {
|
|
7015
7677
|
this.connected = true;
|
|
7016
7678
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
7017
|
-
log.info(
|
|
7679
|
+
log.info(TAG32, "Broadcast subscription active");
|
|
7018
7680
|
}
|
|
7019
7681
|
this.maybeResolveReady();
|
|
7020
7682
|
} else if (status === "CHANNEL_ERROR") {
|
|
7021
7683
|
this.connected = false;
|
|
7022
|
-
log.error(
|
|
7684
|
+
log.error(TAG32, "Broadcast channel error — will rely on reconciliation");
|
|
7023
7685
|
} else if (status === "TIMED_OUT") {
|
|
7024
7686
|
this.connected = false;
|
|
7025
|
-
log.warn(
|
|
7687
|
+
log.warn(TAG32, "Broadcast subscription timed out — retrying...");
|
|
7026
7688
|
} else if (status === "CLOSED") {
|
|
7027
7689
|
this.connected = false;
|
|
7028
7690
|
}
|
|
7029
7691
|
});
|
|
7030
7692
|
this.channel = channel;
|
|
7031
7693
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
7032
|
-
log.debug(
|
|
7694
|
+
log.debug(TAG32, "Presence sync");
|
|
7033
7695
|
}).subscribe(async (status) => {
|
|
7034
7696
|
if (status === "SUBSCRIBED") {
|
|
7035
7697
|
await presenceChannel.track({
|
|
@@ -7042,7 +7704,7 @@ class Watcher {
|
|
|
7042
7704
|
agentName: this.identity.agentName
|
|
7043
7705
|
});
|
|
7044
7706
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
7045
|
-
log.info(
|
|
7707
|
+
log.info(TAG32, "Presence tracked on board-presence channel");
|
|
7046
7708
|
}
|
|
7047
7709
|
this.presenceTracked = true;
|
|
7048
7710
|
this.maybeResolveReady();
|
|
@@ -7064,10 +7726,10 @@ class Watcher {
|
|
|
7064
7726
|
this.supabase = null;
|
|
7065
7727
|
}
|
|
7066
7728
|
this.connected = false;
|
|
7067
|
-
log.info(
|
|
7729
|
+
log.info(TAG32, "Broadcast subscription stopped");
|
|
7068
7730
|
}
|
|
7069
7731
|
}
|
|
7070
|
-
var
|
|
7732
|
+
var TAG32 = "watcher";
|
|
7071
7733
|
var init_watcher = __esm(() => {
|
|
7072
7734
|
init_log();
|
|
7073
7735
|
});
|
|
@@ -7154,10 +7816,10 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
7154
7816
|
});
|
|
7155
7817
|
} catch {}
|
|
7156
7818
|
if (result.removed.length > 0) {
|
|
7157
|
-
log.info(
|
|
7819
|
+
log.info(TAG33, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
7158
7820
|
}
|
|
7159
7821
|
if (result.errors.length > 0) {
|
|
7160
|
-
log.warn(
|
|
7822
|
+
log.warn(TAG33, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
7161
7823
|
}
|
|
7162
7824
|
return result;
|
|
7163
7825
|
}
|
|
@@ -7187,7 +7849,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
7187
7849
|
} catch (err) {
|
|
7188
7850
|
const detail = gitErrorDetail(err);
|
|
7189
7851
|
if (isTransientGitNetworkError(detail)) {
|
|
7190
|
-
log.debug(
|
|
7852
|
+
log.debug(TAG33, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
7191
7853
|
return result;
|
|
7192
7854
|
}
|
|
7193
7855
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -7226,7 +7888,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
7226
7888
|
continue;
|
|
7227
7889
|
}
|
|
7228
7890
|
if (clock() > sweepDeadline) {
|
|
7229
|
-
log.debug(
|
|
7891
|
+
log.debug(TAG33, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
7230
7892
|
break;
|
|
7231
7893
|
}
|
|
7232
7894
|
try {
|
|
@@ -7239,17 +7901,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
7239
7901
|
} catch (err) {
|
|
7240
7902
|
const detail = gitErrorDetail(err);
|
|
7241
7903
|
if (isTransientGitNetworkError(detail)) {
|
|
7242
|
-
log.debug(
|
|
7904
|
+
log.debug(TAG33, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
7243
7905
|
break;
|
|
7244
7906
|
}
|
|
7245
7907
|
result.errors.push({ ref, error: detail });
|
|
7246
7908
|
}
|
|
7247
7909
|
}
|
|
7248
7910
|
if (result.removed.length > 0) {
|
|
7249
|
-
log.info(
|
|
7911
|
+
log.info(TAG33, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
7250
7912
|
}
|
|
7251
7913
|
if (result.errors.length > 0) {
|
|
7252
|
-
log.warn(
|
|
7914
|
+
log.warn(TAG33, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
7253
7915
|
}
|
|
7254
7916
|
return result;
|
|
7255
7917
|
}
|
|
@@ -7280,13 +7942,13 @@ class WorktreeGc {
|
|
|
7280
7942
|
try {
|
|
7281
7943
|
runWorktreeGc(this.basePath, this.store);
|
|
7282
7944
|
} catch (err) {
|
|
7283
|
-
log.warn(
|
|
7945
|
+
log.warn(TAG33, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
7284
7946
|
}
|
|
7285
7947
|
if (this.remoteOpts) {
|
|
7286
7948
|
try {
|
|
7287
7949
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
7288
7950
|
} catch (err) {
|
|
7289
|
-
log.warn(
|
|
7951
|
+
log.warn(TAG33, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
7290
7952
|
}
|
|
7291
7953
|
}
|
|
7292
7954
|
}
|
|
@@ -7300,7 +7962,7 @@ function getRepoRoot2() {
|
|
|
7300
7962
|
return null;
|
|
7301
7963
|
}
|
|
7302
7964
|
}
|
|
7303
|
-
var
|
|
7965
|
+
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
7966
|
var init_worktree_gc = __esm(() => {
|
|
7305
7967
|
init_log();
|
|
7306
7968
|
init_worktree();
|
|
@@ -7404,7 +8066,7 @@ async function main() {
|
|
|
7404
8066
|
} catch (err) {
|
|
7405
8067
|
if (err instanceof ConfigValidationError) {
|
|
7406
8068
|
banner.fail();
|
|
7407
|
-
log.error(
|
|
8069
|
+
log.error(TAG34, err.message);
|
|
7408
8070
|
process.exit(1);
|
|
7409
8071
|
}
|
|
7410
8072
|
throw err;
|
|
@@ -7472,6 +8134,7 @@ async function main() {
|
|
|
7472
8134
|
daemonPid: process.pid,
|
|
7473
8135
|
startedAt,
|
|
7474
8136
|
uptimeMs: Date.now() - startedAt,
|
|
8137
|
+
runner: config.agent.runner,
|
|
7475
8138
|
workers: pool.snapshotWorkers().map((w) => ({
|
|
7476
8139
|
...w,
|
|
7477
8140
|
phase: null
|
|
@@ -7513,7 +8176,7 @@ async function main() {
|
|
|
7513
8176
|
if (shuttingDown)
|
|
7514
8177
|
return;
|
|
7515
8178
|
shuttingDown = true;
|
|
7516
|
-
log.info(
|
|
8179
|
+
log.info(TAG34, `Received ${signal}, shutting down gracefully...`);
|
|
7517
8180
|
reconciler.stop();
|
|
7518
8181
|
mergeMonitor?.stop();
|
|
7519
8182
|
worktreeGc.stop();
|
|
@@ -7523,18 +8186,18 @@ async function main() {
|
|
|
7523
8186
|
}
|
|
7524
8187
|
await watcher.stop();
|
|
7525
8188
|
await pool.shutdown();
|
|
7526
|
-
log.info(
|
|
8189
|
+
log.info(TAG34, "Daemon stopped.");
|
|
7527
8190
|
process.exit(exitCode);
|
|
7528
8191
|
};
|
|
7529
8192
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
7530
8193
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
7531
8194
|
process.on("uncaughtException", (err) => {
|
|
7532
|
-
log.error(
|
|
8195
|
+
log.error(TAG34, `Uncaught exception: ${err.message}`);
|
|
7533
8196
|
exitCode = 1;
|
|
7534
8197
|
shutdown("uncaughtException");
|
|
7535
8198
|
});
|
|
7536
8199
|
process.on("unhandledRejection", (reason) => {
|
|
7537
|
-
log.error(
|
|
8200
|
+
log.error(TAG34, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
7538
8201
|
exitCode = 1;
|
|
7539
8202
|
shutdown("unhandledRejection");
|
|
7540
8203
|
});
|
|
@@ -7587,35 +8250,35 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
7587
8250
|
if (assignedAgentId === undefined)
|
|
7588
8251
|
return;
|
|
7589
8252
|
if (assignedAgentId === agentId) {
|
|
7590
|
-
log.info(
|
|
8253
|
+
log.info(TAG34, `Broadcast: card ${cardId} assigned to agent`);
|
|
7591
8254
|
try {
|
|
7592
8255
|
await pool.resetAttemptsForReassign(cardId);
|
|
7593
8256
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
7594
8257
|
} catch (err) {
|
|
7595
|
-
log.error(
|
|
8258
|
+
log.error(TAG34, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
7596
8259
|
}
|
|
7597
8260
|
} else if (pool.isCardKnown(cardId)) {
|
|
7598
|
-
log.info(
|
|
8261
|
+
log.info(TAG34, `Broadcast: card ${cardId} unassigned from agent`);
|
|
7599
8262
|
await pool.removeCard(cardId);
|
|
7600
8263
|
}
|
|
7601
8264
|
}
|
|
7602
8265
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
7603
8266
|
const { card } = await client.getCard(cardId);
|
|
7604
8267
|
if (card.assigned_agent_id !== agentId) {
|
|
7605
|
-
log.debug(
|
|
8268
|
+
log.debug(TAG34, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
7606
8269
|
return;
|
|
7607
8270
|
}
|
|
7608
8271
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
7609
8272
|
const columns = board.columns;
|
|
7610
8273
|
const column = columns.find((c) => c.id === card.column_id);
|
|
7611
8274
|
if (!column) {
|
|
7612
|
-
log.warn(
|
|
8275
|
+
log.warn(TAG34, `Column not found for card ${cardId}`);
|
|
7613
8276
|
return;
|
|
7614
8277
|
}
|
|
7615
8278
|
const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7616
8279
|
const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7617
8280
|
if (!isPickupColumn && !isReviewColumn) {
|
|
7618
|
-
log.info(
|
|
8281
|
+
log.info(TAG34, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
|
|
7619
8282
|
return;
|
|
7620
8283
|
}
|
|
7621
8284
|
const mode = isReviewColumn ? "review" : "implement";
|
|
@@ -7623,16 +8286,16 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
7623
8286
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
7624
8287
|
const subtasks = card.subtasks ?? [];
|
|
7625
8288
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
7626
|
-
log.debug(
|
|
8289
|
+
log.debug(TAG34, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
7627
8290
|
return;
|
|
7628
8291
|
}
|
|
7629
8292
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
7630
|
-
log.info(
|
|
8293
|
+
log.info(TAG34, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
7631
8294
|
return;
|
|
7632
8295
|
}
|
|
7633
8296
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
7634
8297
|
}
|
|
7635
|
-
var
|
|
8298
|
+
var TAG34 = "daemon", PKG_VERSION;
|
|
7636
8299
|
var init_src = __esm(() => {
|
|
7637
8300
|
init_board_helpers();
|
|
7638
8301
|
init_config();
|