@taskless-app/swarm 0.6.19 → 0.6.20
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 +9 -5
- package/dist/overmind.js +294 -14
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2167,7 +2167,7 @@ function tokenFromFile() {
|
|
|
2167
2167
|
return "";
|
|
2168
2168
|
}
|
|
2169
2169
|
}
|
|
2170
|
-
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2170
|
+
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2171
2171
|
var init_config = __esm({
|
|
2172
2172
|
"src/config.ts"() {
|
|
2173
2173
|
"use strict";
|
|
@@ -2216,6 +2216,7 @@ var init_config = __esm({
|
|
|
2216
2216
|
SWARM_PROD_HEARTBEAT_MS = envNum("SWARM_PROD_HEARTBEAT_MS", 15e3);
|
|
2217
2217
|
SWARM_VERSION_CHECK_MS = envNum("SWARM_VERSION_CHECK_MS", 5 * 6e4);
|
|
2218
2218
|
SWARM_SWEEP_INTERVAL_MS = envNum("SWARM_SWEEP_INTERVAL_MS", 10 * 6e4);
|
|
2219
|
+
SWARM_WORKTREE_REAP_INTERVAL_MS = envNum("SWARM_WORKTREE_REAP_INTERVAL_MS", 5 * 6e4);
|
|
2219
2220
|
DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
|
|
2220
2221
|
DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
|
|
2221
2222
|
DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
|
|
@@ -2372,7 +2373,7 @@ var init_package = __esm({
|
|
|
2372
2373
|
"package.json"() {
|
|
2373
2374
|
package_default = {
|
|
2374
2375
|
name: "@taskless-app/swarm",
|
|
2375
|
-
version: "0.6.
|
|
2376
|
+
version: "0.6.20",
|
|
2376
2377
|
type: "module",
|
|
2377
2378
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2378
2379
|
keywords: [
|
|
@@ -2422,7 +2423,7 @@ var init_package = __esm({
|
|
|
2422
2423
|
execa: "^10.0.0",
|
|
2423
2424
|
zod: "3.25.76"
|
|
2424
2425
|
},
|
|
2425
|
-
gitHead: "
|
|
2426
|
+
gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
|
|
2426
2427
|
};
|
|
2427
2428
|
}
|
|
2428
2429
|
});
|
|
@@ -11022,12 +11023,15 @@ async function previousHooksPath(worktree) {
|
|
|
11022
11023
|
}
|
|
11023
11024
|
async function removeWorktree(repo, path) {
|
|
11024
11025
|
const branch = (await $5({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
11026
|
+
await releaseWorktree(repo, path);
|
|
11027
|
+
if (branch) await $5({ reject: false })`git -C ${repo} branch -D ${branch}`;
|
|
11028
|
+
}
|
|
11029
|
+
async function releaseWorktree(repo, path) {
|
|
11025
11030
|
await cleanupWorktreeProcesses(path);
|
|
11026
11031
|
await detachWorktree(repo, path);
|
|
11027
|
-
if (branch) await $5({ reject: false })`git -C ${repo} branch -D ${branch}`;
|
|
11028
11032
|
}
|
|
11029
11033
|
async function worktreeDirty(path) {
|
|
11030
|
-
const st = await $5({ reject: false })`git -C ${path} status --porcelain`;
|
|
11034
|
+
const st = await $5({ reject: false })`git -C ${path} status --porcelain --untracked-files=all`;
|
|
11031
11035
|
if (st.exitCode !== 0) return true;
|
|
11032
11036
|
const status = st.stdout.trim();
|
|
11033
11037
|
if (status) return true;
|
package/dist/overmind.js
CHANGED
|
@@ -2275,6 +2275,7 @@ __export(config_exports, {
|
|
|
2275
2275
|
SWARM_VERIFY: () => SWARM_VERIFY,
|
|
2276
2276
|
SWARM_VERSION_CHECK_MS: () => SWARM_VERSION_CHECK_MS,
|
|
2277
2277
|
SWARM_WATCHDOG_STALL_MS: () => SWARM_WATCHDOG_STALL_MS,
|
|
2278
|
+
SWARM_WORKTREE_REAP_INTERVAL_MS: () => SWARM_WORKTREE_REAP_INTERVAL_MS,
|
|
2278
2279
|
TASKLESS_API: () => TASKLESS_API,
|
|
2279
2280
|
TASKLESS_MCP_PATH: () => TASKLESS_MCP_PATH,
|
|
2280
2281
|
TASKLESS_TASK_URL_BASE: () => TASKLESS_TASK_URL_BASE,
|
|
@@ -2371,7 +2372,7 @@ function reviewCutoffAt() {
|
|
|
2371
2372
|
}
|
|
2372
2373
|
return at;
|
|
2373
2374
|
}
|
|
2374
|
-
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2375
|
+
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2375
2376
|
var init_config = __esm({
|
|
2376
2377
|
"src/config.ts"() {
|
|
2377
2378
|
"use strict";
|
|
@@ -2420,6 +2421,7 @@ var init_config = __esm({
|
|
|
2420
2421
|
SWARM_PROD_HEARTBEAT_MS = envNum("SWARM_PROD_HEARTBEAT_MS", 15e3);
|
|
2421
2422
|
SWARM_VERSION_CHECK_MS = envNum("SWARM_VERSION_CHECK_MS", 5 * 6e4);
|
|
2422
2423
|
SWARM_SWEEP_INTERVAL_MS = envNum("SWARM_SWEEP_INTERVAL_MS", 10 * 6e4);
|
|
2424
|
+
SWARM_WORKTREE_REAP_INTERVAL_MS = envNum("SWARM_WORKTREE_REAP_INTERVAL_MS", 5 * 6e4);
|
|
2423
2425
|
DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
|
|
2424
2426
|
DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
|
|
2425
2427
|
DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
|
|
@@ -3486,12 +3488,18 @@ async function previousHooksPath(worktree) {
|
|
|
3486
3488
|
}
|
|
3487
3489
|
async function removeWorktree(repo, path) {
|
|
3488
3490
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
3491
|
+
await releaseWorktree(repo, path);
|
|
3492
|
+
if (branch) await $2({ reject: false })`git -C ${repo} branch -D ${branch}`;
|
|
3493
|
+
}
|
|
3494
|
+
async function releaseWorktree(repo, path) {
|
|
3489
3495
|
await cleanupWorktreeProcesses(path);
|
|
3490
3496
|
await detachWorktree(repo, path);
|
|
3491
|
-
|
|
3497
|
+
}
|
|
3498
|
+
async function pruneWorktrees(repo) {
|
|
3499
|
+
await $2({ reject: false })`git -C ${repo} worktree prune`;
|
|
3492
3500
|
}
|
|
3493
3501
|
async function worktreeDirty(path) {
|
|
3494
|
-
const st = await $2({ reject: false })`git -C ${path} status --porcelain`;
|
|
3502
|
+
const st = await $2({ reject: false })`git -C ${path} status --porcelain --untracked-files=all`;
|
|
3495
3503
|
if (st.exitCode !== 0) return true;
|
|
3496
3504
|
const status = st.stdout.trim();
|
|
3497
3505
|
if (status) return true;
|
|
@@ -10507,6 +10515,70 @@ function parkReason(attempts, reason) {
|
|
|
10507
10515
|
return `${PRE_START_MARK} ${attempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
|
|
10508
10516
|
}
|
|
10509
10517
|
|
|
10518
|
+
// src/worktreeReap.ts
|
|
10519
|
+
init_src();
|
|
10520
|
+
var DIRTY_WORKTREE_MARK = "worktree \u043D\u0435 \u0443\u0431\u0440\u0430\u043D: \u0432 \u043D\u0451\u043C \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430";
|
|
10521
|
+
var HOLDS_WORKTREE = /* @__PURE__ */ new Set(["running", "blocked", "review", "paused", "storm_dead", "stalled"]);
|
|
10522
|
+
function isHelperRun(run) {
|
|
10523
|
+
return run.reviews_run_id != null || run.verifies_run_id != null || run.resolves_run_id != null;
|
|
10524
|
+
}
|
|
10525
|
+
function prIsDead(st) {
|
|
10526
|
+
return st.merged || st.state.toUpperCase() === "MERGED" || st.state.toUpperCase() === "CLOSED";
|
|
10527
|
+
}
|
|
10528
|
+
function heldByLive(deps, all, run, path) {
|
|
10529
|
+
if (deps.liveWorktrees?.().has(path)) return true;
|
|
10530
|
+
return all.some((r) => r.id !== run.id && r.worktree_path === path && HOLDS_WORKTREE.has(r.status));
|
|
10531
|
+
}
|
|
10532
|
+
async function reapFinishedWorktrees(deps) {
|
|
10533
|
+
const report = { removed: 0, keptDirty: 0 };
|
|
10534
|
+
const prunedRepos = /* @__PURE__ */ new Set();
|
|
10535
|
+
const all = deps.db.listRuns();
|
|
10536
|
+
for (const run of all) {
|
|
10537
|
+
const path = run.worktree_path;
|
|
10538
|
+
if (!path || !isTerminalStatus(run.status)) continue;
|
|
10539
|
+
if (heldByLive(deps, all, run, path)) continue;
|
|
10540
|
+
if (!deps.worktreeExists(path)) {
|
|
10541
|
+
if (run.repo && deps.pruneWorktrees && !prunedRepos.has(run.repo)) {
|
|
10542
|
+
prunedRepos.add(run.repo);
|
|
10543
|
+
await deps.pruneWorktrees(run.repo).catch(() => {
|
|
10544
|
+
});
|
|
10545
|
+
}
|
|
10546
|
+
continue;
|
|
10547
|
+
}
|
|
10548
|
+
if (!isHelperRun(run)) {
|
|
10549
|
+
if (!run.pr_url) continue;
|
|
10550
|
+
const st = await deps.prStatus(run.pr_url).catch(() => null);
|
|
10551
|
+
if (!st || !prIsDead(st)) continue;
|
|
10552
|
+
}
|
|
10553
|
+
if (await keepDirty(deps, run, path)) {
|
|
10554
|
+
report.keptDirty += 1;
|
|
10555
|
+
continue;
|
|
10556
|
+
}
|
|
10557
|
+
await deps.releaseWorktree(run.repo, path).catch(() => {
|
|
10558
|
+
});
|
|
10559
|
+
deps.db.addEvent(run.id, "step", `worktree \u0443\u0431\u0440\u0430\u043D: ${path} (\u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D, ${reapReason(run)}) \u2014 \u0441\u043B\u0430\u0433 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D \u0434\u043B\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430`);
|
|
10560
|
+
report.removed += 1;
|
|
10561
|
+
}
|
|
10562
|
+
return report;
|
|
10563
|
+
}
|
|
10564
|
+
function reapReason(run) {
|
|
10565
|
+
return isHelperRun(run) ? "\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D, \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0441\u043A\u0430\u044F \u043A\u043E\u043F\u0438\u044F \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u0430" : "PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0438\u043B\u0438 \u0437\u0430\u043A\u0440\u044B\u0442";
|
|
10566
|
+
}
|
|
10567
|
+
async function keepDirty(deps, run, path) {
|
|
10568
|
+
const dirty = deps.isDirty ? await deps.isDirty(path).catch(() => true) : true;
|
|
10569
|
+
if (!dirty) return false;
|
|
10570
|
+
const already = deps.db.events(run.id).some((e) => (e.text ?? "").includes(DIRTY_WORKTREE_MARK));
|
|
10571
|
+
if (!already) {
|
|
10572
|
+
deps.db.addEvent(
|
|
10573
|
+
run.id,
|
|
10574
|
+
"anomaly",
|
|
10575
|
+
`${DIRTY_WORKTREE_MARK}. \u041F\u0443\u0442\u044C: ${path}. \u0412\u043D\u0443\u0442\u0440\u0438 \u0435\u0441\u0442\u044C \u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u044B\u0435 \u0438\u043B\u0438 \u043D\u0435\u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u043B\u0438\u0431\u043E \u043A\u043E\u043C\u043C\u0438\u0442\u044B, \u043D\u0435 \u0434\u043E\u0448\u0435\u0434\u0448\u0438\u0435 \u0434\u043E origin, \u2014 \u0441\u043D\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u0430\u043C \u0438 \u0443\u0431\u0435\u0440\u0438 \u0440\u0443\u043A\u0430\u043C\u0438 (\`git -C ${path} status --untracked-files=all\`).`
|
|
10576
|
+
);
|
|
10577
|
+
deps.narrate?.(`${run.task_slug}: worktree \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u043D\u0430 \u0434\u0438\u0441\u043A\u0435 \u2014 \u0432 \u043D\u0451\u043C \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 (${path}).`);
|
|
10578
|
+
}
|
|
10579
|
+
return true;
|
|
10580
|
+
}
|
|
10581
|
+
|
|
10510
10582
|
// src/overmind.ts
|
|
10511
10583
|
init_git();
|
|
10512
10584
|
|
|
@@ -11740,6 +11812,52 @@ import { createHash as createHash3 } from "crypto";
|
|
|
11740
11812
|
|
|
11741
11813
|
// src/anomalies.ts
|
|
11742
11814
|
init_src();
|
|
11815
|
+
|
|
11816
|
+
// src/reviewRescue.ts
|
|
11817
|
+
init_src();
|
|
11818
|
+
var REVIEW_RESCUE_AFTER_MS = Math.max(6e4, Number(process.env.SWARM_REVIEW_RESCUE_MS) || 10 * 6e4);
|
|
11819
|
+
var REVIEW_RESCUE_MARK = "\u0430\u0432\u0442\u043E\u043F\u043E\u0434\u044A\u0451\u043C \u0440\u0435\u0432\u044C\u044E";
|
|
11820
|
+
var AUTHOR_GONE_STATUSES = /* @__PURE__ */ new Set(["paused", "failed", "stalled"]);
|
|
11821
|
+
function isExecutorRun(run) {
|
|
11822
|
+
if (run.reviews_run_id != null || run.verifies_run_id != null || run.resolves_run_id != null) return false;
|
|
11823
|
+
const kind = run.kind ?? "drone";
|
|
11824
|
+
return kind === "drone" || kind === "hero";
|
|
11825
|
+
}
|
|
11826
|
+
function prOpenedAtMs(events) {
|
|
11827
|
+
const opened = events.filter((e) => e.kind === "pr_opened");
|
|
11828
|
+
if (opened.length === 0) return null;
|
|
11829
|
+
const ms = sqliteMs(opened[opened.length - 1].at);
|
|
11830
|
+
return Number.isFinite(ms) ? ms : null;
|
|
11831
|
+
}
|
|
11832
|
+
function reviewlessPrCandidates(db, now, afterMs = REVIEW_RESCUE_AFTER_MS) {
|
|
11833
|
+
const out = [];
|
|
11834
|
+
for (const run of db.listRuns()) {
|
|
11835
|
+
if (!run.pr_url) continue;
|
|
11836
|
+
if (run.status !== "review" && !AUTHOR_GONE_STATUSES.has(run.status)) continue;
|
|
11837
|
+
if (!isExecutorRun(run)) continue;
|
|
11838
|
+
if (run.review_verdict != null) continue;
|
|
11839
|
+
if (run.status === "review") {
|
|
11840
|
+
if (db.reviewerCountFor(run.id) > 0) continue;
|
|
11841
|
+
if (db.activeReviewersFor(run.id).length > 0) continue;
|
|
11842
|
+
}
|
|
11843
|
+
const events = db.events(run.id);
|
|
11844
|
+
const openedAt = prOpenedAtMs(events) ?? sqliteMs(run.updated_at);
|
|
11845
|
+
if (!Number.isFinite(openedAt) || now - openedAt < afterMs) continue;
|
|
11846
|
+
out.push(run);
|
|
11847
|
+
}
|
|
11848
|
+
return out;
|
|
11849
|
+
}
|
|
11850
|
+
function lastReviewBlockReason(db, runId) {
|
|
11851
|
+
const hit = db.events(runId).findLast((e) => (e.text ?? "").includes(`${REVIEW_RESCUE_MARK}:`));
|
|
11852
|
+
if (!hit?.text) return null;
|
|
11853
|
+
const idx = hit.text.indexOf(`${REVIEW_RESCUE_MARK}:`);
|
|
11854
|
+
return hit.text.slice(idx + REVIEW_RESCUE_MARK.length + 1).trim() || null;
|
|
11855
|
+
}
|
|
11856
|
+
function shouldNoteReason(db, runId, reason) {
|
|
11857
|
+
return lastReviewBlockReason(db, runId) !== reason;
|
|
11858
|
+
}
|
|
11859
|
+
|
|
11860
|
+
// src/anomalies.ts
|
|
11743
11861
|
var LEVEL_RANK = { note: 0, feed: 1, decision: 2 };
|
|
11744
11862
|
function plural2(n, forms) {
|
|
11745
11863
|
const mod100 = n % 100;
|
|
@@ -12107,15 +12225,18 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
12107
12225
|
key: `pr_no_reviewer:${run.id}`,
|
|
12108
12226
|
minDurationMs: thresholds.prNoReviewerMs,
|
|
12109
12227
|
group: PR_GROUP,
|
|
12110
|
-
build: () =>
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12228
|
+
build: () => {
|
|
12229
|
+
const why = lastReviewBlockReason(db, run.id);
|
|
12230
|
+
return {
|
|
12231
|
+
runId: run.id,
|
|
12232
|
+
// В ленту: рой действует сам (автоподъём), решения человека тут не требуется —
|
|
12233
|
+
// если и подъём не вышел, причина в теле и есть то, с чем человек идёт разбираться.
|
|
12234
|
+
level: "feed",
|
|
12235
|
+
title: runTitle(run, "PR \u0431\u0435\u0437 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430"),
|
|
12236
|
+
body: `${droneOf(run)}: PR \u043E\u0442\u043A\u0440\u044B\u0442 \u2265${Math.round(thresholds.prNoReviewerMs / 6e4)} \u043C\u0438\u043D, \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0442\u0430\u043A \u0438 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F \u2014 ${why ?? "\u0440\u043E\u0439 \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B \u043F\u0440\u0438\u0447\u0438\u043D\u0443 (\u0430\u0432\u0442\u043E\u043F\u043E\u0434\u044A\u0451\u043C \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0434\u043E\u0448\u0451\u043B \u0434\u043E \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430)"} \u2014 ${run.pr_url}.`,
|
|
12237
|
+
...hrefOf(run.task_url)
|
|
12238
|
+
};
|
|
12239
|
+
}
|
|
12119
12240
|
});
|
|
12120
12241
|
} else if (run.review_verdict === "changes" && db.activeReviewersFor(run.id).length === 0) {
|
|
12121
12242
|
candidates.push({
|
|
@@ -13191,7 +13312,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
13191
13312
|
// package.json
|
|
13192
13313
|
var package_default = {
|
|
13193
13314
|
name: "@taskless-app/swarm",
|
|
13194
|
-
version: "0.6.
|
|
13315
|
+
version: "0.6.20",
|
|
13195
13316
|
type: "module",
|
|
13196
13317
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
13197
13318
|
keywords: [
|
|
@@ -13241,7 +13362,7 @@ var package_default = {
|
|
|
13241
13362
|
execa: "^10.0.0",
|
|
13242
13363
|
zod: "3.25.76"
|
|
13243
13364
|
},
|
|
13244
|
-
gitHead: "
|
|
13365
|
+
gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
|
|
13245
13366
|
};
|
|
13246
13367
|
|
|
13247
13368
|
// src/version.ts
|
|
@@ -16091,6 +16212,92 @@ async function closeMergedTaskRuns(deps) {
|
|
|
16091
16212
|
}
|
|
16092
16213
|
}
|
|
16093
16214
|
}
|
|
16215
|
+
function relinkUnboundReviewers(deps) {
|
|
16216
|
+
const all = deps.db.listRuns();
|
|
16217
|
+
const unbound = all.filter((r) => r.kind === "reviewer" && r.reviews_run_id == null && r.pr_url && REVIEWER_ALIVE_STATUSES.has(r.status));
|
|
16218
|
+
if (unbound.length === 0) return;
|
|
16219
|
+
for (const rv of unbound) {
|
|
16220
|
+
const candidates = all.filter(
|
|
16221
|
+
(r) => r.id !== rv.id && r.pr_url === rv.pr_url && (r.kind ?? "drone") !== "reviewer" && (r.kind ?? "drone") !== "verifier" && r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null
|
|
16222
|
+
);
|
|
16223
|
+
if (candidates.length !== 1) {
|
|
16224
|
+
const why = candidates.length === 0 ? `\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043F\u043E \u0441\u0441\u044B\u043B\u043A\u0435 ${rv.pr_url} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u043D\u0435\u043A\u0443\u0434\u0430` : `\u043F\u043E \u0441\u0441\u044B\u043B\u043A\u0435 ${rv.pr_url} \u043D\u0430\u0439\u0434\u0435\u043D\u043E ${candidates.length} \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u2014 \u043A\u0430\u043A\u043E\u0439 \u0438\u0437 \u043D\u0438\u0445 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439, \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F`;
|
|
16225
|
+
if (!deps.db.events(rv.id).some((e) => (e.text ?? "").includes(why))) deps.db.addEvent(rv.id, "step", why);
|
|
16226
|
+
continue;
|
|
16227
|
+
}
|
|
16228
|
+
const parent = candidates[0];
|
|
16229
|
+
deps.db.updateRun(rv.id, { reviews_run_id: parent.id, review_cycle: parent.review_cycle });
|
|
16230
|
+
deps.db.addEvent(rv.id, "step", `\u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u043C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u043F\u043E \u0441\u0441\u044B\u043B\u043A\u0435 PR: #${parent.id} (${parent.task_slug}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u043A\u0443\u0434\u0430 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C (TASWM-375)`);
|
|
16231
|
+
deps.db.addEvent(parent.id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${rv.name} (#${rv.id}) \u043F\u043E\u0434\u043D\u044F\u0442 \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u2014 \u0441\u0432\u044F\u0437\u0430\u043B \u0435\u0433\u043E \u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u043C \u043F\u043E PR-\u0441\u0441\u044B\u043B\u043A\u0435, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043B\u044F\u0436\u0435\u0442 \u0441\u044E\u0434\u0430`);
|
|
16232
|
+
narrate(deps, `${droneLabel(rv)} \u0440\u0430\u0431\u043E\u0442\u0430\u043B \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u043A \u043F\u0440\u043E\u0433\u043E\u043D\u0443 \u2014 \u0441\u0432\u044F\u0437\u0430\u043B \u043F\u043E PR-\u0441\u0441\u044B\u043B\u043A\u0435, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0442\u0435\u043F\u0435\u0440\u044C \u043B\u044F\u0436\u0435\u0442 \u0432 ${parent.task_slug}, \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043A\u043E\u043C\u043C\u0435\u043D\u0442 GitHub.`);
|
|
16233
|
+
}
|
|
16234
|
+
}
|
|
16235
|
+
function deliberateReviewStop(db, run) {
|
|
16236
|
+
const texts = db.events(run.id).map((e) => e.text ?? "");
|
|
16237
|
+
const has = (mark) => texts.some((t) => t.includes(mark));
|
|
16238
|
+
if (has(SELF_REFERENCE_MARK)) {
|
|
16239
|
+
return `PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 (${DAEMON_CODE_PATHS.join(", ")}) \u2014 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E (TASWM-270): \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0438\u0439 \u0434\u0435\u043C\u043E\u043D \u043A\u0440\u0443\u0442\u0438\u0442\u0441\u044F \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u043C \u043A\u043E\u0434\u0435 \u0438 \u0441\u0432\u043E\u044E \u0436\u0435 \u043F\u0440\u0430\u0432\u043A\u0443 \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
16240
|
+
}
|
|
16241
|
+
if (has(REVIEW_ESCALATION_MARK)) return `\u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B (${REVIEW_SPAWN_LIMIT}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
16242
|
+
if (has(REVIEW_CYCLE_ESCALATION_MARK)) return `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (${REVIEW_CYCLE_LIMIT}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
16243
|
+
if (has(reviewBudgetMark(run.review_cycle ?? 0))) return "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
|
|
16244
|
+
if (has(reviewUnrecoverableMark(run.branch || run.task_slug))) return "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
|
|
16245
|
+
return null;
|
|
16246
|
+
}
|
|
16247
|
+
function noteReviewBlocked(deps, run, reason) {
|
|
16248
|
+
if (!shouldNoteReason(deps.db, run.id, reason)) return;
|
|
16249
|
+
deps.db.addEvent(run.id, "step", `${REVIEW_RESCUE_MARK}: ${reason}`);
|
|
16250
|
+
}
|
|
16251
|
+
async function rescueReviewlessPrs(deps, stormActive, now) {
|
|
16252
|
+
for (const run of reviewlessPrCandidates(deps.db, now)) {
|
|
16253
|
+
try {
|
|
16254
|
+
const stop = deliberateReviewStop(deps.db, run);
|
|
16255
|
+
if (stop) {
|
|
16256
|
+
noteReviewBlocked(deps, run, stop);
|
|
16257
|
+
continue;
|
|
16258
|
+
}
|
|
16259
|
+
const authorGone = AUTHOR_GONE_STATUSES.has(run.status) && (run.status !== "paused" || NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r)));
|
|
16260
|
+
if (authorGone) {
|
|
16261
|
+
const st = await deps.prStatus(run.pr_url).catch(() => null);
|
|
16262
|
+
const liveness = prLiveness(st);
|
|
16263
|
+
if (liveness !== "open") {
|
|
16264
|
+
noteReviewBlocked(deps, run, liveness === "unknown" ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043F\u0440\u043E PR \u2014 \u043D\u0435 \u0437\u043D\u0430\u044E, \u0436\u0438\u0432 \u043B\u0438 \u043E\u043D; \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" : `PR ${liveness === "merged" ? "\u0441\u043C\u0435\u0440\u0436\u0435\u043D" : "\u0437\u0430\u043A\u0440\u044B\u0442"} \u2014 \u0440\u0435\u0432\u044C\u044E\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
|
|
16265
|
+
continue;
|
|
16266
|
+
}
|
|
16267
|
+
if (st.isDraft) {
|
|
16268
|
+
noteReviewBlocked(deps, run, "PR \u2014 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A: \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0438 \u043D\u0435 \u0441\u043C\u043E\u0442\u0440\u0438\u0442 (\u0441\u043D\u0438\u043C\u0438 draft)");
|
|
16269
|
+
continue;
|
|
16270
|
+
}
|
|
16271
|
+
const wasLabel = STATUS_META[run.status]?.label ?? run.status;
|
|
16272
|
+
deps.db.updateRun(run.id, { status: "review" });
|
|
16273
|
+
deps.db.addEvent(
|
|
16274
|
+
run.id,
|
|
16275
|
+
"step",
|
|
16276
|
+
`${REVIEW_RESCUE_MARK}: \u0430\u0432\u0442\u043E\u0440 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 (${wasLabel}), \u0430 PR \u043E\u0442\u043A\u0440\u044B\u0442 \u0438 \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0438\u0439 \u043D\u0443\u0436\u0435\u043D \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0441\u0443\u0434\u044C\u0431\u044B \u0430\u0432\u0442\u043E\u0440\u0430`
|
|
16277
|
+
);
|
|
16278
|
+
narrate(deps, `PR ${droneLabel(run)} \u0436\u0438\u0432, \u0430 \u0435\u0433\u043E \u0430\u0432\u0442\u043E\u0440 \u2014 \u043D\u0435\u0442 (${wasLabel}): \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043D\u0443\u0436\u0435\u043D.`);
|
|
16279
|
+
continue;
|
|
16280
|
+
}
|
|
16281
|
+
if (run.status !== "review") continue;
|
|
16282
|
+
noteReviewBlocked(deps, run, reviewGateReason(deps, run, stormActive));
|
|
16283
|
+
} catch (e) {
|
|
16284
|
+
console.error(`[overmind] \u0430\u0432\u0442\u043E\u043F\u043E\u0434\u044A\u0451\u043C \u0440\u0435\u0432\u044C\u044E \u0434\u043B\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id} (${run.task_slug}) \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
16285
|
+
}
|
|
16286
|
+
}
|
|
16287
|
+
}
|
|
16288
|
+
function reviewGateReason(deps, run, stormActive) {
|
|
16289
|
+
if (!deps.spawnReviewer) return "\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 (SWARM_AUTO_REVIEW=0)";
|
|
16290
|
+
if (stormActive) return "\u0448\u0442\u043E\u0440\u043C Anthropic \u2014 \u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u0440\u043E\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442, \u0440\u0435\u0432\u044C\u044E \u0436\u0434\u0451\u0442 \u043E\u0442\u0431\u043E\u044F";
|
|
16291
|
+
if (deps.reviewCutoffAt && run.updated_at < deps.reviewCutoffAt) {
|
|
16292
|
+
return `\u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0442\u0430\u0440\u0448\u0435 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E (cutoff ${deps.reviewCutoffAt}) \u2014 \u0440\u043E\u0439 \u0435\u0433\u043E \u043D\u0435 \u0431\u0435\u0440\u0451\u0442, \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0440\u0443\u043A\u0430\u043C\u0438`;
|
|
16293
|
+
}
|
|
16294
|
+
if (deps.resourcesOk && !deps.resourcesOk()) return "\u043C\u0430\u0448\u0438\u043D\u0430 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0436\u0434\u0451\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u043E\u0432";
|
|
16295
|
+
if (deps.reviewCap !== void 0) {
|
|
16296
|
+
const busy = countActiveReviewers(deps.db);
|
|
16297
|
+
if (busy >= deps.reviewCap) return `\u0437\u0430\u043D\u044F\u0442 \u0441\u043B\u043E\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430: \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 ${busy} \u043F\u0440\u0438 SWARM_REVIEW_CAP=${deps.reviewCap} \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F, \u043A\u0430\u043A \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F`;
|
|
16298
|
+
}
|
|
16299
|
+
return "\u0432\u0441\u0435 \u0433\u0435\u0439\u0442\u044B \u043E\u0442\u043A\u0440\u044B\u0442\u044B, \u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u043E\u0448\u0438\u0431\u043A\u0443 \u0441\u043F\u0430\u0432\u043D\u0430";
|
|
16300
|
+
}
|
|
16094
16301
|
async function closeOrphanedReviewers(deps) {
|
|
16095
16302
|
for (const rv of deps.db.listRuns()) {
|
|
16096
16303
|
const checkedId = rv.kind === "reviewer" ? rv.reviews_run_id : rv.kind === "verifier" ? rv.verifies_run_id : null;
|
|
@@ -17166,9 +17373,42 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
17166
17373
|
return "skipped";
|
|
17167
17374
|
}
|
|
17168
17375
|
if (e instanceof AreaBusyError) return deferSpawn(deps, run, e.message);
|
|
17376
|
+
if (e instanceof WorktreeBusyError && await healBusyWorktree(deps, run, e)) {
|
|
17377
|
+
try {
|
|
17378
|
+
await deps.spawn(run);
|
|
17379
|
+
if (deps.spawnDefer) clearDefer(deps.spawnDefer, `run:${run.id}`);
|
|
17380
|
+
narrate(deps, `\u0411\u0435\u0440\u0443 ${run.task_slug} \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u0441\u043F\u0430\u0432\u043D\u044E ${droneLabel(run)} (\u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0437\u0430 \u043C\u0451\u0440\u0442\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439).`);
|
|
17381
|
+
return "spawned";
|
|
17382
|
+
} catch (again) {
|
|
17383
|
+
return await failBeforeStart(deps, run, String(again));
|
|
17384
|
+
}
|
|
17385
|
+
}
|
|
17169
17386
|
return await failBeforeStart(deps, run, String(e));
|
|
17170
17387
|
}
|
|
17171
17388
|
}
|
|
17389
|
+
async function healBusyWorktree(deps, run, e) {
|
|
17390
|
+
const path = e.worktreePath;
|
|
17391
|
+
if (!deps.releaseWorktree) return false;
|
|
17392
|
+
if (deps.liveWorktrees?.().has(path)) return false;
|
|
17393
|
+
const holder = deps.db.listRuns().find((r) => r.id !== run.id && r.worktree_path === path && !isTerminalStatus(r.status));
|
|
17394
|
+
if (holder && (holder.pid == null || deps.isAlive(holder.pid))) {
|
|
17395
|
+
return false;
|
|
17396
|
+
}
|
|
17397
|
+
if (deps.isDirty && await deps.isDirty(path).catch(() => true)) {
|
|
17398
|
+
deps.db.addEvent(
|
|
17399
|
+
run.id,
|
|
17400
|
+
"anomaly",
|
|
17401
|
+
`${DIRTY_WORKTREE_MARK}. \u041F\u0443\u0442\u044C: ${path}. \u0416\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0437\u0430 \u043D\u0438\u043C \u043D\u0435\u0442, \u043D\u043E \u0432\u043D\u0443\u0442\u0440\u0438 \u0435\u0441\u0442\u044C \u043D\u0435\u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u043F\u0440\u0438\u0431\u0438\u0440\u0430\u0442\u044C \u043D\u0435 \u0441\u0442\u0430\u043B, \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u0430\u043C: \`git -C ${path} status --untracked-files=all\`.`
|
|
17402
|
+
);
|
|
17403
|
+
narrate(deps, `${run.task_slug}: worktree \u0437\u0430\u043D\u044F\u0442 \u043C\u0451\u0440\u0442\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439, \u043D\u043E \u0432 \u043D\u0451\u043C \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`);
|
|
17404
|
+
return false;
|
|
17405
|
+
}
|
|
17406
|
+
await deps.releaseWorktree(run.repo, path).catch(() => {
|
|
17407
|
+
});
|
|
17408
|
+
deps.db.addEvent(run.id, "step", `worktree ${path} \u0434\u0435\u0440\u0436\u0430\u043B\u0430 \u043C\u0451\u0440\u0442\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u2014 \u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044E \u0441\u043F\u0430\u0432\u043D (TASWM-476)`);
|
|
17409
|
+
if (holder) deps.db.addEvent(holder.id, "step", `worktree ${path} \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D: \u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0437\u0430 \u043D\u0438\u043C \u043D\u0435\u0442, \u0441\u043B\u0430\u0433 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id}`);
|
|
17410
|
+
return true;
|
|
17411
|
+
}
|
|
17172
17412
|
async function failBeforeStart(deps, run, reason) {
|
|
17173
17413
|
deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
|
|
17174
17414
|
deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
|
|
@@ -17733,6 +17973,18 @@ async function tickOnce(deps) {
|
|
|
17733
17973
|
}
|
|
17734
17974
|
}
|
|
17735
17975
|
}
|
|
17976
|
+
if (deps.reapWorktrees) {
|
|
17977
|
+
const rw = deps.reapWorktrees;
|
|
17978
|
+
const sinceLast = rw.lastAt == null ? Infinity : now - rw.lastAt;
|
|
17979
|
+
if (sinceLast >= rw.intervalMs) {
|
|
17980
|
+
rw.lastAt = now;
|
|
17981
|
+
try {
|
|
17982
|
+
await rw.run();
|
|
17983
|
+
} catch (e) {
|
|
17984
|
+
console.error("[overmind] \u0443\u0431\u043E\u0440\u043A\u0430 worktree \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C:", e);
|
|
17985
|
+
}
|
|
17986
|
+
}
|
|
17987
|
+
}
|
|
17736
17988
|
if (deps.logRotate) {
|
|
17737
17989
|
const lr = deps.logRotate;
|
|
17738
17990
|
const sinceLast = lr.lastAt == null ? Infinity : now - lr.lastAt;
|
|
@@ -17969,6 +18221,8 @@ async function tickOnce(deps) {
|
|
|
17969
18221
|
}
|
|
17970
18222
|
}
|
|
17971
18223
|
await closeMergedTaskRuns(deps);
|
|
18224
|
+
await rescueReviewlessPrs(deps, stormActive, now);
|
|
18225
|
+
relinkUnboundReviewers(deps);
|
|
17972
18226
|
await closeOrphanedReviewers(deps);
|
|
17973
18227
|
await spawnVerifiersForOpenPrs(deps, stormActive);
|
|
17974
18228
|
refreshReviewPhases(deps);
|
|
@@ -19362,6 +19616,32 @@ async function runDaemon(opts) {
|
|
|
19362
19616
|
intervalMs: SWARM_SWEEP_INTERVAL_MS,
|
|
19363
19617
|
lastAt: null
|
|
19364
19618
|
},
|
|
19619
|
+
// Жнец worktree (TASWM-476): убирает копии терминальных прогонов, чьи PR смержены/закрыты.
|
|
19620
|
+
// Живость каталога определяем ПО ПРОЦЕССАМ (listDroneProcesses — тот же снимок, по которому
|
|
19621
|
+
// идёт реконсайл рантайма), а не только по статусу в зеркале: статус мог не успеть
|
|
19622
|
+
// обновиться, а сносить каталог из-под работающего дрона нельзя ни при каком статусе.
|
|
19623
|
+
reapWorktrees: {
|
|
19624
|
+
run: async () => {
|
|
19625
|
+
const report = await reapFinishedWorktrees({
|
|
19626
|
+
db,
|
|
19627
|
+
prStatus: (url) => prStatus(url),
|
|
19628
|
+
isDirty: (p) => worktreeDirty(p),
|
|
19629
|
+
releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
|
|
19630
|
+
pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
|
|
19631
|
+
worktreeExists: existsSync17,
|
|
19632
|
+
liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
|
|
19633
|
+
narrate: narrator.narrate
|
|
19634
|
+
});
|
|
19635
|
+
if (report.removed || report.keptDirty) {
|
|
19636
|
+
console.log(`[overmind] \u0443\u0431\u043E\u0440\u043A\u0430 worktree: \u0441\u043D\u0435\u0441\u0435\u043D\u043E ${report.removed}, \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E \u0441 \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u043E\u0439 ${report.keptDirty}`);
|
|
19637
|
+
}
|
|
19638
|
+
},
|
|
19639
|
+
intervalMs: SWARM_WORKTREE_REAP_INTERVAL_MS,
|
|
19640
|
+
lastAt: null
|
|
19641
|
+
},
|
|
19642
|
+
// Самолечение залипшего слага (TASWM-476): те же два кирпича, что у жнеца выше.
|
|
19643
|
+
releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
|
|
19644
|
+
liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
|
|
19365
19645
|
isDirty: (p) => worktreeDirty(p),
|
|
19366
19646
|
// git-активность как liveness (TASWM-67): последний коммит worktree — реальный git-вызов.
|
|
19367
19647
|
lastCommitMs: (p) => worktreeLastCommitMs(p),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"execa": "^10.0.0",
|
|
39
39
|
"zod": "3.25.76"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "2d05a2e2b7c197ab916f7ff756e3a829b138df16",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
|
|
44
44
|
"swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",
|