bosun 0.41.6 → 0.41.8
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/config/config.mjs +4 -1
- package/infra/monitor.mjs +13 -0
- package/package.json +1 -1
- package/task/task-executor.mjs +80 -9
package/config/config.mjs
CHANGED
|
@@ -1217,7 +1217,10 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1217
1217
|
{
|
|
1218
1218
|
const selPath = selectedRepository?.path || "";
|
|
1219
1219
|
const selHasGit = selPath && existsSync(resolve(selPath, ".git"));
|
|
1220
|
-
repoRoot =
|
|
1220
|
+
repoRoot =
|
|
1221
|
+
explicitRepoRoot ||
|
|
1222
|
+
(selHasGit ? selPath : null) ||
|
|
1223
|
+
getFallbackRepoRoot();
|
|
1221
1224
|
}
|
|
1222
1225
|
|
|
1223
1226
|
if (
|
package/infra/monitor.mjs
CHANGED
|
@@ -15152,10 +15152,23 @@ if (isExecutorDisabled()) {
|
|
|
15152
15152
|
"[monitor] task-executor lifecycle delegation enabled — finalization/recovery handled by workflow replacement",
|
|
15153
15153
|
);
|
|
15154
15154
|
}
|
|
15155
|
+
const workflowRunsDir =
|
|
15156
|
+
config?.configDir &&
|
|
15157
|
+
String(config?.activeWorkspace || process.env.BOSUN_WORKSPACE || "").trim()
|
|
15158
|
+
? resolve(
|
|
15159
|
+
config.configDir,
|
|
15160
|
+
"workspaces",
|
|
15161
|
+
String(config?.activeWorkspace || process.env.BOSUN_WORKSPACE || "").trim(),
|
|
15162
|
+
String(repoSlug || "").split("/").filter(Boolean).pop() || "bosun",
|
|
15163
|
+
".bosun",
|
|
15164
|
+
"workflow-runs",
|
|
15165
|
+
)
|
|
15166
|
+
: null;
|
|
15155
15167
|
const execOpts = {
|
|
15156
15168
|
...internalExecutorConfig,
|
|
15157
15169
|
repoRoot,
|
|
15158
15170
|
repoSlug,
|
|
15171
|
+
workflowRunsDir,
|
|
15159
15172
|
agentPrompts,
|
|
15160
15173
|
workflowOwnsTaskLifecycle: workflowOwnsTaskExecutorLifecycle,
|
|
15161
15174
|
sendTelegram:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.8",
|
|
4
4
|
"description": "Bosun Autonomous Engineering — manages AI agent executors with failover, extremely powerful workflow builder, and a massive amount of included default workflow templates for autonomous engineering, creates PRs via Vibe-Kanban API, and sends Telegram notifications. Supports N executors with weighted distribution, multi-repo projects, and auto-setup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/task/task-executor.mjs
CHANGED
|
@@ -122,6 +122,7 @@ const CLAIM_CONFLICT_COMMENT_COOLDOWN_MS = 30 * 60 * 1000; // 30 minutes
|
|
|
122
122
|
const REPO_AREA_SLOW_MERGE_LATENCY_MS = 4 * 60 * 60 * 1000;
|
|
123
123
|
const REPO_AREA_VERY_SLOW_MERGE_LATENCY_MS = 8 * 60 * 60 * 1000;
|
|
124
124
|
const REPO_AREA_CONTENTION_EVENT_LIMIT = 60;
|
|
125
|
+
const WORKFLOW_ACTIVE_RUNS_INDEX = "_active-runs.json";
|
|
125
126
|
const FATAL_CLAIM_RENEW_ERRORS = new Set([
|
|
126
127
|
"task_claimed_by_different_instance",
|
|
127
128
|
"claim_token_mismatch",
|
|
@@ -2313,6 +2314,7 @@ class TaskExecutor {
|
|
|
2313
2314
|
activeWorkspace: "",
|
|
2314
2315
|
branchRouting: null,
|
|
2315
2316
|
defaultTargetBranch: null,
|
|
2317
|
+
workflowRunsDir: null,
|
|
2316
2318
|
onTaskStarted: null,
|
|
2317
2319
|
onTaskCompleted: null,
|
|
2318
2320
|
onTaskFailed: null,
|
|
@@ -2356,6 +2358,11 @@ class TaskExecutor {
|
|
|
2356
2358
|
merged.branchRouting?.defaultBranch ||
|
|
2357
2359
|
process.env.VK_TARGET_BRANCH ||
|
|
2358
2360
|
"origin/main";
|
|
2361
|
+
this.workflowRunsDir =
|
|
2362
|
+
typeof merged.workflowRunsDir === "string" &&
|
|
2363
|
+
String(merged.workflowRunsDir).trim()
|
|
2364
|
+
? resolve(String(merged.workflowRunsDir))
|
|
2365
|
+
: null;
|
|
2359
2366
|
this.onTaskStarted = merged.onTaskStarted;
|
|
2360
2367
|
this.onTaskCompleted = merged.onTaskCompleted;
|
|
2361
2368
|
this.onTaskFailed = merged.onTaskFailed;
|
|
@@ -3919,6 +3926,9 @@ class TaskExecutor {
|
|
|
3919
3926
|
.map((entry) => String(entry?.taskKey || "").trim())
|
|
3920
3927
|
.filter(Boolean),
|
|
3921
3928
|
);
|
|
3929
|
+
const activeWorkflowTaskIds = this.workflowOwnsTaskLifecycle
|
|
3930
|
+
? this._readActiveWorkflowTaskIds()
|
|
3931
|
+
: new Set();
|
|
3922
3932
|
|
|
3923
3933
|
const available = Math.max(0, this.maxParallel - this._activeSlots.size);
|
|
3924
3934
|
if (available === 0) return;
|
|
@@ -4048,14 +4058,14 @@ class TaskExecutor {
|
|
|
4048
4058
|
}
|
|
4049
4059
|
const isFreshEnough =
|
|
4050
4060
|
ageMs === 0 || ageMs <= INPROGRESS_RECOVERY_MAX_AGE_MS;
|
|
4061
|
+
const hasWorkflowRun = activeWorkflowTaskIds.has(id);
|
|
4051
4062
|
|
|
4052
|
-
// In workflow-owned mode,
|
|
4053
|
-
//
|
|
4054
|
-
//
|
|
4055
|
-
//
|
|
4056
|
-
// in-progress and let stale/unstarted guards above handle true stranding.
|
|
4063
|
+
// In workflow-owned mode, the authoritative liveness signals are the
|
|
4064
|
+
// persisted workflow active-runs index, then any live executor thread,
|
|
4065
|
+
// then the shared-state owner. If none of those exist, the task is
|
|
4066
|
+
// ownerless and must be reset even when still "fresh".
|
|
4057
4067
|
if (this.workflowOwnsTaskLifecycle) {
|
|
4058
|
-
if (hasThread) {
|
|
4068
|
+
if (hasWorkflowRun || hasThread) {
|
|
4059
4069
|
skippedForActiveClaim++;
|
|
4060
4070
|
continue;
|
|
4061
4071
|
}
|
|
@@ -4080,10 +4090,25 @@ class TaskExecutor {
|
|
|
4080
4090
|
resetToTodo++;
|
|
4081
4091
|
continue;
|
|
4082
4092
|
}
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4093
|
+
try {
|
|
4094
|
+
await transitionTaskStatus(id, "todo", {
|
|
4095
|
+
source: "task-executor-recovery-missing-workflow-run",
|
|
4096
|
+
});
|
|
4097
|
+
} catch {
|
|
4098
|
+
/* best effort */
|
|
4086
4099
|
}
|
|
4100
|
+
try {
|
|
4101
|
+
transitionInternalTaskStatus(
|
|
4102
|
+
id,
|
|
4103
|
+
"todo",
|
|
4104
|
+
"task-executor-recovery-missing-workflow-run",
|
|
4105
|
+
);
|
|
4106
|
+
} catch {
|
|
4107
|
+
/* best effort */
|
|
4108
|
+
}
|
|
4109
|
+
this._removeRuntimeSlot(id);
|
|
4110
|
+
resetToTodo++;
|
|
4111
|
+
continue;
|
|
4087
4112
|
}
|
|
4088
4113
|
|
|
4089
4114
|
if (hasThread || isFreshEnough) {
|
|
@@ -4154,6 +4179,52 @@ class TaskExecutor {
|
|
|
4154
4179
|
}
|
|
4155
4180
|
}
|
|
4156
4181
|
|
|
4182
|
+
_readActiveWorkflowTaskIds() {
|
|
4183
|
+
const taskIds = new Set();
|
|
4184
|
+
if (!this.workflowRunsDir) return taskIds;
|
|
4185
|
+
const activeRunsPath = resolve(this.workflowRunsDir, WORKFLOW_ACTIVE_RUNS_INDEX);
|
|
4186
|
+
if (!existsSync(activeRunsPath)) return taskIds;
|
|
4187
|
+
let activeRuns = [];
|
|
4188
|
+
try {
|
|
4189
|
+
const parsed = JSON.parse(readFileSync(activeRunsPath, "utf8"));
|
|
4190
|
+
activeRuns = Array.isArray(parsed)
|
|
4191
|
+
? parsed
|
|
4192
|
+
: Array.isArray(parsed?.runs)
|
|
4193
|
+
? parsed.runs
|
|
4194
|
+
: [];
|
|
4195
|
+
} catch {
|
|
4196
|
+
return taskIds;
|
|
4197
|
+
}
|
|
4198
|
+
for (const entry of activeRuns) {
|
|
4199
|
+
const directTaskId = normalizeTaskIdKey(
|
|
4200
|
+
entry?.taskId || entry?.activeTaskId,
|
|
4201
|
+
);
|
|
4202
|
+
if (directTaskId) {
|
|
4203
|
+
taskIds.add(directTaskId);
|
|
4204
|
+
continue;
|
|
4205
|
+
}
|
|
4206
|
+
const runId = String(entry?.runId || "").trim();
|
|
4207
|
+
if (!runId) continue;
|
|
4208
|
+
const detailPath = resolve(this.workflowRunsDir, `${runId}.json`);
|
|
4209
|
+
if (!existsSync(detailPath)) continue;
|
|
4210
|
+
try {
|
|
4211
|
+
const detail = JSON.parse(readFileSync(detailPath, "utf8"));
|
|
4212
|
+
const detailTaskId = normalizeTaskIdKey(
|
|
4213
|
+
detail?.data?.taskId ||
|
|
4214
|
+
detail?.data?.activeTaskId ||
|
|
4215
|
+
detail?.inputData?.taskId ||
|
|
4216
|
+
detail?.inputData?.activeTaskId,
|
|
4217
|
+
);
|
|
4218
|
+
if (detailTaskId) {
|
|
4219
|
+
taskIds.add(detailTaskId);
|
|
4220
|
+
}
|
|
4221
|
+
} catch {
|
|
4222
|
+
/* best effort */
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
return taskIds;
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4157
4228
|
/**
|
|
4158
4229
|
* Returns the current executor status for monitoring / Telegram.
|
|
4159
4230
|
* @returns {Object}
|