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