@taskless-app/swarm 0.6.18 → 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 +201 -16
- package/dist/overmind.js +558 -57
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -311,8 +311,8 @@ function computeReviewPhase(input) {
|
|
|
311
311
|
}
|
|
312
312
|
if (ciRed) {
|
|
313
313
|
const jobs = (input.ciFailing ?? []).filter((j) => j && j !== "?");
|
|
314
|
-
const
|
|
315
|
-
return { kind: "ci_fixing", label:
|
|
314
|
+
const label3 = jobs.length ? `${REVIEW_PHASE_TEXT.ci_fixing}: ${jobs.join(", ")}` : REVIEW_PHASE_TEXT.ci_fixing;
|
|
315
|
+
return { kind: "ci_fixing", label: label3, ready: false };
|
|
316
316
|
}
|
|
317
317
|
if (reviewVerdict === "changes") {
|
|
318
318
|
return { kind: "changes", label: REVIEW_PHASE_TEXT.changes, ready: false };
|
|
@@ -327,8 +327,8 @@ function computeReviewPhase(input) {
|
|
|
327
327
|
if (p && p.total > 0 && p.pass >= p.total) {
|
|
328
328
|
return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
|
|
329
329
|
}
|
|
330
|
-
const
|
|
331
|
-
return { kind: "checks", label, ready: false };
|
|
330
|
+
const label2 = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
|
|
331
|
+
return { kind: "checks", label: label2, ready: false };
|
|
332
332
|
}
|
|
333
333
|
var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
|
|
334
334
|
var init_reviewPhase = __esm({
|
|
@@ -439,6 +439,92 @@ var init_mergeGate = __esm({
|
|
|
439
439
|
}
|
|
440
440
|
});
|
|
441
441
|
|
|
442
|
+
// ../swarm-core/src/mergeQueue.ts
|
|
443
|
+
function orderMergeQueue(ready) {
|
|
444
|
+
return [...ready].sort((a, b) => a.prNumber - b.prNumber || a.runId - b.runId);
|
|
445
|
+
}
|
|
446
|
+
function label(e) {
|
|
447
|
+
return e.slug ? `#${e.prNumber} (${e.slug})` : `#${e.prNumber}`;
|
|
448
|
+
}
|
|
449
|
+
function computeMergeQueue(input) {
|
|
450
|
+
const queue = orderMergeQueue(input.ready);
|
|
451
|
+
const size = queue.length;
|
|
452
|
+
const idx = queue.findIndex((e) => e.runId === input.entry.runId && e.prNumber === input.entry.prNumber);
|
|
453
|
+
const position = idx + 1;
|
|
454
|
+
const head = queue[0];
|
|
455
|
+
if (head && idx > 0) {
|
|
456
|
+
return {
|
|
457
|
+
allowed: false,
|
|
458
|
+
code: "queue_wait",
|
|
459
|
+
reason: `\u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 PR \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443: \u0441\u0435\u0439\u0447\u0430\u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u044C PR ${label(head)}, \u044D\u0442\u043E\u0442 \u2014 ${position}-\u0439 \u0438\u0437 ${size}. \u041C\u0435\u0440\u0436\u0438\u0442\u044C \u0435\u0433\u043E \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u043B\u044C\u0437\u044F \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C, \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0434 \u043D\u0438\u043C \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F (TASWM-472)`,
|
|
460
|
+
position,
|
|
461
|
+
size
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
if (input.behindBy == null) {
|
|
465
|
+
return {
|
|
466
|
+
allowed: false,
|
|
467
|
+
code: "base_unknown",
|
|
468
|
+
reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0437\u043D\u0430\u0442\u044C, \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043B\u0438 \u0432\u0435\u0442\u043A\u0430 PR \u0442\u0435\u043A\u0443\u0449\u0438\u0439 main \u2014 \u0430 \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0437\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0435 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E \u043F\u0440\u043E main \u043F\u043E\u0441\u043B\u0435 \u0432\u043B\u0438\u0432\u0430\u043D\u0438\u044F. \u042D\u0442\u043E \u0442\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u043E (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B): \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443 (TASWM-472)",
|
|
469
|
+
position,
|
|
470
|
+
size
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
if (input.behindBy > 0) {
|
|
474
|
+
return {
|
|
475
|
+
allowed: false,
|
|
476
|
+
code: "base_stale",
|
|
477
|
+
reason: `\u0431\u0430\u0437\u0430 \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u043D\u0430 ${input.behindBy} \u043A\u043E\u043C\u043C\u0438\u0442(\u043E\u0432) \u0441 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u2014 \u0437\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u0439 \u0431\u0430\u0437\u0435 \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E main \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C. \u0412\u043B\u0435\u0439 main \u0432 \u0432\u0435\u0442\u043A\u0443 PR \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 (TASWM-472)`,
|
|
478
|
+
position,
|
|
479
|
+
size
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (input.checks === null) {
|
|
483
|
+
return {
|
|
484
|
+
allowed: false,
|
|
485
|
+
code: "checks_unknown",
|
|
486
|
+
reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 PR \u2014 \u0430 \u043D\u0430 \u0441\u0432\u0435\u0436\u0435\u0439 \u0431\u0430\u0437\u0435 \u0438\u043C\u0435\u043D\u043D\u043E \u043E\u043D\u0438 \u0438 \u043E\u0442\u0432\u0435\u0447\u0430\u044E\u0442, \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043B\u0438 main \u0437\u0435\u043B\u0451\u043D\u044B\u043C. \u042D\u0442\u043E \u0442\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u043E (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B): \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443 (TASWM-472)",
|
|
487
|
+
position,
|
|
488
|
+
size
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (input.checks === "fail") {
|
|
492
|
+
return {
|
|
493
|
+
allowed: false,
|
|
494
|
+
code: "checks_red",
|
|
495
|
+
reason: "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u041E\u0411\u041D\u041E\u0412\u041B\u0401\u041D\u041D\u041E\u0419 \u0431\u0430\u0437\u0435 \u043A\u0440\u0430\u0441\u043D\u044B\u0435 \u2014 \u044D\u0442\u043E\u0442 PR \u043B\u043E\u043C\u0430\u0435\u0442 main \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u043C \u0431\u0430\u0437\u044B. \u041C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F: \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430 (TASWM-472)",
|
|
496
|
+
position,
|
|
497
|
+
size
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (input.checks === "pending") {
|
|
501
|
+
return {
|
|
502
|
+
allowed: false,
|
|
503
|
+
code: "checks_pending",
|
|
504
|
+
reason: "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 \u0435\u0449\u0451 \u0438\u0434\u0443\u0442 \u2014 \u0438\u0445 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0438 \u0435\u0441\u0442\u044C \u043E\u0442\u0432\u0435\u0442 \xAB\u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u043B\u0438 main \u0437\u0435\u043B\u0451\u043D\u044B\u043C\xBB. \u0414\u043E\u0436\u0434\u0438\u0441\u044C (TASWM-472)",
|
|
505
|
+
position,
|
|
506
|
+
size
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
allowed: true,
|
|
511
|
+
code: "queue_head",
|
|
512
|
+
reason: "\u0433\u043E\u043B\u043E\u0432\u0430 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430, \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0441\u0432\u0435\u0436\u0430\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043D\u0435\u0439 \u043D\u0435 \u043A\u0440\u0430\u0441\u043D\u044B\u0435 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E",
|
|
513
|
+
position,
|
|
514
|
+
size
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function computeMergeDecision(gate, queue) {
|
|
518
|
+
if (!gate.allowed || gate.code === "unknown_pr" || !queue) return { ...gate, queue: queue ?? null };
|
|
519
|
+
if (!queue.allowed) return { allowed: false, code: queue.code, reason: queue.reason, queue };
|
|
520
|
+
return { ...gate, reason: `${gate.reason}; ${queue.reason}`, queue };
|
|
521
|
+
}
|
|
522
|
+
var init_mergeQueue = __esm({
|
|
523
|
+
"../swarm-core/src/mergeQueue.ts"() {
|
|
524
|
+
"use strict";
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
442
528
|
// ../swarm-core/src/modelPolicy.ts
|
|
443
529
|
function findProvider(id) {
|
|
444
530
|
const key = typeof id === "string" ? id.trim().toLowerCase() : "";
|
|
@@ -1545,6 +1631,7 @@ var init_src = __esm({
|
|
|
1545
1631
|
init_timeline();
|
|
1546
1632
|
init_reviewPhase();
|
|
1547
1633
|
init_mergeGate();
|
|
1634
|
+
init_mergeQueue();
|
|
1548
1635
|
init_verifyVerdict();
|
|
1549
1636
|
init_modelPolicy();
|
|
1550
1637
|
init_status();
|
|
@@ -2161,6 +2248,7 @@ __export(config_exports, {
|
|
|
2161
2248
|
SWARM_MAX_FD_LOAD_FACTOR: () => SWARM_MAX_FD_LOAD_FACTOR,
|
|
2162
2249
|
SWARM_MAX_IN_FLIGHT: () => SWARM_MAX_IN_FLIGHT,
|
|
2163
2250
|
SWARM_MAX_LOAD_FACTOR: () => SWARM_MAX_LOAD_FACTOR,
|
|
2251
|
+
SWARM_MERGE_QUEUE: () => SWARM_MERGE_QUEUE,
|
|
2164
2252
|
SWARM_MIN_FREE_GB: () => SWARM_MIN_FREE_GB,
|
|
2165
2253
|
SWARM_OUTBOX: () => SWARM_OUTBOX,
|
|
2166
2254
|
SWARM_PROD_HEARTBEAT_MS: () => SWARM_PROD_HEARTBEAT_MS,
|
|
@@ -2187,6 +2275,7 @@ __export(config_exports, {
|
|
|
2187
2275
|
SWARM_VERIFY: () => SWARM_VERIFY,
|
|
2188
2276
|
SWARM_VERSION_CHECK_MS: () => SWARM_VERSION_CHECK_MS,
|
|
2189
2277
|
SWARM_WATCHDOG_STALL_MS: () => SWARM_WATCHDOG_STALL_MS,
|
|
2278
|
+
SWARM_WORKTREE_REAP_INTERVAL_MS: () => SWARM_WORKTREE_REAP_INTERVAL_MS,
|
|
2190
2279
|
TASKLESS_API: () => TASKLESS_API,
|
|
2191
2280
|
TASKLESS_MCP_PATH: () => TASKLESS_MCP_PATH,
|
|
2192
2281
|
TASKLESS_TASK_URL_BASE: () => TASKLESS_TASK_URL_BASE,
|
|
@@ -2283,7 +2372,7 @@ function reviewCutoffAt() {
|
|
|
2283
2372
|
}
|
|
2284
2373
|
return at;
|
|
2285
2374
|
}
|
|
2286
|
-
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, 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;
|
|
2287
2376
|
var init_config = __esm({
|
|
2288
2377
|
"src/config.ts"() {
|
|
2289
2378
|
"use strict";
|
|
@@ -2332,6 +2421,7 @@ var init_config = __esm({
|
|
|
2332
2421
|
SWARM_PROD_HEARTBEAT_MS = envNum("SWARM_PROD_HEARTBEAT_MS", 15e3);
|
|
2333
2422
|
SWARM_VERSION_CHECK_MS = envNum("SWARM_VERSION_CHECK_MS", 5 * 6e4);
|
|
2334
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);
|
|
2335
2425
|
DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
|
|
2336
2426
|
DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
|
|
2337
2427
|
DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
|
|
@@ -2356,6 +2446,7 @@ var init_config = __esm({
|
|
|
2356
2446
|
SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
|
|
2357
2447
|
SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
|
|
2358
2448
|
SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
|
|
2449
|
+
SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
|
|
2359
2450
|
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
|
|
2360
2451
|
SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
|
|
2361
2452
|
SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
|
|
@@ -2401,12 +2492,12 @@ function isTransientResourceError(e) {
|
|
|
2401
2492
|
const code = e?.code;
|
|
2402
2493
|
return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
|
|
2403
2494
|
}
|
|
2404
|
-
function logTransientSpawnError(
|
|
2495
|
+
function logTransientSpawnError(label2, e) {
|
|
2405
2496
|
if (isTransientResourceError(e)) {
|
|
2406
2497
|
const code = e.code;
|
|
2407
|
-
console.warn(`[overmind] ${
|
|
2498
|
+
console.warn(`[overmind] ${label2}: \u041E\u0421 \u043D\u0435 \u0434\u0430\u0451\u0442 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B (${code}) \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0438\u0433\u043D\u0430\u043B \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043A\u0430, \u043D\u0435 \u043F\u0430\u0434\u0430\u044E`);
|
|
2408
2499
|
} else {
|
|
2409
|
-
console.warn(`[overmind] ${
|
|
2500
|
+
console.warn(`[overmind] ${label2} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F (\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u0437\u0430 \u044D\u0442\u043E\u0442 \u0442\u0438\u043A): ${String(e)}`);
|
|
2410
2501
|
}
|
|
2411
2502
|
}
|
|
2412
2503
|
function resourcesOk(load1, cpus, freeGb, maxLoadFactor = SWARM_MAX_LOAD_FACTOR, minFreeGb = SWARM_MIN_FREE_GB, fdLoadRatio = null, maxFdLoadFactor = SWARM_MAX_FD_LOAD_FACTOR, cpuBusy = null, maxCpuBusy = SWARM_MAX_CPU_BUSY) {
|
|
@@ -2417,8 +2508,8 @@ function resourcesOk(load1, cpus, freeGb, maxLoadFactor = SWARM_MAX_LOAD_FACTOR,
|
|
|
2417
2508
|
}
|
|
2418
2509
|
function parseVmStatAvailableBytes(vmStatOut) {
|
|
2419
2510
|
const page = Number(vmStatOut.match(/page size of (\d+) bytes/)?.[1]) || 4096;
|
|
2420
|
-
const pages = (
|
|
2421
|
-
const m = vmStatOut.match(new RegExp(`Pages ${
|
|
2511
|
+
const pages = (label2) => {
|
|
2512
|
+
const m = vmStatOut.match(new RegExp(`Pages ${label2}:\\s+(\\d+)`));
|
|
2422
2513
|
return m ? Number(m[1]) : 0;
|
|
2423
2514
|
};
|
|
2424
2515
|
return (pages("free") + pages("inactive") + pages("speculative") + pages("purgeable")) * page;
|
|
@@ -2870,8 +2961,8 @@ function describeError(e, maxDepth = 4) {
|
|
|
2870
2961
|
const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
|
|
2871
2962
|
const code = cur.code;
|
|
2872
2963
|
const codeText = code === void 0 || code === null ? "" : String(code);
|
|
2873
|
-
const
|
|
2874
|
-
if (
|
|
2964
|
+
const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
|
|
2965
|
+
if (label2 && !parts.includes(label2)) parts.push(label2);
|
|
2875
2966
|
cur = cur.cause;
|
|
2876
2967
|
}
|
|
2877
2968
|
return parts.join(" \u2190 ") || String(e);
|
|
@@ -3397,12 +3488,18 @@ async function previousHooksPath(worktree) {
|
|
|
3397
3488
|
}
|
|
3398
3489
|
async function removeWorktree(repo, path) {
|
|
3399
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) {
|
|
3400
3495
|
await cleanupWorktreeProcesses(path);
|
|
3401
3496
|
await detachWorktree(repo, path);
|
|
3402
|
-
|
|
3497
|
+
}
|
|
3498
|
+
async function pruneWorktrees(repo) {
|
|
3499
|
+
await $2({ reject: false })`git -C ${repo} worktree prune`;
|
|
3403
3500
|
}
|
|
3404
3501
|
async function worktreeDirty(path) {
|
|
3405
|
-
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`;
|
|
3406
3503
|
if (st.exitCode !== 0) return true;
|
|
3407
3504
|
const status = st.stdout.trim();
|
|
3408
3505
|
if (status) return true;
|
|
@@ -3992,37 +4089,37 @@ async function materializeAttachments(opts) {
|
|
|
3992
4089
|
let index = 0;
|
|
3993
4090
|
for (const ref of opts.refs) {
|
|
3994
4091
|
index++;
|
|
3995
|
-
const
|
|
4092
|
+
const label2 = ref.caption || ref.url;
|
|
3996
4093
|
const cls = classifyAttachment(ref);
|
|
3997
4094
|
if ("skip" in cls) {
|
|
3998
|
-
result.skipped.push(`${
|
|
4095
|
+
result.skipped.push(`${label2} \u2014 ${cls.skip}`);
|
|
3999
4096
|
continue;
|
|
4000
4097
|
}
|
|
4001
4098
|
if (result.files.length >= maxFiles) {
|
|
4002
|
-
result.skipped.push(`${
|
|
4099
|
+
result.skipped.push(`${label2} \u2014 \u0441\u0432\u0435\u0440\u0445 \u043B\u0438\u043C\u0438\u0442\u0430 \u0432 ${maxFiles} \u0444\u0430\u0439\u043B\u043E\u0432`);
|
|
4003
4100
|
continue;
|
|
4004
4101
|
}
|
|
4005
4102
|
if (ref.size != null && ref.size > maxFile) {
|
|
4006
|
-
result.skipped.push(`${
|
|
4103
|
+
result.skipped.push(`${label2} \u2014 ${mb(ref.size)} > \u043B\u0438\u043C\u0438\u0442\u0430 ${mb(maxFile)} \u043D\u0430 \u0444\u0430\u0439\u043B`);
|
|
4007
4104
|
continue;
|
|
4008
4105
|
}
|
|
4009
4106
|
let file = null;
|
|
4010
4107
|
try {
|
|
4011
4108
|
file = await opts.fetchFile(ref.url);
|
|
4012
4109
|
} catch (e) {
|
|
4013
|
-
result.skipped.push(`${
|
|
4110
|
+
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${errText(e)})`);
|
|
4014
4111
|
continue;
|
|
4015
4112
|
}
|
|
4016
4113
|
if (!file) {
|
|
4017
|
-
result.skipped.push(`${
|
|
4114
|
+
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0438\u043B\u0438 \u0444\u0430\u0439\u043B \u0443\u0434\u0430\u043B\u0451\u043D)`);
|
|
4018
4115
|
continue;
|
|
4019
4116
|
}
|
|
4020
4117
|
if (file.bytes.byteLength > maxFile) {
|
|
4021
|
-
result.skipped.push(`${
|
|
4118
|
+
result.skipped.push(`${label2} \u2014 ${mb(file.bytes.byteLength)} > \u043B\u0438\u043C\u0438\u0442\u0430 ${mb(maxFile)} \u043D\u0430 \u0444\u0430\u0439\u043B`);
|
|
4022
4119
|
continue;
|
|
4023
4120
|
}
|
|
4024
4121
|
if (total + file.bytes.byteLength > maxTotal) {
|
|
4025
|
-
result.skipped.push(`${
|
|
4122
|
+
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0432\u043B\u0435\u0437\u043B\u043E \u0432 \u043E\u0431\u0449\u0438\u0439 \u043B\u0438\u043C\u0438\u0442 ${mb(maxTotal)}`);
|
|
4026
4123
|
continue;
|
|
4027
4124
|
}
|
|
4028
4125
|
const name = attachmentFileName(index, ref.caption, attachmentExt(ref, file.contentType));
|
|
@@ -4033,7 +4130,7 @@ async function materializeAttachments(opts) {
|
|
|
4033
4130
|
}
|
|
4034
4131
|
await io.writeFile(join20(dir, name), file.bytes);
|
|
4035
4132
|
} catch (e) {
|
|
4036
|
-
result.skipped.push(`${
|
|
4133
|
+
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
4037
4134
|
continue;
|
|
4038
4135
|
}
|
|
4039
4136
|
total += file.bytes.byteLength;
|
|
@@ -4681,7 +4778,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4681
4778
|
return await safeCall("update_task", { id, assigneeId: userId }) != null;
|
|
4682
4779
|
},
|
|
4683
4780
|
uploadShot: (file) => uploadShot(safeCall, file, fetchImpl),
|
|
4684
|
-
addComment: async (idOrSlug, body,
|
|
4781
|
+
addComment: async (idOrSlug, body, label2, attachments) => {
|
|
4685
4782
|
const id = await resolveId(idOrSlug);
|
|
4686
4783
|
if (!id) {
|
|
4687
4784
|
console.error(`[taskless] add_comment \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D: \u0437\u0430\u0434\u0430\u0447\u0430 ${idOrSlug} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 (resolveId \u2192 null)`);
|
|
@@ -4700,7 +4797,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4700
4797
|
body: parts[i],
|
|
4701
4798
|
// Метки роя — на КАЖДОЙ части: без них хвост отчёта выглядит в ленте задачи
|
|
4702
4799
|
// чужой репликой без автора (swarmKind/swarmDroneName рисуются чипами).
|
|
4703
|
-
...
|
|
4800
|
+
...label2 ?? {},
|
|
4704
4801
|
// TASWM-294: скрины — к ПЕРВОЙ части, к шапке отчёта. Разносить их по
|
|
4705
4802
|
// частям треда нельзя: человек открывает задачу и должен увидеть, как
|
|
4706
4803
|
// это выглядит, в том же комменте, что и «что сделано».
|
|
@@ -10418,6 +10515,70 @@ function parkReason(attempts, reason) {
|
|
|
10418
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}`;
|
|
10419
10516
|
}
|
|
10420
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
|
+
|
|
10421
10582
|
// src/overmind.ts
|
|
10422
10583
|
init_git();
|
|
10423
10584
|
|
|
@@ -10472,6 +10633,22 @@ async function prHeadBranch(prUrl, run = realRunner) {
|
|
|
10472
10633
|
return null;
|
|
10473
10634
|
}
|
|
10474
10635
|
}
|
|
10636
|
+
async function prBaseSync(prUrl, run = realRunner) {
|
|
10637
|
+
const m = prUrl.trim().match(/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/\d+/i);
|
|
10638
|
+
if (!isGithubPrUrl(prUrl) || !m) return null;
|
|
10639
|
+
const [, owner, repo] = m;
|
|
10640
|
+
try {
|
|
10641
|
+
const j = JSON.parse(await run(["pr", "view", prUrl, "--json", "baseRefName,headRefOid"]));
|
|
10642
|
+
const base3 = j.baseRefName;
|
|
10643
|
+
const head = j.headRefOid;
|
|
10644
|
+
if (!base3 || !head) return null;
|
|
10645
|
+
const out = await run(["api", `repos/${owner}/${repo}/compare/${base3}...${head}?per_page=1`, "--jq", ".behind_by"]);
|
|
10646
|
+
const behindBy = Number(out.trim());
|
|
10647
|
+
return Number.isFinite(behindBy) && behindBy >= 0 ? { behindBy, base: base3 } : null;
|
|
10648
|
+
} catch {
|
|
10649
|
+
return null;
|
|
10650
|
+
}
|
|
10651
|
+
}
|
|
10475
10652
|
async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
|
|
10476
10653
|
if (!branch || !repoDir) return [];
|
|
10477
10654
|
try {
|
|
@@ -10704,17 +10881,17 @@ function normalizeZoneLabel(zone) {
|
|
|
10704
10881
|
return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
|
|
10705
10882
|
}
|
|
10706
10883
|
function renderZoneMemoryBlock(zone, notes) {
|
|
10707
|
-
const
|
|
10708
|
-
if (!
|
|
10884
|
+
const label2 = normalizeZoneLabel(zone);
|
|
10885
|
+
if (!label2) return "";
|
|
10709
10886
|
const head = notes.length ? [
|
|
10710
|
-
`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${
|
|
10887
|
+
`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
|
|
10711
10888
|
...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
|
|
10712
10889
|
"\u042D\u0442\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438, \u0430 \u043D\u0435 \u0438\u0441\u0442\u0438\u043D\u0430 \u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0446\u0438\u0438: \u0440\u0430\u0437\u043E\u0448\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C \u2014 \u0432\u0435\u0440\u044C \u043A\u043E\u0434\u0443 \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0441\u0434\u0430\u0447\u0435.",
|
|
10713
10890
|
`\u041F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u0432 done: \`zone_notes_used: ["${notes[0].id}"]\` (id \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435 \u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u042D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0436\u0438\u0432\u0451\u0442: \u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430, \u0438 \u0432\u044B\u0442\u0435\u0441\u043D\u044F\u0435\u0442\u0441\u044F \u0442\u0430, \u0432 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0440\u043E\u0439 \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0445 \u043D\u0435 \u0443\u0431\u0435\u0436\u0434\u0430\u043B\u0441\u044F. \u041D\u0435 \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u043E\u0441\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E, \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434.`
|
|
10714
|
-
] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${
|
|
10891
|
+
] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
|
|
10715
10892
|
return [
|
|
10716
10893
|
...head,
|
|
10717
|
-
`\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${
|
|
10894
|
+
`\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label2}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`
|
|
10718
10895
|
].join("\n");
|
|
10719
10896
|
}
|
|
10720
10897
|
function collectZoneNoteConfirmations(report) {
|
|
@@ -11635,6 +11812,52 @@ import { createHash as createHash3 } from "crypto";
|
|
|
11635
11812
|
|
|
11636
11813
|
// src/anomalies.ts
|
|
11637
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
|
|
11638
11861
|
var LEVEL_RANK = { note: 0, feed: 1, decision: 2 };
|
|
11639
11862
|
function plural2(n, forms) {
|
|
11640
11863
|
const mod100 = n % 100;
|
|
@@ -12002,15 +12225,18 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
12002
12225
|
key: `pr_no_reviewer:${run.id}`,
|
|
12003
12226
|
minDurationMs: thresholds.prNoReviewerMs,
|
|
12004
12227
|
group: PR_GROUP,
|
|
12005
|
-
build: () =>
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
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
|
+
}
|
|
12014
12240
|
});
|
|
12015
12241
|
} else if (run.review_verdict === "changes" && db.activeReviewersFor(run.id).length === 0) {
|
|
12016
12242
|
candidates.push({
|
|
@@ -12508,6 +12734,11 @@ function makeDroneCapReader(opts) {
|
|
|
12508
12734
|
|
|
12509
12735
|
// src/mergeGate.ts
|
|
12510
12736
|
init_src();
|
|
12737
|
+
|
|
12738
|
+
// src/mergeQueue.ts
|
|
12739
|
+
init_src();
|
|
12740
|
+
|
|
12741
|
+
// src/prNumber.ts
|
|
12511
12742
|
function prNumberOf(ref) {
|
|
12512
12743
|
const s = (ref || "").trim();
|
|
12513
12744
|
if (!s) return null;
|
|
@@ -12516,6 +12747,79 @@ function prNumberOf(ref) {
|
|
|
12516
12747
|
const bare = s.match(/^#?(\d+)$/);
|
|
12517
12748
|
return bare ? Number(bare[1]) : null;
|
|
12518
12749
|
}
|
|
12750
|
+
|
|
12751
|
+
// src/mergeQueue.ts
|
|
12752
|
+
var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
|
|
12753
|
+
function isMergeCandidate(r) {
|
|
12754
|
+
return !!r.pr_url && r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
|
|
12755
|
+
}
|
|
12756
|
+
function mergeQueueCandidates(runs, repo) {
|
|
12757
|
+
const byPr = /* @__PURE__ */ new Map();
|
|
12758
|
+
for (const r of runs) {
|
|
12759
|
+
if (r.repo !== repo || !isMergeCandidate(r)) continue;
|
|
12760
|
+
const num = prNumberOf(r.pr_url);
|
|
12761
|
+
const seen = byPr.get(num);
|
|
12762
|
+
if (!seen || r.id > seen.id) byPr.set(num, r);
|
|
12763
|
+
}
|
|
12764
|
+
return [...byPr.values()];
|
|
12765
|
+
}
|
|
12766
|
+
var BASE_TTL_MS = 2e4;
|
|
12767
|
+
function makeMergeQueueResolver(deps) {
|
|
12768
|
+
const memo = /* @__PURE__ */ new Map();
|
|
12769
|
+
const evaluate = async (e, prUrl, ready) => {
|
|
12770
|
+
const now = Date.now();
|
|
12771
|
+
let cached = memo.get(prUrl);
|
|
12772
|
+
const ttl = deps.ttlMs ?? BASE_TTL_MS;
|
|
12773
|
+
if (!cached || now - cached.at >= ttl) {
|
|
12774
|
+
const base3 = await deps.baseSync(prUrl).catch(() => null);
|
|
12775
|
+
let checks = "none";
|
|
12776
|
+
if (base3?.behindBy === 0 && deps.checks) {
|
|
12777
|
+
const ci = await deps.checks(prUrl).catch(() => null);
|
|
12778
|
+
checks = ci ? ci.bucket : null;
|
|
12779
|
+
}
|
|
12780
|
+
cached = { at: now, base: base3, checks };
|
|
12781
|
+
memo.set(prUrl, cached);
|
|
12782
|
+
}
|
|
12783
|
+
return computeMergeQueue({ entry: e, ready, behindBy: cached.base ? cached.base.behindBy : null, checks: cached.checks });
|
|
12784
|
+
};
|
|
12785
|
+
return async (run) => {
|
|
12786
|
+
if (!deps.baseSync || !run.pr_url) return null;
|
|
12787
|
+
const num = prNumberOf(run.pr_url);
|
|
12788
|
+
if (num == null) return null;
|
|
12789
|
+
const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
|
|
12790
|
+
const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
|
|
12791
|
+
if (!mine) return null;
|
|
12792
|
+
const ready = [];
|
|
12793
|
+
const urlOf = /* @__PURE__ */ new Map();
|
|
12794
|
+
for (const c of candidates) {
|
|
12795
|
+
if (!await deps.gateAllows(c).catch(() => false)) continue;
|
|
12796
|
+
ready.push(entryOf(c));
|
|
12797
|
+
urlOf.set(c.id, c.pr_url);
|
|
12798
|
+
}
|
|
12799
|
+
const entry = entryOf(mine);
|
|
12800
|
+
if (!ready.some((e) => e.runId === entry.runId)) return null;
|
|
12801
|
+
let queue = orderMergeQueue(ready);
|
|
12802
|
+
while (queue.length > 1 && queue[0].runId !== entry.runId) {
|
|
12803
|
+
const head = queue[0];
|
|
12804
|
+
const headUrl = urlOf.get(head.runId);
|
|
12805
|
+
const decision = await evaluate(head, headUrl, queue);
|
|
12806
|
+
if (decision.code !== "checks_red") break;
|
|
12807
|
+
queue = queue.slice(1);
|
|
12808
|
+
}
|
|
12809
|
+
if (queue[0].runId !== entry.runId) {
|
|
12810
|
+
return computeMergeQueue({ entry, ready: queue, behindBy: null, checks: "none" });
|
|
12811
|
+
}
|
|
12812
|
+
return evaluate(entry, mine.pr_url, queue);
|
|
12813
|
+
};
|
|
12814
|
+
}
|
|
12815
|
+
function entryOf(r) {
|
|
12816
|
+
return { runId: r.id, prNumber: prNumberOf(r.pr_url), slug: r.task_slug };
|
|
12817
|
+
}
|
|
12818
|
+
function needsBaseRefresh(d) {
|
|
12819
|
+
return d?.code === "base_stale";
|
|
12820
|
+
}
|
|
12821
|
+
|
|
12822
|
+
// src/mergeGate.ts
|
|
12519
12823
|
function findRunByPr(runs, ref) {
|
|
12520
12824
|
const num = prNumberOf(ref);
|
|
12521
12825
|
if (num == null) return null;
|
|
@@ -12527,17 +12831,34 @@ function findRunByPr(runs, ref) {
|
|
|
12527
12831
|
return best;
|
|
12528
12832
|
}
|
|
12529
12833
|
function makeMergeGateResolver(deps) {
|
|
12530
|
-
return
|
|
12531
|
-
|
|
12532
|
-
|
|
12834
|
+
return makeMergeGateParts(deps).gate;
|
|
12835
|
+
}
|
|
12836
|
+
function makeMergeGateParts(deps) {
|
|
12837
|
+
const gateFor = async (run) => {
|
|
12533
12838
|
const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
|
|
12534
|
-
|
|
12839
|
+
return computeMergeGate({
|
|
12535
12840
|
hasRun: true,
|
|
12536
12841
|
autoReview,
|
|
12537
12842
|
reviewVerdict: run.review_verdict,
|
|
12538
12843
|
autoVerify: deps.autoVerify === true,
|
|
12539
12844
|
verifyVerdict: run.verify_verdict ?? null
|
|
12540
12845
|
});
|
|
12846
|
+
};
|
|
12847
|
+
const queueFor = makeMergeQueueResolver({
|
|
12848
|
+
listRuns: deps.listRuns,
|
|
12849
|
+
gateAllows: async (r) => (await gateFor(r)).allowed,
|
|
12850
|
+
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
12851
|
+
...deps.checks ? { checks: deps.checks } : {},
|
|
12852
|
+
...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
|
|
12853
|
+
});
|
|
12854
|
+
const gate = async (ref) => {
|
|
12855
|
+
const run = findRunByPr(deps.listRuns(), ref);
|
|
12856
|
+
if (!run) {
|
|
12857
|
+
return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
|
|
12858
|
+
}
|
|
12859
|
+
const accept = await gateFor(run);
|
|
12860
|
+
const queue = accept.allowed && accept.code !== "unknown_pr" ? await queueFor(run) : null;
|
|
12861
|
+
const decision = computeMergeDecision(accept, queue);
|
|
12541
12862
|
return {
|
|
12542
12863
|
...decision,
|
|
12543
12864
|
run: {
|
|
@@ -12550,6 +12871,7 @@ function makeMergeGateResolver(deps) {
|
|
|
12550
12871
|
}
|
|
12551
12872
|
};
|
|
12552
12873
|
};
|
|
12874
|
+
return { gate, queue: queueFor };
|
|
12553
12875
|
}
|
|
12554
12876
|
|
|
12555
12877
|
// src/envGate.ts
|
|
@@ -12990,7 +13312,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
12990
13312
|
// package.json
|
|
12991
13313
|
var package_default = {
|
|
12992
13314
|
name: "@taskless-app/swarm",
|
|
12993
|
-
version: "0.6.
|
|
13315
|
+
version: "0.6.20",
|
|
12994
13316
|
type: "module",
|
|
12995
13317
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
12996
13318
|
keywords: [
|
|
@@ -13040,7 +13362,7 @@ var package_default = {
|
|
|
13040
13362
|
execa: "^10.0.0",
|
|
13041
13363
|
zod: "3.25.76"
|
|
13042
13364
|
},
|
|
13043
|
-
gitHead: "
|
|
13365
|
+
gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
|
|
13044
13366
|
};
|
|
13045
13367
|
|
|
13046
13368
|
// src/version.ts
|
|
@@ -14743,8 +15065,8 @@ async function executeHostCommands(opts) {
|
|
|
14743
15065
|
}
|
|
14744
15066
|
async function executeOne(cmd, opts) {
|
|
14745
15067
|
const { effects, ack, statePath } = opts;
|
|
14746
|
-
const
|
|
14747
|
-
effects.narrate(`\u{1F4DF} \u041A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u0437 Taskless: ${
|
|
15068
|
+
const label2 = hostCommandLabel(cmd);
|
|
15069
|
+
effects.narrate(`\u{1F4DF} \u041A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u0437 Taskless: ${label2}`);
|
|
14748
15070
|
try {
|
|
14749
15071
|
switch (cmd.kind) {
|
|
14750
15072
|
case "pause":
|
|
@@ -15627,6 +15949,14 @@ function lastConflictAttemptMs(db, runId) {
|
|
|
15627
15949
|
if (!evs.length) return null;
|
|
15628
15950
|
return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
|
|
15629
15951
|
}
|
|
15952
|
+
async function needsBaseRefreshNow(deps, run, stormActive, now) {
|
|
15953
|
+
if (!deps.mergeQueue || !deps.resolveConflict || stormActive) return false;
|
|
15954
|
+
if (deps.db.activeResolverFor(run.id)) return false;
|
|
15955
|
+
if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) return false;
|
|
15956
|
+
const last = lastConflictAttemptMs(deps.db, run.id);
|
|
15957
|
+
if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) return false;
|
|
15958
|
+
return needsBaseRefresh(await deps.mergeQueue(run).catch(() => null));
|
|
15959
|
+
}
|
|
15630
15960
|
function lastCiCheckMs(db, runId) {
|
|
15631
15961
|
const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked");
|
|
15632
15962
|
if (!evs.length) return null;
|
|
@@ -15882,6 +16212,92 @@ async function closeMergedTaskRuns(deps) {
|
|
|
15882
16212
|
}
|
|
15883
16213
|
}
|
|
15884
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
|
+
}
|
|
15885
16301
|
async function closeOrphanedReviewers(deps) {
|
|
15886
16302
|
for (const rv of deps.db.listRuns()) {
|
|
15887
16303
|
const checkedId = rv.kind === "reviewer" ? rv.reviews_run_id : rv.kind === "verifier" ? rv.verifies_run_id : null;
|
|
@@ -16957,9 +17373,42 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
16957
17373
|
return "skipped";
|
|
16958
17374
|
}
|
|
16959
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
|
+
}
|
|
16960
17386
|
return await failBeforeStart(deps, run, String(e));
|
|
16961
17387
|
}
|
|
16962
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
|
+
}
|
|
16963
17412
|
async function failBeforeStart(deps, run, reason) {
|
|
16964
17413
|
deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
|
|
16965
17414
|
deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
|
|
@@ -17524,6 +17973,18 @@ async function tickOnce(deps) {
|
|
|
17524
17973
|
}
|
|
17525
17974
|
}
|
|
17526
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
|
+
}
|
|
17527
17988
|
if (deps.logRotate) {
|
|
17528
17989
|
const lr = deps.logRotate;
|
|
17529
17990
|
const sinceLast = lr.lastAt == null ? Infinity : now - lr.lastAt;
|
|
@@ -17653,7 +18114,8 @@ async function tickOnce(deps) {
|
|
|
17653
18114
|
continue;
|
|
17654
18115
|
}
|
|
17655
18116
|
await retireOutdatedReviewers(deps, run);
|
|
17656
|
-
|
|
18117
|
+
const baseStale = !st.conflicting && await needsBaseRefreshNow(deps, run, stormActive, now);
|
|
18118
|
+
if ((st.conflicting || baseStale) && deps.resolveConflict && !stormActive) {
|
|
17657
18119
|
if (deps.resourcesOk && !deps.resourcesOk()) continue;
|
|
17658
18120
|
if (countActiveDrones(deps.db) >= droneCap) continue;
|
|
17659
18121
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
@@ -17666,7 +18128,11 @@ async function tickOnce(deps) {
|
|
|
17666
18128
|
}
|
|
17667
18129
|
const last = lastConflictAttemptMs(deps.db, run.id);
|
|
17668
18130
|
if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) continue;
|
|
17669
|
-
deps.db.addEvent(
|
|
18131
|
+
deps.db.addEvent(
|
|
18132
|
+
run.id,
|
|
18133
|
+
"conflict",
|
|
18134
|
+
st.conflicting ? "PR \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 main \u2014 \u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 main (merge-main-in)" : "\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043C\u0435\u0440\u0436\u0430: \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u2014 \u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 main \u0438 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0435\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435 (TASWM-472)"
|
|
18135
|
+
);
|
|
17670
18136
|
try {
|
|
17671
18137
|
await deps.resolveConflict(run);
|
|
17672
18138
|
} catch (e) {
|
|
@@ -17755,6 +18221,8 @@ async function tickOnce(deps) {
|
|
|
17755
18221
|
}
|
|
17756
18222
|
}
|
|
17757
18223
|
await closeMergedTaskRuns(deps);
|
|
18224
|
+
await rescueReviewlessPrs(deps, stormActive, now);
|
|
18225
|
+
relinkUnboundReviewers(deps);
|
|
17758
18226
|
await closeOrphanedReviewers(deps);
|
|
17759
18227
|
await spawnVerifiersForOpenPrs(deps, stormActive);
|
|
17760
18228
|
refreshReviewPhases(deps);
|
|
@@ -18989,8 +19457,21 @@ async function runDaemon(opts) {
|
|
|
18989
19457
|
};
|
|
18990
19458
|
control.set = setPaused;
|
|
18991
19459
|
const restart = initialRestartState();
|
|
19460
|
+
const mergeParts = makeMergeGateParts({
|
|
19461
|
+
listRuns: () => db.listRuns(),
|
|
19462
|
+
...autoReviewProject ? { autoReview: autoReviewProject } : {},
|
|
19463
|
+
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
19464
|
+
autoVerify: SWARM_AUTO_VERIFY,
|
|
19465
|
+
// TASWM-344: вторая ось приёмки — вердикт верификатора
|
|
19466
|
+
// TASWM-472: третья ось — ОЧЕРЕДЬ. Вливаем по одному и только на свежей базе; ридер
|
|
19467
|
+
// свежести ходит в gh (`compare/base...head` → behind_by). Ключа нет при
|
|
19468
|
+
// SWARM_MERGE_QUEUE=0 — тогда гейт отвечает ровно как до TASWM-472.
|
|
19469
|
+
...SWARM_MERGE_QUEUE ? { baseSync: (url) => prBaseSync(url), checks: (url) => prCiStatus(url) } : {}
|
|
19470
|
+
});
|
|
18992
19471
|
const deps = {
|
|
18993
19472
|
db,
|
|
19473
|
+
// TASWM-472: реконсайлу от очереди нужен ровно исход `base_stale` (см. needsBaseRefreshNow).
|
|
19474
|
+
...SWARM_MERGE_QUEUE ? { mergeQueue: mergeParts.queue } : {},
|
|
18994
19475
|
narrate: narrator.narrate,
|
|
18995
19476
|
control,
|
|
18996
19477
|
spawn: async (run) => {
|
|
@@ -19072,7 +19553,7 @@ async function runDaemon(opts) {
|
|
|
19072
19553
|
// TasklessClient.addComment + gh/git хелперы. Без tl (нет токена) ключи не задаём —
|
|
19073
19554
|
// postAdoptionReport/postScrapedVerdictReport становятся no-op, как раньше.
|
|
19074
19555
|
...tl ? {
|
|
19075
|
-
reportComment: (slug, body,
|
|
19556
|
+
reportComment: (slug, body, label2, ws) => tl.forWorkspace(ws).addComment(slug, body, label2)
|
|
19076
19557
|
} : {},
|
|
19077
19558
|
prCommitTitles: (prUrl) => prCommitTitles(prUrl),
|
|
19078
19559
|
prHeadBranch: (prUrl) => prHeadBranch(prUrl),
|
|
@@ -19135,6 +19616,32 @@ async function runDaemon(opts) {
|
|
|
19135
19616
|
intervalMs: SWARM_SWEEP_INTERVAL_MS,
|
|
19136
19617
|
lastAt: null
|
|
19137
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)),
|
|
19138
19645
|
isDirty: (p) => worktreeDirty(p),
|
|
19139
19646
|
// git-активность как liveness (TASWM-67): последний коммит worktree — реальный git-вызов.
|
|
19140
19647
|
lastCommitMs: (p) => worktreeLastCommitMs(p),
|
|
@@ -19548,13 +20055,7 @@ async function runDaemon(opts) {
|
|
|
19548
20055
|
// TASWM-396: тот же ридер решает и подъём ревьюера, не только мерж — иначе проектная
|
|
19549
20056
|
// настройка «ревью не нужно» ничего не выключала, а машина продолжала платить.
|
|
19550
20057
|
...autoReviewProject ? { autoReviewForRepo: autoReviewProject } : {},
|
|
19551
|
-
mergeGate:
|
|
19552
|
-
listRuns: () => db.listRuns(),
|
|
19553
|
-
...autoReviewProject ? { autoReview: autoReviewProject } : {},
|
|
19554
|
-
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
19555
|
-
autoVerify: SWARM_AUTO_VERIFY
|
|
19556
|
-
// TASWM-344: вторая ось приёмки — вердикт верификатора
|
|
19557
|
-
}),
|
|
20058
|
+
mergeGate: mergeParts.gate,
|
|
19558
20059
|
// Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
|
|
19559
20060
|
// narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
|
|
19560
20061
|
control,
|