@taskless-app/swarm 0.6.11 → 0.6.13
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 +1220 -356
- package/dist/overmind.js +1153 -304
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -13,7 +13,7 @@ function isMilestoneKind(kind) {
|
|
|
13
13
|
return MILESTONE_KIND_SET.has(kind);
|
|
14
14
|
}
|
|
15
15
|
function toRunKind(role) {
|
|
16
|
-
return role === "hero" || role === "reviewer" ? role : "drone";
|
|
16
|
+
return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
|
|
17
17
|
}
|
|
18
18
|
var ACTIVE_DRONE_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
|
|
19
19
|
var init_types = __esm({
|
|
@@ -335,6 +335,53 @@ var init_reviewPhase = __esm({
|
|
|
335
335
|
}
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
+
// ../swarm-core/src/verifyVerdict.ts
|
|
339
|
+
function isVerifyVerdict(v) {
|
|
340
|
+
return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
|
|
341
|
+
}
|
|
342
|
+
function toVerifyVerdict(v) {
|
|
343
|
+
return isVerifyVerdict(v) ? v : null;
|
|
344
|
+
}
|
|
345
|
+
function isNegativeVerifyVerdict(v) {
|
|
346
|
+
return v === "not_reproduced" || v === "found_issues";
|
|
347
|
+
}
|
|
348
|
+
function verifyVerdictLabel(v) {
|
|
349
|
+
switch (v) {
|
|
350
|
+
case "confirmed":
|
|
351
|
+
return "\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E";
|
|
352
|
+
case "not_reproduced":
|
|
353
|
+
return "\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F";
|
|
354
|
+
case "found_issues":
|
|
355
|
+
return "\u041D\u0410\u0428\u0401\u041B";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function parseVerifyVerdictComment(text) {
|
|
359
|
+
const line = (text ?? "").split("\n").find((l) => l.trim().length > 0);
|
|
360
|
+
if (!line) return null;
|
|
361
|
+
let best = null;
|
|
362
|
+
for (const [verdict, marks] of VERDICT_MARKS) {
|
|
363
|
+
for (const mark of marks) {
|
|
364
|
+
const at = line.indexOf(mark);
|
|
365
|
+
if (at < 0) continue;
|
|
366
|
+
if (!best || at < best.at) best = { verdict, at };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return best?.verdict ?? null;
|
|
370
|
+
}
|
|
371
|
+
var VERIFY_VERDICTS, VERIFY_VERDICT_SET, VERDICT_MARKS;
|
|
372
|
+
var init_verifyVerdict = __esm({
|
|
373
|
+
"../swarm-core/src/verifyVerdict.ts"() {
|
|
374
|
+
"use strict";
|
|
375
|
+
VERIFY_VERDICTS = ["confirmed", "not_reproduced", "found_issues"];
|
|
376
|
+
VERIFY_VERDICT_SET = new Set(VERIFY_VERDICTS);
|
|
377
|
+
VERDICT_MARKS = [
|
|
378
|
+
["not_reproduced", ["\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F", "NOT REPRODUCED", "\u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "NOT CONFIRMED"]],
|
|
379
|
+
["found_issues", ["\u041D\u0410\u0428\u0401\u041B", "\u041D\u0410\u0428\u0415\u041B", "FOUND"]],
|
|
380
|
+
["confirmed", ["\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E", "CONFIRMED"]]
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
338
385
|
// ../swarm-core/src/mergeGate.ts
|
|
339
386
|
function computeMergeGate(input) {
|
|
340
387
|
if (!input.hasRun) {
|
|
@@ -343,21 +390,38 @@ function computeMergeGate(input) {
|
|
|
343
390
|
if (!input.autoReview) {
|
|
344
391
|
return { allowed: true, code: "auto_review_off", reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F" };
|
|
345
392
|
}
|
|
346
|
-
if (input.reviewVerdict === "approve") {
|
|
347
|
-
return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
|
|
348
|
-
}
|
|
349
393
|
if (input.reviewVerdict === "changes") {
|
|
350
394
|
return { allowed: false, code: "changes_requested", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043F\u0440\u0430\u0432\u043A\u0438 (REQUEST CHANGES) \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430" };
|
|
351
395
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
396
|
+
if (input.reviewVerdict !== "approve") {
|
|
397
|
+
return {
|
|
398
|
+
allowed: false,
|
|
399
|
+
code: "no_verdict",
|
|
400
|
+
reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (input.autoVerify) {
|
|
404
|
+
if (input.verifyVerdict == null) {
|
|
405
|
+
return {
|
|
406
|
+
allowed: false,
|
|
407
|
+
code: "no_verify_verdict",
|
|
408
|
+
reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve, \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 (TASWM-344)"
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (isNegativeVerifyVerdict(input.verifyVerdict)) {
|
|
412
|
+
return {
|
|
413
|
+
allowed: false,
|
|
414
|
+
code: "verify_negative",
|
|
415
|
+
reason: `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u044B\u043D\u0435\u0441 \xAB${verifyVerdictLabel(input.verifyVerdict)}\xBB \u2014 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u043E\u0441\u044C, \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-344)`
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
|
|
357
420
|
}
|
|
358
421
|
var init_mergeGate = __esm({
|
|
359
422
|
"../swarm-core/src/mergeGate.ts"() {
|
|
360
423
|
"use strict";
|
|
424
|
+
init_verifyVerdict();
|
|
361
425
|
}
|
|
362
426
|
});
|
|
363
427
|
|
|
@@ -1383,6 +1447,7 @@ var init_src = __esm({
|
|
|
1383
1447
|
init_timeline();
|
|
1384
1448
|
init_reviewPhase();
|
|
1385
1449
|
init_mergeGate();
|
|
1450
|
+
init_verifyVerdict();
|
|
1386
1451
|
init_status();
|
|
1387
1452
|
init_deps();
|
|
1388
1453
|
init_time();
|
|
@@ -1532,11 +1597,18 @@ var init_swarmFiles = __esm({
|
|
|
1532
1597
|
});
|
|
1533
1598
|
|
|
1534
1599
|
// src/pricing.ts
|
|
1535
|
-
function
|
|
1536
|
-
const
|
|
1537
|
-
if (!
|
|
1600
|
+
function pricingAt(pricing, at) {
|
|
1601
|
+
const intro = pricing.intro;
|
|
1602
|
+
if (!intro) return pricing;
|
|
1603
|
+
const day = at.toISOString().slice(0, 10);
|
|
1604
|
+
return day <= intro.until ? { inputPerMTok: intro.inputPerMTok, outputPerMTok: intro.outputPerMTok } : pricing;
|
|
1605
|
+
}
|
|
1606
|
+
function costFromUsage(usage, model, table = MODEL_PRICING, at = /* @__PURE__ */ new Date()) {
|
|
1607
|
+
const entry = model ? table[model] : void 0;
|
|
1608
|
+
if (!entry) {
|
|
1538
1609
|
return { costUsd: 0, warning: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0430: "${model ?? ""}" \u2014 cost \u043D\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D` };
|
|
1539
1610
|
}
|
|
1611
|
+
const pricing = pricingAt(entry, at);
|
|
1540
1612
|
const input = usage.input_tokens ?? 0;
|
|
1541
1613
|
const output = usage.output_tokens ?? 0;
|
|
1542
1614
|
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
@@ -1549,10 +1621,12 @@ var init_pricing = __esm({
|
|
|
1549
1621
|
"src/pricing.ts"() {
|
|
1550
1622
|
"use strict";
|
|
1551
1623
|
MODEL_PRICING = {
|
|
1552
|
-
//
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1624
|
+
// Sonnet 5: вводные 2/10 по 31.08.2026 включительно, дальше базовые 3/15.
|
|
1625
|
+
"claude-sonnet-5": {
|
|
1626
|
+
inputPerMTok: 3,
|
|
1627
|
+
outputPerMTok: 15,
|
|
1628
|
+
intro: { inputPerMTok: 2, outputPerMTok: 10, until: "2026-08-31" }
|
|
1629
|
+
},
|
|
1556
1630
|
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1557
1631
|
// Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
|
|
1558
1632
|
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
@@ -1991,6 +2065,7 @@ __export(config_exports, {
|
|
|
1991
2065
|
SWARM_ANOMALY_RESOURCE_GATE_STALL_MS: () => SWARM_ANOMALY_RESOURCE_GATE_STALL_MS,
|
|
1992
2066
|
SWARM_ANOMALY_RUN_DOWN_WINDOW_MS: () => SWARM_ANOMALY_RUN_DOWN_WINDOW_MS,
|
|
1993
2067
|
SWARM_AUTO_REVIEW: () => SWARM_AUTO_REVIEW,
|
|
2068
|
+
SWARM_AUTO_VERIFY: () => SWARM_AUTO_VERIFY,
|
|
1994
2069
|
SWARM_BACKEND: () => SWARM_BACKEND,
|
|
1995
2070
|
SWARM_CLAIM_INTERVAL_MS: () => SWARM_CLAIM_INTERVAL_MS,
|
|
1996
2071
|
SWARM_CPU_SANITY_BUSY: () => SWARM_CPU_SANITY_BUSY,
|
|
@@ -2121,7 +2196,7 @@ function reviewCutoffAt() {
|
|
|
2121
2196
|
}
|
|
2122
2197
|
return at;
|
|
2123
2198
|
}
|
|
2124
|
-
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, 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, 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_WATCHDOG_STALL_MS;
|
|
2199
|
+
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, 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_WATCHDOG_STALL_MS;
|
|
2125
2200
|
var init_config = __esm({
|
|
2126
2201
|
"src/config.ts"() {
|
|
2127
2202
|
"use strict";
|
|
@@ -2192,6 +2267,7 @@ var init_config = __esm({
|
|
|
2192
2267
|
TASKLESS_TASK_URL_BASE = process.env.TASKLESS_TASK_URL_BASE || (TASKLESS_WORKSPACE_SLUG ? `${TASKLESS_API.replace(/\/$/, "")}/app/${TASKLESS_WORKSPACE_SLUG}` : "");
|
|
2193
2268
|
SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
|
|
2194
2269
|
SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
|
|
2270
|
+
SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
|
|
2195
2271
|
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
|
|
2196
2272
|
SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
|
|
2197
2273
|
SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
|
|
@@ -2631,6 +2707,105 @@ var init_heartbeat = __esm({
|
|
|
2631
2707
|
}
|
|
2632
2708
|
});
|
|
2633
2709
|
|
|
2710
|
+
// src/agentToken.ts
|
|
2711
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
2712
|
+
import { join as join8 } from "path";
|
|
2713
|
+
function loadAgentToken() {
|
|
2714
|
+
try {
|
|
2715
|
+
const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
|
|
2716
|
+
return s.token ? s : null;
|
|
2717
|
+
} catch {
|
|
2718
|
+
return null;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
async function probeAgentToken(args, fetchFn = fetch) {
|
|
2722
|
+
try {
|
|
2723
|
+
const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
|
|
2724
|
+
headers: { Authorization: `Bearer ${args.token}` },
|
|
2725
|
+
// Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
|
|
2726
|
+
// то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
|
|
2727
|
+
signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
|
|
2728
|
+
});
|
|
2729
|
+
if (res.status === 401 || res.status === 403) return "revoked";
|
|
2730
|
+
return res.ok ? "ok" : "unreachable";
|
|
2731
|
+
} catch {
|
|
2732
|
+
return "unreachable";
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
function isRevokedTokenError(e) {
|
|
2736
|
+
if (e && typeof e === "object" && e.code === 401) return true;
|
|
2737
|
+
const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
2738
|
+
return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
|
|
2739
|
+
}
|
|
2740
|
+
function revokedTokenLine() {
|
|
2741
|
+
return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
|
|
2742
|
+
}
|
|
2743
|
+
function revokedTokenComplaint() {
|
|
2744
|
+
return [
|
|
2745
|
+
"",
|
|
2746
|
+
"\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
2747
|
+
"\u2502 \u0422\u041E\u041A\u0415\u041D \u0420\u041E\u042F \u041D\u0415 \u041F\u0420\u0418\u041D\u042F\u0422 (401). \u0414\u043E\u0441\u0442\u0443\u043F \u043A Taskless \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D. \u2502",
|
|
2748
|
+
"\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
|
|
2749
|
+
"\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u2014 \u043E\u0434\u043D\u0430 \u0438\u0437 \u0442\u0440\u0451\u0445, \u0432\u0441\u0435 \u0442\u0440\u0438 \u0440\u0435\u0448\u0430\u044E\u0442\u0441\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C:",
|
|
2750
|
+
" \u2022 \xAB\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\xBB \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0442\u043E\u043A\u0435\u043D \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u044B;",
|
|
2751
|
+
" \u2022 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u0440\u043E\u044F\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439) \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D\u044B \u0432\u0441\u0435;",
|
|
2752
|
+
" \u2022 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C \u2014 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C.",
|
|
2753
|
+
"",
|
|
2754
|
+
"\u0412\u0445\u043E\u0434 \u043D\u0430 \u0414\u0420\u0423\u0413\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435 \u044D\u0442\u043E\u0442 \u0442\u043E\u043A\u0435\u043D \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0433\u0430\u0441\u0438\u0442 (TASWM-324): \u0443 \u043A\u0430\u0436\u0434\u043E\u0439",
|
|
2755
|
+
"\u043C\u0430\u0448\u0438\u043D\u044B \u0441\u0432\u043E\u0439 \u0442\u043E\u043A\u0435\u043D, \u043F\u0440\u0438\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 \u043A \u0435\u0451 hostId.",
|
|
2756
|
+
"",
|
|
2757
|
+
"\u0414\u0435\u043C\u043E\u043D \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0438 \u043E\u0442\u0447\u0451\u0442\u044B",
|
|
2758
|
+
"\u043D\u0435 \u043F\u0438\u0448\u0443\u0442\u0441\u044F. \u041F\u043E\u0447\u0438\u043D\u0438 \u0432\u0445\u043E\u0434\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E: swarm login",
|
|
2759
|
+
"\u041F\u0440\u043E\u0431\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442, \u0430 \u0432\u043E\u0439\u0442\u0438 \u043C\u0435\u0448\u0430\u0435\u0442 \u0441\u0442\u0430\u0440\u044B\u0439 \u0444\u0430\u0439\u043B: swarm login --force",
|
|
2760
|
+
""
|
|
2761
|
+
].join("\n");
|
|
2762
|
+
}
|
|
2763
|
+
var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
|
|
2764
|
+
var init_agentToken = __esm({
|
|
2765
|
+
"src/agentToken.ts"() {
|
|
2766
|
+
"use strict";
|
|
2767
|
+
init_config();
|
|
2768
|
+
STORE_PATH = join8(SWARM_HOME, "agent.json");
|
|
2769
|
+
base = (api) => api.replace(/\/$/, "");
|
|
2770
|
+
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2771
|
+
}
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2774
|
+
// src/linkFailure.ts
|
|
2775
|
+
function describeError(e, maxDepth = 4) {
|
|
2776
|
+
const parts = [];
|
|
2777
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2778
|
+
let cur = e;
|
|
2779
|
+
for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
|
|
2780
|
+
seen.add(cur);
|
|
2781
|
+
const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
|
|
2782
|
+
const code = cur.code;
|
|
2783
|
+
const codeText = code === void 0 || code === null ? "" : String(code);
|
|
2784
|
+
const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
|
|
2785
|
+
if (label && !parts.includes(label)) parts.push(label);
|
|
2786
|
+
cur = cur.cause;
|
|
2787
|
+
}
|
|
2788
|
+
return parts.join(" \u2190 ") || String(e);
|
|
2789
|
+
}
|
|
2790
|
+
function explainLinkFailure(e) {
|
|
2791
|
+
const detail = describeError(e);
|
|
2792
|
+
if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
|
|
2793
|
+
if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
|
|
2794
|
+
if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
|
|
2795
|
+
if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
|
|
2796
|
+
return { kind: "rejected", detail };
|
|
2797
|
+
}
|
|
2798
|
+
var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
|
|
2799
|
+
var init_linkFailure = __esm({
|
|
2800
|
+
"src/linkFailure.ts"() {
|
|
2801
|
+
"use strict";
|
|
2802
|
+
init_agentToken();
|
|
2803
|
+
UNKNOWN_LINK_REASON = "\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B";
|
|
2804
|
+
OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout/i;
|
|
2805
|
+
MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2808
|
+
|
|
2634
2809
|
// src/procList.ts
|
|
2635
2810
|
function parsePsRows(out) {
|
|
2636
2811
|
const rows = [];
|
|
@@ -2952,7 +3127,7 @@ var init_procCleanup = __esm({
|
|
|
2952
3127
|
// src/git.ts
|
|
2953
3128
|
import { existsSync as existsSync9, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
|
|
2954
3129
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
2955
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3130
|
+
import { isAbsolute as isAbsolute2, join as join16, sep as sep2 } from "path";
|
|
2956
3131
|
import { $ as $2 } from "execa";
|
|
2957
3132
|
function isUnrecoverableRefError(e) {
|
|
2958
3133
|
if (e instanceof MissingRefError) return true;
|
|
@@ -2964,7 +3139,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
2964
3139
|
}
|
|
2965
3140
|
function isLinkedWorktree(dir) {
|
|
2966
3141
|
try {
|
|
2967
|
-
return statSync7(
|
|
3142
|
+
return statSync7(join16(dir, ".git")).isFile();
|
|
2968
3143
|
} catch {
|
|
2969
3144
|
return false;
|
|
2970
3145
|
}
|
|
@@ -3074,8 +3249,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3074
3249
|
try {
|
|
3075
3250
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3076
3251
|
if (!commonDir) return false;
|
|
3077
|
-
const infoDir =
|
|
3078
|
-
const excludeFile =
|
|
3252
|
+
const infoDir = join16(commonDir, "info");
|
|
3253
|
+
const excludeFile = join16(infoDir, "exclude");
|
|
3079
3254
|
let current = "";
|
|
3080
3255
|
try {
|
|
3081
3256
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3095,7 +3270,7 @@ async function gitPath(worktree, flag) {
|
|
|
3095
3270
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3096
3271
|
const raw = out.stdout.trim();
|
|
3097
3272
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3098
|
-
return isAbsolute2(raw) ? raw :
|
|
3273
|
+
return isAbsolute2(raw) ? raw : join16(worktree, raw);
|
|
3099
3274
|
}
|
|
3100
3275
|
async function ensureWorktreeHygiene(worktree) {
|
|
3101
3276
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3105,12 +3280,12 @@ async function installGuardHooks(worktree) {
|
|
|
3105
3280
|
try {
|
|
3106
3281
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3107
3282
|
if (!gitDir) return false;
|
|
3108
|
-
const hooksDir =
|
|
3283
|
+
const hooksDir = join16(gitDir, "swarm-hooks");
|
|
3109
3284
|
const prev = await previousHooksPath(worktree);
|
|
3110
3285
|
await mkdir(hooksDir, { recursive: true });
|
|
3111
3286
|
for (const hook of GUARD_HOOKS) {
|
|
3112
|
-
await writeFile(
|
|
3113
|
-
await chmod(
|
|
3287
|
+
await writeFile(join16(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
3288
|
+
await chmod(join16(hooksDir, hook), 493);
|
|
3114
3289
|
}
|
|
3115
3290
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3116
3291
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3129,7 +3304,7 @@ async function previousHooksPath(worktree) {
|
|
|
3129
3304
|
if (out.exitCode === 0 && value) return value;
|
|
3130
3305
|
}
|
|
3131
3306
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3132
|
-
return commonDir ?
|
|
3307
|
+
return commonDir ? join16(commonDir, "hooks") : null;
|
|
3133
3308
|
}
|
|
3134
3309
|
async function removeWorktree(repo, path) {
|
|
3135
3310
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3148,7 +3323,7 @@ async function worktreeDirty(path) {
|
|
|
3148
3323
|
async function repairMovedWorktrees(targetDir) {
|
|
3149
3324
|
const subdirs = (dir) => {
|
|
3150
3325
|
try {
|
|
3151
|
-
return readdirSync10(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) =>
|
|
3326
|
+
return readdirSync10(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join16(dir, d.name));
|
|
3152
3327
|
} catch {
|
|
3153
3328
|
return [];
|
|
3154
3329
|
}
|
|
@@ -3275,18 +3450,18 @@ __export(droneChannel_exports, {
|
|
|
3275
3450
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
3276
3451
|
});
|
|
3277
3452
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3278
|
-
import { chmodSync, mkdirSync as mkdirSync7, readFileSync as
|
|
3453
|
+
import { chmodSync, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
3279
3454
|
import { dirname as dirname3 } from "path";
|
|
3280
3455
|
import { serve } from "@hono/node-server";
|
|
3281
3456
|
function loadChannelSecret(path) {
|
|
3282
3457
|
try {
|
|
3283
|
-
const s =
|
|
3458
|
+
const s = readFileSync14(path, "utf8").trim();
|
|
3284
3459
|
if (s.length >= 32) return s;
|
|
3285
3460
|
} catch {
|
|
3286
3461
|
}
|
|
3287
3462
|
const secret = randomBytes(32).toString("hex");
|
|
3288
3463
|
mkdirSync7(dirname3(path), { recursive: true });
|
|
3289
|
-
|
|
3464
|
+
writeFileSync10(path, secret, { mode: 384 });
|
|
3290
3465
|
try {
|
|
3291
3466
|
chmodSync(path, 384);
|
|
3292
3467
|
} catch {
|
|
@@ -3325,6 +3500,7 @@ function bad(status, error) {
|
|
|
3325
3500
|
function scopeOf(store, run) {
|
|
3326
3501
|
const ids = /* @__PURE__ */ new Set([run.id]);
|
|
3327
3502
|
if (run.reviews_run_id != null) ids.add(run.reviews_run_id);
|
|
3503
|
+
if (run.verifies_run_id != null) ids.add(run.verifies_run_id);
|
|
3328
3504
|
if (run.kind === "hero") for (const r of store.squadRuns(run.id)) ids.add(r.id);
|
|
3329
3505
|
return ids;
|
|
3330
3506
|
}
|
|
@@ -3337,6 +3513,10 @@ function invalidPatchValue(field, v) {
|
|
|
3337
3513
|
return typeof v === "string" && v in STATUS_META ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441: ${String(v)}`;
|
|
3338
3514
|
case "review_verdict":
|
|
3339
3515
|
return v === null || v === "approve" || v === "changes" ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442: ${String(v)}`;
|
|
3516
|
+
// TASWM-444: словарь исходов верификатора — один на весь продукт (swarm-core), поэтому
|
|
3517
|
+
// проверяем его же функцией, а не повторяем литералы третьим списком.
|
|
3518
|
+
case "verify_verdict":
|
|
3519
|
+
return v === null || isVerifyVerdict(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430: ${String(v)}`;
|
|
3340
3520
|
case "review_cycle":
|
|
3341
3521
|
return typeof v === "number" && Number.isInteger(v) && v >= 0 ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0446\u0438\u043A\u043B \u0440\u0435\u0432\u044C\u044E: ${String(v)}`;
|
|
3342
3522
|
case "pr_url":
|
|
@@ -3600,7 +3780,16 @@ var init_droneChannel = __esm({
|
|
|
3600
3780
|
"use strict";
|
|
3601
3781
|
init_src();
|
|
3602
3782
|
LOCAL_CLI_KEY = "cli";
|
|
3603
|
-
PATCHABLE = /* @__PURE__ */ new Set([
|
|
3783
|
+
PATCHABLE = /* @__PURE__ */ new Set([
|
|
3784
|
+
"status",
|
|
3785
|
+
"pr_url",
|
|
3786
|
+
"review_verdict",
|
|
3787
|
+
// TASWM-444: вердикт верификатора — такая же сдача работы, как review_verdict у ревьюера,
|
|
3788
|
+
// и пишется он тем же путём на ПРОВЕРЯЕМЫЙ прогон. Без него роль verifier была немой:
|
|
3789
|
+
// вердикт выносился комментарием на PR, а `done` отбивался каналом.
|
|
3790
|
+
"verify_verdict",
|
|
3791
|
+
"review_cycle"
|
|
3792
|
+
]);
|
|
3604
3793
|
EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
3605
3794
|
ChannelUnavailableError = class extends Error {
|
|
3606
3795
|
constructor(downMs, message) {
|
|
@@ -3616,7 +3805,7 @@ var init_droneChannel = __esm({
|
|
|
3616
3805
|
|
|
3617
3806
|
// src/attachments.ts
|
|
3618
3807
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3619
|
-
import { join as
|
|
3808
|
+
import { join as join19 } from "path";
|
|
3620
3809
|
function attachmentsFromDoc(doc, origin) {
|
|
3621
3810
|
const out = [];
|
|
3622
3811
|
const walk2 = (node) => {
|
|
@@ -3707,7 +3896,7 @@ async function materializeAttachments(opts) {
|
|
|
3707
3896
|
};
|
|
3708
3897
|
const result = { files: [], skipped: [] };
|
|
3709
3898
|
if (opts.refs.length === 0) return result;
|
|
3710
|
-
const dir =
|
|
3899
|
+
const dir = join19(opts.worktree, ATTACHMENTS_DIR);
|
|
3711
3900
|
await io.removeDir(dir);
|
|
3712
3901
|
let dirReady = false;
|
|
3713
3902
|
let total = 0;
|
|
@@ -3753,7 +3942,7 @@ async function materializeAttachments(opts) {
|
|
|
3753
3942
|
await io.mkdir(dir);
|
|
3754
3943
|
dirReady = true;
|
|
3755
3944
|
}
|
|
3756
|
-
await io.writeFile(
|
|
3945
|
+
await io.writeFile(join19(dir, name), file.bytes);
|
|
3757
3946
|
} catch (e) {
|
|
3758
3947
|
result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
3759
3948
|
continue;
|
|
@@ -3906,7 +4095,7 @@ var init_attachments = __esm({
|
|
|
3906
4095
|
|
|
3907
4096
|
// src/shotUpload.ts
|
|
3908
4097
|
import { readFile as fsReadFile } from "fs/promises";
|
|
3909
|
-
import { basename as basename2, isAbsolute as isAbsolute3, join as
|
|
4098
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join20 } from "path";
|
|
3910
4099
|
function shotMimetype(path) {
|
|
3911
4100
|
const dot = path.lastIndexOf(".");
|
|
3912
4101
|
if (dot < 0) return null;
|
|
@@ -3932,7 +4121,7 @@ async function readShots(paths, opts) {
|
|
|
3932
4121
|
}
|
|
3933
4122
|
let bytes;
|
|
3934
4123
|
try {
|
|
3935
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
4124
|
+
bytes = await read(isAbsolute3(path) ? path : join20(opts.worktree, path));
|
|
3936
4125
|
} catch (e) {
|
|
3937
4126
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
3938
4127
|
continue;
|
|
@@ -4010,15 +4199,15 @@ var init_shotUpload = __esm({
|
|
|
4010
4199
|
|
|
4011
4200
|
// src/oauth.ts
|
|
4012
4201
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
4013
|
-
import { readFileSync as
|
|
4014
|
-
import { join as
|
|
4202
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
|
|
4203
|
+
import { join as join21 } from "path";
|
|
4015
4204
|
function makePkce() {
|
|
4016
4205
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4017
4206
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
4018
4207
|
return { verifier, challenge };
|
|
4019
4208
|
}
|
|
4020
4209
|
async function registerClient(args, fetchFn = fetch) {
|
|
4021
|
-
const res = await fetchFn(`${
|
|
4210
|
+
const res = await fetchFn(`${base2(args.api)}${REGISTER}`, {
|
|
4022
4211
|
method: "POST",
|
|
4023
4212
|
headers: { "Content-Type": "application/json" },
|
|
4024
4213
|
body: JSON.stringify({
|
|
@@ -4033,7 +4222,7 @@ async function registerClient(args, fetchFn = fetch) {
|
|
|
4033
4222
|
return j.client_id;
|
|
4034
4223
|
}
|
|
4035
4224
|
function authorizeUrl(args) {
|
|
4036
|
-
const u = new URL(`${
|
|
4225
|
+
const u = new URL(`${base2(args.api)}${AUTHORIZE}`);
|
|
4037
4226
|
u.searchParams.set("response_type", "code");
|
|
4038
4227
|
u.searchParams.set("client_id", args.clientId);
|
|
4039
4228
|
u.searchParams.set("redirect_uri", args.redirectUri);
|
|
@@ -4044,7 +4233,7 @@ function authorizeUrl(args) {
|
|
|
4044
4233
|
return u.toString();
|
|
4045
4234
|
}
|
|
4046
4235
|
async function postToken(api, params, fetchFn) {
|
|
4047
|
-
const res = await fetchFn(`${
|
|
4236
|
+
const res = await fetchFn(`${base2(api)}${TOKEN}`, {
|
|
4048
4237
|
method: "POST",
|
|
4049
4238
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4050
4239
|
body: new URLSearchParams(params).toString()
|
|
@@ -4095,23 +4284,23 @@ function toStore(clientId, t, now) {
|
|
|
4095
4284
|
}
|
|
4096
4285
|
function loadStore() {
|
|
4097
4286
|
try {
|
|
4098
|
-
return JSON.parse(
|
|
4287
|
+
return JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
|
|
4099
4288
|
} catch {
|
|
4100
4289
|
return null;
|
|
4101
4290
|
}
|
|
4102
4291
|
}
|
|
4103
4292
|
function saveStore(s) {
|
|
4104
|
-
|
|
4293
|
+
writeFileSync11(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
4105
4294
|
}
|
|
4106
4295
|
function clearStore() {
|
|
4107
4296
|
try {
|
|
4108
|
-
|
|
4297
|
+
unlinkSync4(STORE_PATH2);
|
|
4109
4298
|
} catch {
|
|
4110
4299
|
}
|
|
4111
4300
|
}
|
|
4112
4301
|
function loadClientId() {
|
|
4113
4302
|
try {
|
|
4114
|
-
return JSON.parse(
|
|
4303
|
+
return JSON.parse(readFileSync15(CLIENT_PATH, "utf8")).clientId;
|
|
4115
4304
|
} catch {
|
|
4116
4305
|
return null;
|
|
4117
4306
|
}
|
|
@@ -4120,7 +4309,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
4120
4309
|
const existing = loadClientId();
|
|
4121
4310
|
if (existing) return existing;
|
|
4122
4311
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
4123
|
-
|
|
4312
|
+
writeFileSync11(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
4124
4313
|
return clientId;
|
|
4125
4314
|
}
|
|
4126
4315
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -4137,7 +4326,7 @@ async function ensureAccess(store, opts = {}) {
|
|
|
4137
4326
|
return null;
|
|
4138
4327
|
}
|
|
4139
4328
|
}
|
|
4140
|
-
var REGISTER, AUTHORIZE, TOKEN, SCOPE,
|
|
4329
|
+
var REGISTER, AUTHORIZE, TOKEN, SCOPE, STORE_PATH2, CLIENT_PATH, base2;
|
|
4141
4330
|
var init_oauth = __esm({
|
|
4142
4331
|
"src/oauth.ts"() {
|
|
4143
4332
|
"use strict";
|
|
@@ -4146,64 +4335,45 @@ var init_oauth = __esm({
|
|
|
4146
4335
|
AUTHORIZE = "/api/oauth/authorize";
|
|
4147
4336
|
TOKEN = "/api/oauth/token";
|
|
4148
4337
|
SCOPE = "openid profile email taskless:full";
|
|
4149
|
-
|
|
4150
|
-
CLIENT_PATH =
|
|
4151
|
-
|
|
4338
|
+
STORE_PATH2 = join21(SWARM_HOME, "oauth.json");
|
|
4339
|
+
CLIENT_PATH = join21(SWARM_HOME, "oauth-client.json");
|
|
4340
|
+
base2 = (api) => api.replace(/\/$/, "");
|
|
4152
4341
|
}
|
|
4153
4342
|
});
|
|
4154
4343
|
|
|
4155
|
-
// src/
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
}
|
|
4344
|
+
// src/authState.ts
|
|
4345
|
+
function noteTokenRejected(nowMs = Date.now()) {
|
|
4346
|
+
if (!denial) {
|
|
4347
|
+
denial = { since: nowMs, lastShoutAt: nowMs, count: 1 };
|
|
4348
|
+
return revokedTokenComplaint();
|
|
4349
|
+
}
|
|
4350
|
+
denial.count += 1;
|
|
4351
|
+
if (nowMs - denial.lastShoutAt < SHOUT_EVERY_MS) return null;
|
|
4352
|
+
denial.lastShoutAt = nowMs;
|
|
4353
|
+
return `${revokedTokenComplaint()}
|
|
4354
|
+
${deniedSinceLine(nowMs)}
|
|
4355
|
+
`;
|
|
4165
4356
|
}
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
const res = await fetchFn(`${base2(args.api)}/api/auth/me`, {
|
|
4169
|
-
headers: { Authorization: `Bearer ${args.token}` }
|
|
4170
|
-
});
|
|
4171
|
-
if (res.status === 401 || res.status === 403) return "revoked";
|
|
4172
|
-
return res.ok ? "ok" : "unreachable";
|
|
4173
|
-
} catch {
|
|
4174
|
-
return "unreachable";
|
|
4175
|
-
}
|
|
4357
|
+
function noteTokenAccepted() {
|
|
4358
|
+
denial = null;
|
|
4176
4359
|
}
|
|
4177
|
-
function
|
|
4178
|
-
if (
|
|
4179
|
-
|
|
4180
|
-
return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
|
|
4360
|
+
function authStateLine(nowMs = Date.now()) {
|
|
4361
|
+
if (!denial) return null;
|
|
4362
|
+
return `\u0414\u041E\u0421\u0422\u0423\u041F \u041A TASKLESS \u041E\u0422\u041E\u0417\u0412\u0410\u041D \u2014 ${deniedSinceLine(nowMs)}. \u041F\u043E\u0447\u0438\u043D\u0438 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: swarm login`;
|
|
4181
4363
|
}
|
|
4182
|
-
function
|
|
4183
|
-
|
|
4364
|
+
function deniedSinceLine(nowMs) {
|
|
4365
|
+
const d = denial;
|
|
4366
|
+
if (!d) return "";
|
|
4367
|
+
const min = Math.max(1, Math.round((nowMs - d.since) / 6e4));
|
|
4368
|
+
return `\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0442\u043E\u043A\u0435\u043D ${min} \u043C\u0438\u043D, \u043E\u0442\u043A\u0430\u0437\u043E\u0432 ${d.count}`;
|
|
4184
4369
|
}
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
"\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
4189
|
-
"\u2502 \u0422\u041E\u041A\u0415\u041D \u0420\u041E\u042F \u041D\u0415 \u041F\u0420\u0418\u041D\u042F\u0422 (401). \u0414\u043E\u0441\u0442\u0443\u043F \u043A Taskless \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D. \u2502",
|
|
4190
|
-
"\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
|
|
4191
|
-
"\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u2014 \u043E\u0434\u043D\u0430 \u0438\u0437 \u0434\u0432\u0443\u0445, \u043E\u0431\u0435 \u0434\u0435\u043B\u0430\u044E\u0442\u0441\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435:",
|
|
4192
|
-
" \u2022 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439) \u2014 \u0442\u043E\u043A\u0435\u043D \u043E\u0442\u043E\u0437\u0432\u0430\u043D;",
|
|
4193
|
-
" \u2022 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C \u2014 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C.",
|
|
4194
|
-
"",
|
|
4195
|
-
"\u0414\u0435\u043C\u043E\u043D \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0438 \u043E\u0442\u0447\u0451\u0442\u044B",
|
|
4196
|
-
"\u043D\u0435 \u043F\u0438\u0448\u0443\u0442\u0441\u044F. \u041F\u043E\u0447\u0438\u043D\u0438 \u0432\u0445\u043E\u0434\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E: swarm login",
|
|
4197
|
-
""
|
|
4198
|
-
].join("\n");
|
|
4199
|
-
}
|
|
4200
|
-
var STORE_PATH2, base2;
|
|
4201
|
-
var init_agentToken = __esm({
|
|
4202
|
-
"src/agentToken.ts"() {
|
|
4370
|
+
var SHOUT_EVERY_MS, denial;
|
|
4371
|
+
var init_authState = __esm({
|
|
4372
|
+
"src/authState.ts"() {
|
|
4203
4373
|
"use strict";
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4374
|
+
init_agentToken();
|
|
4375
|
+
SHOUT_EVERY_MS = 10 * 6e4;
|
|
4376
|
+
denial = null;
|
|
4207
4377
|
}
|
|
4208
4378
|
});
|
|
4209
4379
|
|
|
@@ -4244,6 +4414,7 @@ __export(taskless_exports, {
|
|
|
4244
4414
|
extractText: () => extractText,
|
|
4245
4415
|
fetchTaskContext: () => fetchTaskContext,
|
|
4246
4416
|
isClosingTaskCategory: () => isClosingTaskCategory,
|
|
4417
|
+
lastSwarmConnectFailure: () => lastSwarmConnectFailure,
|
|
4247
4418
|
makeFileFetcher: () => makeFileFetcher,
|
|
4248
4419
|
makeTasklessClient: () => makeTasklessClient,
|
|
4249
4420
|
parseWorkspaces: () => parseWorkspaces,
|
|
@@ -4588,12 +4759,14 @@ async function preflightAuth(opts = {}) {
|
|
|
4588
4759
|
if (!agent?.token) return true;
|
|
4589
4760
|
const probe = await probeAgentToken({ api, token: agent.token }, opts.fetchFn ?? fetch);
|
|
4590
4761
|
if (probe === "revoked") {
|
|
4591
|
-
out(revokedTokenComplaint());
|
|
4762
|
+
out(noteTokenRejected() ?? revokedTokenComplaint());
|
|
4592
4763
|
return false;
|
|
4593
4764
|
}
|
|
4594
4765
|
if (probe === "unreachable") {
|
|
4595
4766
|
out("[taskless] \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D) \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u044E, \u043D\u043E \u0441\u0432\u044F\u0437\u0438 \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0431\u044B\u0442\u044C.");
|
|
4767
|
+
return true;
|
|
4596
4768
|
}
|
|
4769
|
+
noteTokenAccepted();
|
|
4597
4770
|
return true;
|
|
4598
4771
|
}
|
|
4599
4772
|
async function connectTaskless(cfg) {
|
|
@@ -4610,11 +4783,15 @@ async function connectTaskless(cfg) {
|
|
|
4610
4783
|
try {
|
|
4611
4784
|
await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
|
|
4612
4785
|
} catch (e) {
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4786
|
+
if (isRevokedTokenError(e)) {
|
|
4787
|
+
const shout = noteTokenRejected();
|
|
4788
|
+
if (shout) console.error(shout);
|
|
4789
|
+
} else {
|
|
4790
|
+
console.warn(`Taskless: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${describeError(e)}) \u2192 slug-only. \u0412\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E.`);
|
|
4791
|
+
}
|
|
4616
4792
|
return null;
|
|
4617
4793
|
}
|
|
4794
|
+
noteTokenAccepted();
|
|
4618
4795
|
const call = async (name, args) => await client.callTool({ name, arguments: args });
|
|
4619
4796
|
return makeTasklessClient(
|
|
4620
4797
|
call,
|
|
@@ -4627,27 +4804,41 @@ async function connectSwarmMcp(cfg) {
|
|
|
4627
4804
|
const api = cfg?.api ?? TASKLESS_API;
|
|
4628
4805
|
const workspace = cfg?.workspace ?? resolvedWorkspaceId();
|
|
4629
4806
|
const token = await resolveApiToken(api, cfg?.token);
|
|
4630
|
-
if (!token)
|
|
4807
|
+
if (!token) {
|
|
4808
|
+
swarmConnectFailure = "\u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u0430 \u0440\u043E\u044F \u2014 \u043D\u0443\u0436\u0435\u043D \u0432\u0445\u043E\u0434: swarm login";
|
|
4809
|
+
return null;
|
|
4810
|
+
}
|
|
4631
4811
|
const url = new URL(mcpEndpoint(api));
|
|
4632
4812
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
4633
4813
|
requestInit: { headers: { Authorization: `Bearer ${token}` } }
|
|
4634
4814
|
});
|
|
4635
4815
|
const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
|
|
4636
4816
|
try {
|
|
4637
|
-
await client.connect(transport);
|
|
4817
|
+
await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
|
|
4638
4818
|
} catch (e) {
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4819
|
+
if (isRevokedTokenError(e)) {
|
|
4820
|
+
swarmConnectFailure = revokedTokenLine();
|
|
4821
|
+
const shout = noteTokenRejected();
|
|
4822
|
+
if (shout) console.error(`${shout}
|
|
4823
|
+
\u0421\u043E\u0431\u044B\u0442\u0438\u044F \u043F\u043E\u043A\u0430 \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox \u0438 \u0443\u0435\u0434\u0443\u0442, \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u0441\u0442\u0443\u043F \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.`);
|
|
4824
|
+
} else {
|
|
4825
|
+
swarmConnectFailure = describeError(e);
|
|
4826
|
+
console.warn(`Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${swarmConnectFailure}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`);
|
|
4827
|
+
}
|
|
4642
4828
|
return null;
|
|
4643
4829
|
}
|
|
4830
|
+
swarmConnectFailure = null;
|
|
4831
|
+
noteTokenAccepted();
|
|
4644
4832
|
return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
|
|
4645
4833
|
}
|
|
4834
|
+
function lastSwarmConnectFailure() {
|
|
4835
|
+
return swarmConnectFailure;
|
|
4836
|
+
}
|
|
4646
4837
|
function swarmToolArgs(name, args, workspace) {
|
|
4647
4838
|
if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
|
|
4648
4839
|
return { workspaceId: workspace, ...args };
|
|
4649
4840
|
}
|
|
4650
|
-
var SLUG_RE, WS_FREE_TOOLS, TaskBodyUnavailableError, TASK_CONTEXT_MAX_DOCS, TASK_CONTEXT_MAX_DOC_CHARS, TASK_CONTEXT_MAX_COMMENTS, TASK_CONTEXT_MAX_COMMENT_CHARS, UNTRUSTED_TASK_INPUT_NOTICE, TASKLESS_CONNECT_TIMEOUT_MS, FILE_FETCH_TIMEOUT_MS, FILE_FETCH_MAX_REDIRECTS, legacyWarned, WS_SCOPED_SWARM_TOOLS;
|
|
4841
|
+
var SLUG_RE, WS_FREE_TOOLS, TaskBodyUnavailableError, TASK_CONTEXT_MAX_DOCS, TASK_CONTEXT_MAX_DOC_CHARS, TASK_CONTEXT_MAX_COMMENTS, TASK_CONTEXT_MAX_COMMENT_CHARS, UNTRUSTED_TASK_INPUT_NOTICE, TASKLESS_CONNECT_TIMEOUT_MS, FILE_FETCH_TIMEOUT_MS, FILE_FETCH_MAX_REDIRECTS, legacyWarned, swarmConnectFailure, WS_SCOPED_SWARM_TOOLS;
|
|
4651
4842
|
var init_taskless = __esm({
|
|
4652
4843
|
"src/taskless.ts"() {
|
|
4653
4844
|
"use strict";
|
|
@@ -4657,6 +4848,8 @@ var init_taskless = __esm({
|
|
|
4657
4848
|
init_config();
|
|
4658
4849
|
init_oauth();
|
|
4659
4850
|
init_agentToken();
|
|
4851
|
+
init_linkFailure();
|
|
4852
|
+
init_authState();
|
|
4660
4853
|
init_workspace();
|
|
4661
4854
|
SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
|
|
4662
4855
|
WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
|
|
@@ -4687,6 +4880,7 @@ var init_taskless = __esm({
|
|
|
4687
4880
|
FILE_FETCH_TIMEOUT_MS = 15e3;
|
|
4688
4881
|
FILE_FETCH_MAX_REDIRECTS = 3;
|
|
4689
4882
|
legacyWarned = false;
|
|
4883
|
+
swarmConnectFailure = null;
|
|
4690
4884
|
WS_SCOPED_SWARM_TOOLS = /* @__PURE__ */ new Set(["swarm_run_upsert", "get_task"]);
|
|
4691
4885
|
}
|
|
4692
4886
|
});
|
|
@@ -4748,6 +4942,10 @@ function rowToRun(row) {
|
|
|
4748
4942
|
resolves_run_id: row.resolves_run_id ?? null,
|
|
4749
4943
|
reviews_run_id: row.reviews_run_id ?? null,
|
|
4750
4944
|
review_verdict: row.review_verdict ?? null,
|
|
4945
|
+
verifies_run_id: row.verifies_run_id ?? null,
|
|
4946
|
+
// TASWM-344: через узкий парсер, а не сырой строкой — в зеркале может лежать значение,
|
|
4947
|
+
// записанное демоном другой версии; непонятное здесь значит «вердикта нет».
|
|
4948
|
+
verify_verdict: toVerifyVerdict(row.verify_verdict),
|
|
4751
4949
|
review_phase: row.review_phase ?? null,
|
|
4752
4950
|
// вычисляемая фаза ревью (TASWM-230)
|
|
4753
4951
|
review_cycle: row.review_cycle ?? 0,
|
|
@@ -4816,6 +5014,8 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4816
5014
|
resolves_run_id INTEGER,
|
|
4817
5015
|
reviews_run_id INTEGER,
|
|
4818
5016
|
review_verdict TEXT,
|
|
5017
|
+
verifies_run_id INTEGER,
|
|
5018
|
+
verify_verdict TEXT,
|
|
4819
5019
|
review_cycle INTEGER NOT NULL DEFAULT 0,
|
|
4820
5020
|
task_title TEXT,
|
|
4821
5021
|
task_desc TEXT,
|
|
@@ -4892,6 +5092,12 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4892
5092
|
// вердикт (approve|changes), который ревьюер пишет на проверяемый прогон.
|
|
4893
5093
|
"ALTER TABLE runs ADD COLUMN reviews_run_id INTEGER",
|
|
4894
5094
|
"ALTER TABLE runs ADD COLUMN review_verdict TEXT",
|
|
5095
|
+
// Верификатор (TASWM-344): прогон, который пытается ОПРОВЕРГНУТЬ работу
|
|
5096
|
+
// #verifies_run_id, и его вердикт (confirmed|not_reproduced|found_issues) на
|
|
5097
|
+
// проверяемом прогоне. Своя пара колонок, а не переиспользование reviews_*:
|
|
5098
|
+
// ревью и верификация идут на одном PR одновременно и считаются раздельно.
|
|
5099
|
+
"ALTER TABLE runs ADD COLUMN verifies_run_id INTEGER",
|
|
5100
|
+
"ALTER TABLE runs ADD COLUMN verify_verdict TEXT",
|
|
4895
5101
|
// Авто-ревью v2 (TASWM-162): номер цикла ревью — пере-ревью после фикс-итерации.
|
|
4896
5102
|
"ALTER TABLE runs ADD COLUMN review_cycle INTEGER NOT NULL DEFAULT 0",
|
|
4897
5103
|
// Вычисляемая фаза ревью (TASWM-230): человеко-фраза «чего ждёт PR», её считает демон.
|
|
@@ -4928,7 +5134,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4928
5134
|
createRun(input) {
|
|
4929
5135
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
4930
5136
|
const res = this.db.prepare(
|
|
4931
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
5137
|
+
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4932
5138
|
).run(
|
|
4933
5139
|
input.task_slug,
|
|
4934
5140
|
name,
|
|
@@ -4946,6 +5152,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4946
5152
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
4947
5153
|
input.resolves_run_id ?? null,
|
|
4948
5154
|
input.reviews_run_id ?? null,
|
|
5155
|
+
input.verifies_run_id ?? null,
|
|
5156
|
+
// прогон-верификатор (TASWM-344); обычный прогон — null
|
|
4949
5157
|
input.review_cycle ?? 0,
|
|
4950
5158
|
// цикл ревью для ревьюера (авто-ревью v2, TASWM-162); обычный прогон — 0
|
|
4951
5159
|
input.priority ?? 0,
|
|
@@ -4956,7 +5164,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4956
5164
|
// воркспейс задачи прогона (TASWM-288); null → глобальный пин
|
|
4957
5165
|
);
|
|
4958
5166
|
const id = Number(res.lastInsertRowid);
|
|
4959
|
-
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
5167
|
+
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
4960
5168
|
this.addEvent(id, "spawned", spawnNote);
|
|
4961
5169
|
return this.getRun(id);
|
|
4962
5170
|
}
|
|
@@ -4982,13 +5190,14 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4982
5190
|
*/
|
|
4983
5191
|
ensureRun(input) {
|
|
4984
5192
|
this.db.prepare(
|
|
4985
|
-
"INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?)"
|
|
5193
|
+
"INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, verifies_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?, ?)"
|
|
4986
5194
|
).run(
|
|
4987
5195
|
input.id,
|
|
4988
5196
|
input.task_slug,
|
|
4989
5197
|
input.kind ?? "drone",
|
|
4990
5198
|
input.name || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)",
|
|
4991
5199
|
input.reviews_run_id ?? null,
|
|
5200
|
+
input.verifies_run_id ?? null,
|
|
4992
5201
|
input.hero_run_id ?? null
|
|
4993
5202
|
);
|
|
4994
5203
|
}
|
|
@@ -5133,6 +5342,31 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5133
5342
|
const rows = this.db.prepare(`SELECT * FROM runs WHERE reviews_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(reviewRunId);
|
|
5134
5343
|
return rows.map(rowToRun);
|
|
5135
5344
|
}
|
|
5345
|
+
/**
|
|
5346
|
+
* ВЕРИФИКАЦИЯ (TASWM-344) — те же три счётчика, что у ревью выше, но по своей связи
|
|
5347
|
+
* `verifies_run_id` и со своим капом. Скоуп «текущий цикл ревью» тот же и по той же
|
|
5348
|
+
* причине: фикс-итерация бампит review_cycle и обнуляет вердикты (reviewTransitionPatch),
|
|
5349
|
+
* значит исправленную работу обязан проверить СВЕЖИЙ верификатор, а не считаться уже
|
|
5350
|
+
* полученным подтверждением по коду, которого больше нет.
|
|
5351
|
+
*/
|
|
5352
|
+
verifierCountFor(verifyRunId) {
|
|
5353
|
+
const row = this.db.prepare(
|
|
5354
|
+
"SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status NOT IN ('failed','stalled','stopped','superseded') AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
|
|
5355
|
+
).get(verifyRunId, verifyRunId);
|
|
5356
|
+
return row.n;
|
|
5357
|
+
}
|
|
5358
|
+
/** Плоский кап попыток верификации на цикл — любой статус, кроме сброшенных оператором. */
|
|
5359
|
+
verifierAttemptCountFor(verifyRunId) {
|
|
5360
|
+
const row = this.db.prepare(
|
|
5361
|
+
"SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status != 'superseded' AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
|
|
5362
|
+
).get(verifyRunId, verifyRunId);
|
|
5363
|
+
return row.n;
|
|
5364
|
+
}
|
|
5365
|
+
/** Нетерминальные верификаторы прогона — закрыть зависших, когда PR уже смержен/закрыт. */
|
|
5366
|
+
activeVerifiersFor(verifyRunId) {
|
|
5367
|
+
const rows = this.db.prepare(`SELECT * FROM runs WHERE verifies_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(verifyRunId);
|
|
5368
|
+
return rows.map(rowToRun);
|
|
5369
|
+
}
|
|
5136
5370
|
/**
|
|
5137
5371
|
* Последний живой (не терминальный) прогон по slug — для swarm cancel. Служебные
|
|
5138
5372
|
* сабраны (reviews_run_id/resolves_run_id — ревьюер/резолвер, TASWM-273) исключены:
|
|
@@ -5147,7 +5381,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5147
5381
|
*/
|
|
5148
5382
|
activeRunForSlug(slug) {
|
|
5149
5383
|
const row = this.db.prepare(
|
|
5150
|
-
`SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL ORDER BY id DESC LIMIT 1`
|
|
5384
|
+
`SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id DESC LIMIT 1`
|
|
5151
5385
|
).get(slug);
|
|
5152
5386
|
return row ? rowToRun(row) : null;
|
|
5153
5387
|
}
|
|
@@ -5187,7 +5421,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5187
5421
|
if (!depends_on || depends_on.length === 0) return [];
|
|
5188
5422
|
const slugs = [...new Set(depends_on)];
|
|
5189
5423
|
const rows = this.db.prepare(
|
|
5190
|
-
`SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL ORDER BY id`
|
|
5424
|
+
`SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id`
|
|
5191
5425
|
).all(...slugs);
|
|
5192
5426
|
const bySlug = /* @__PURE__ */ new Map();
|
|
5193
5427
|
for (const r of rows) {
|
|
@@ -5935,7 +6169,16 @@ var SwarmClient = class _SwarmClient {
|
|
|
5935
6169
|
static REJECT_LIMIT = 5;
|
|
5936
6170
|
static BACKOFF_BASE = 500;
|
|
5937
6171
|
static BACKOFF_MAX = 3e4;
|
|
6172
|
+
// TASWM-325: момент УДАЧНОГО пульса — «когда прод последний раз слышал эту машину»
|
|
6173
|
+
// (прод пишет SwarmHost.lastSeenAt ровно на swarm_heartbeat). Единственное, что про
|
|
6174
|
+
// связь знает КЛИЕНТ: жива ли она вообще — знает транспорт (mcpResilient.link), а
|
|
6175
|
+
// отвергнут ли токен — authState. Здесь их не угадываем: первая редакция считала
|
|
6176
|
+
// успехом любой возврат call и на боевом каллере (который не бросает, а возвращает
|
|
6177
|
+
// isError) всегда показывала «связь есть».
|
|
6178
|
+
heartbeatOkAt = null;
|
|
6179
|
+
now;
|
|
5938
6180
|
constructor(opts) {
|
|
6181
|
+
this.now = opts.now ?? Date.now;
|
|
5939
6182
|
this.call = opts.call;
|
|
5940
6183
|
this.defaultRepo = normalizeRepo(opts.defaultRepo ?? "");
|
|
5941
6184
|
this.autoFlush = opts.autoFlush ?? true;
|
|
@@ -6078,6 +6321,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6078
6321
|
if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
|
|
6079
6322
|
if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
|
|
6080
6323
|
if ("review_verdict" in patch) out.reviewVerdict = patch.review_verdict ?? null;
|
|
6324
|
+
if ("verify_verdict" in patch) out.verifyVerdict = patch.verify_verdict ?? null;
|
|
6081
6325
|
if ("host_id" in patch && patch.host_id) {
|
|
6082
6326
|
out.hostId = patch.host_id;
|
|
6083
6327
|
out.hostPinned = patch.host_pinned === true;
|
|
@@ -6088,6 +6332,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6088
6332
|
this.setRunRefField(out, "resolvesRunId", patch, "resolves_run_id");
|
|
6089
6333
|
this.setRunRefField(out, "supersededById", patch, "superseded_by");
|
|
6090
6334
|
this.setRunRefField(out, "reviewsRunId", patch, "reviews_run_id");
|
|
6335
|
+
this.setRunRefField(out, "verifiesRunId", patch, "verifies_run_id");
|
|
6091
6336
|
return out;
|
|
6092
6337
|
}
|
|
6093
6338
|
// Положить self-ref прогона в прод-патч: локальный number → отложенная ссылка
|
|
@@ -6150,6 +6395,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
6150
6395
|
resolves_run_id: input.resolves_run_id ?? null,
|
|
6151
6396
|
reviews_run_id: input.reviews_run_id ?? null,
|
|
6152
6397
|
review_verdict: null,
|
|
6398
|
+
verifies_run_id: input.verifies_run_id ?? null,
|
|
6399
|
+
// прогон-верификатор (TASWM-344)
|
|
6400
|
+
verify_verdict: null,
|
|
6153
6401
|
review_phase: null,
|
|
6154
6402
|
// вычисляемая фаза ревью (TASWM-230) — демон посчитает на review-тике
|
|
6155
6403
|
review_cycle: input.review_cycle ?? 0,
|
|
@@ -6201,6 +6449,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
6201
6449
|
// резолвер конфликта → связь на review-прогон
|
|
6202
6450
|
reviews_run_id: d.reviews_run_id,
|
|
6203
6451
|
// ревьюер (авто-ревью, TASWM-148) → связь на review-прогон
|
|
6452
|
+
verifies_run_id: d.verifies_run_id,
|
|
6453
|
+
// верификатор (TASWM-344) → связь на проверяемый прогон
|
|
6204
6454
|
host_id: d.host_id,
|
|
6205
6455
|
// машина прогона (TASWM-281) — своя с рождения либо заданная --host
|
|
6206
6456
|
host_pinned: d.host_pinned,
|
|
@@ -6208,7 +6458,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6208
6458
|
base_branch: d.base_branch
|
|
6209
6459
|
// базовая ветка PR (TASWM-285), если задана на спавне
|
|
6210
6460
|
}));
|
|
6211
|
-
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
6461
|
+
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
6212
6462
|
this.addEvent(id, "spawned", spawnNote);
|
|
6213
6463
|
return this.mat(d);
|
|
6214
6464
|
}
|
|
@@ -6312,6 +6562,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
6312
6562
|
activeReviewersFor(reviewRunId) {
|
|
6313
6563
|
return [...this.runs.values()].filter((r) => r.reviews_run_id === reviewRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
|
|
6314
6564
|
}
|
|
6565
|
+
// Верификация (TASWM-344) — зеркало трёх счётчиков ревью выше по своей связи
|
|
6566
|
+
// verifies_run_id; правила «кто не в счёт» те же (см. db.ts:verifierCountFor).
|
|
6567
|
+
verifierCountFor(verifyRunId) {
|
|
6568
|
+
const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
|
|
6569
|
+
const dead = /* @__PURE__ */ new Set(["failed", "stalled", "stopped", "superseded"]);
|
|
6570
|
+
return [...this.runs.values()].filter(
|
|
6571
|
+
(r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && !dead.has(r.status)
|
|
6572
|
+
).length;
|
|
6573
|
+
}
|
|
6574
|
+
verifierAttemptCountFor(verifyRunId) {
|
|
6575
|
+
const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
|
|
6576
|
+
return [...this.runs.values()].filter(
|
|
6577
|
+
(r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && r.status !== "superseded"
|
|
6578
|
+
).length;
|
|
6579
|
+
}
|
|
6580
|
+
activeVerifiersFor(verifyRunId) {
|
|
6581
|
+
return [...this.runs.values()].filter((r) => r.verifies_run_id === verifyRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
|
|
6582
|
+
}
|
|
6315
6583
|
activeRunForSlug(slug) {
|
|
6316
6584
|
const cands = [...this.runs.values()].filter((r) => r.task_slug === slug && !SLUG_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
|
|
6317
6585
|
return cands.length ? this.mat(cands[0]) : null;
|
|
@@ -6347,7 +6615,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
6347
6615
|
if (!depends_on || depends_on.length === 0) return [];
|
|
6348
6616
|
const doneInMirror = /* @__PURE__ */ new Set();
|
|
6349
6617
|
for (const r of this.runs.values()) {
|
|
6350
|
-
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null
|
|
6618
|
+
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null) {
|
|
6619
|
+
doneInMirror.add(r.task_slug);
|
|
6620
|
+
}
|
|
6351
6621
|
}
|
|
6352
6622
|
return depends_on.map((slug) => {
|
|
6353
6623
|
if (doneInMirror.has(slug)) {
|
|
@@ -6393,7 +6663,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6393
6663
|
// Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
|
|
6394
6664
|
// не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
|
|
6395
6665
|
// с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
|
|
6396
|
-
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
|
|
6666
|
+
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId) || Boolean(r.verifiesRunId),
|
|
6397
6667
|
// В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
|
|
6398
6668
|
// запасной вариант ради фикстур и старых ответов.
|
|
6399
6669
|
name: r.droneName ?? r.name ?? null,
|
|
@@ -6609,7 +6879,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6609
6879
|
return this.eventsLog.filter((e) => {
|
|
6610
6880
|
if (e.kind !== "failed" || e.run_id == null || e.at < cutoff) return false;
|
|
6611
6881
|
const r = this.runs.get(e.run_id);
|
|
6612
|
-
return !!r && r.resolves_run_id == null && r.reviews_run_id == null;
|
|
6882
|
+
return !!r && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null;
|
|
6613
6883
|
}).length;
|
|
6614
6884
|
}
|
|
6615
6885
|
recordStormProbe(probe, ext, reason, resetHint) {
|
|
@@ -6734,7 +7004,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6734
7004
|
const workerWt = droneWorktreePath(opts.worktreesDir, d.repo || this.defaultRepo, d.task_slug);
|
|
6735
7005
|
let matchWt = null;
|
|
6736
7006
|
let matchPid = null;
|
|
6737
|
-
if (d.resolves_run_id == null && d.reviews_run_id == null) {
|
|
7007
|
+
if (d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null) {
|
|
6738
7008
|
const p = pidByWorktree.get(workerWt);
|
|
6739
7009
|
if (p != null) {
|
|
6740
7010
|
matchWt = workerWt;
|
|
@@ -6750,9 +7020,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
6750
7020
|
}
|
|
6751
7021
|
}
|
|
6752
7022
|
} else {
|
|
6753
|
-
const
|
|
7023
|
+
const checkedId = d.reviews_run_id ?? d.verifies_run_id;
|
|
7024
|
+
const reviewed = this.runs.get(checkedId);
|
|
6754
7025
|
const reviewedWt = reviewed ? droneWorktreePath(opts.worktreesDir, reviewed.repo || this.defaultRepo, reviewed.task_slug) : workerWt;
|
|
6755
|
-
const prefix = `${reviewedWt}-review-`;
|
|
7026
|
+
const prefix = d.reviews_run_id != null ? `${reviewedWt}-review-` : `${reviewedWt}-verify-`;
|
|
6756
7027
|
for (const [wt, p] of pidByWorktree) {
|
|
6757
7028
|
if (wt.startsWith(prefix)) {
|
|
6758
7029
|
matchWt = wt;
|
|
@@ -6765,7 +7036,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6765
7036
|
this.runtime.set(d.id, { pid: matchPid, worktree_path: matchWt });
|
|
6766
7037
|
alive++;
|
|
6767
7038
|
} else {
|
|
6768
|
-
const wt = d.resolves_run_id == null && d.reviews_run_id == null && wtExists(workerWt) ? workerWt : null;
|
|
7039
|
+
const wt = d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null && wtExists(workerWt) ? workerWt : null;
|
|
6769
7040
|
this.runtime.set(d.id, { pid: null, worktree_path: wt });
|
|
6770
7041
|
dead++;
|
|
6771
7042
|
}
|
|
@@ -6786,6 +7057,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
6786
7057
|
if (sup) local.superseded_by = this.localIdForProdId(sup) ?? local.superseded_by;
|
|
6787
7058
|
const rev = pr.reviewsRunId ?? null;
|
|
6788
7059
|
if (rev) local.reviews_run_id = this.localIdForProdId(rev) ?? local.reviews_run_id;
|
|
7060
|
+
const ver = pr.verifiesRunId ?? null;
|
|
7061
|
+
if (ver) local.verifies_run_id = this.localIdForProdId(ver) ?? local.verifies_run_id;
|
|
6789
7062
|
}
|
|
6790
7063
|
/**
|
|
6791
7064
|
* Авто-интейк (TASWM-100): взять следующую claimable-задачу владельца токена через
|
|
@@ -7282,9 +7555,16 @@ var SwarmClient = class _SwarmClient {
|
|
|
7282
7555
|
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
7283
7556
|
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
7284
7557
|
*
|
|
7558
|
+
* TASWM-333: тем же пульсом уходит ШТОРМ этой машины (`storm` — штормим ли сейчас, и с
|
|
7559
|
+
* какой формулировкой мы в него вошли). Состояние живёт в наших событиях и переживает
|
|
7560
|
+
* рестарт, а в проде его не было вовсе: рой стоял, а парк машин показывал живую свободную
|
|
7561
|
+
* машину — 10.08 это стоило десяти часов простоя, замеченных человеком вручную. Шлём
|
|
7562
|
+
* ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
|
|
7563
|
+
* означало бы «прод помнит вчерашний шторм», когда рой давно работает.
|
|
7564
|
+
*
|
|
7285
7565
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
7286
7566
|
*/
|
|
7287
|
-
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
7567
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm) {
|
|
7288
7568
|
try {
|
|
7289
7569
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
7290
7570
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -7294,12 +7574,17 @@ var SwarmClient = class _SwarmClient {
|
|
|
7294
7574
|
args.paused = !!pause?.paused;
|
|
7295
7575
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
7296
7576
|
}
|
|
7577
|
+
if (storm !== void 0 && storm !== null) {
|
|
7578
|
+
args.storm = storm.active;
|
|
7579
|
+
if (storm.reason) args.stormReason = storm.reason;
|
|
7580
|
+
}
|
|
7297
7581
|
if (this.host) {
|
|
7298
7582
|
args.hostId = this.host.hostId;
|
|
7299
7583
|
args.hostName = this.host.hostName;
|
|
7300
7584
|
}
|
|
7301
7585
|
const res = await this.call("swarm_heartbeat", args);
|
|
7302
7586
|
if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
|
|
7587
|
+
this.heartbeatOkAt = this.now();
|
|
7303
7588
|
const data = res?.structuredContent ?? {};
|
|
7304
7589
|
return {
|
|
7305
7590
|
commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
|
|
@@ -7511,6 +7796,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
7511
7796
|
resolves_run_id: null,
|
|
7512
7797
|
reviews_run_id: null,
|
|
7513
7798
|
review_verdict: null,
|
|
7799
|
+
verifies_run_id: null,
|
|
7800
|
+
// прогон-верификатор (TASWM-344) — прод-поле, заполняется ниже
|
|
7801
|
+
verify_verdict: null,
|
|
7514
7802
|
review_phase: null,
|
|
7515
7803
|
// вычисляемая фаза ревью (TASWM-230) — прод хранит и раздаёт
|
|
7516
7804
|
// Локальная бухгалтерия (в прод-схему не уходит) — на гидрации из прода дефолт 0;
|
|
@@ -7554,6 +7842,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
7554
7842
|
const revProd = pr.reviewsRunId ?? null;
|
|
7555
7843
|
if (revProd) local.reviews_run_id = this.localIdForProdId(revProd) ?? local.reviews_run_id;
|
|
7556
7844
|
local.review_verdict = pr.reviewVerdict ?? local.review_verdict;
|
|
7845
|
+
const verProd = pr.verifiesRunId ?? null;
|
|
7846
|
+
if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
|
|
7847
|
+
local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
|
|
7557
7848
|
local.review_phase = pr.reviewPhase ?? local.review_phase;
|
|
7558
7849
|
local.repo = this.resolveRepo(pr.repo ?? null);
|
|
7559
7850
|
local.host_id = pr.hostId ?? null;
|
|
@@ -7842,6 +8133,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
7842
8133
|
pendingCount() {
|
|
7843
8134
|
return this.pending.length;
|
|
7844
8135
|
}
|
|
8136
|
+
/**
|
|
8137
|
+
* Когда прод в последний раз ПРИНЯЛ пульс этой машины (ISO) — вход `swarm status`.
|
|
8138
|
+
* null — ни разу за жизнь процесса.
|
|
8139
|
+
*
|
|
8140
|
+
* Почему именно пульс, а не «последний удачный вызов»: в парке машин lastSeenAt прод
|
|
8141
|
+
* пишет ровно на swarm_heartbeat. Спрашивать за тем же самым swarm_list_hosts значило бы
|
|
8142
|
+
* завести второй источник правды и платить прод-вызовом в команде, которую зовут именно
|
|
8143
|
+
* тогда, когда прод не отвечает.
|
|
8144
|
+
*/
|
|
8145
|
+
lastHeardAt() {
|
|
8146
|
+
return this.heartbeatOkAt == null ? null : new Date(this.heartbeatOkAt).toISOString();
|
|
8147
|
+
}
|
|
7845
8148
|
close() {
|
|
7846
8149
|
if (this.flushTimer) {
|
|
7847
8150
|
clearTimeout(this.flushTimer);
|
|
@@ -7995,13 +8298,14 @@ function pickRepoPaths(args) {
|
|
|
7995
8298
|
// src/repoDiscovery.ts
|
|
7996
8299
|
init_src();
|
|
7997
8300
|
init_config();
|
|
8301
|
+
init_linkFailure();
|
|
7998
8302
|
import { homedir as homedir3 } from "os";
|
|
7999
|
-
import { readFileSync as
|
|
8000
|
-
import { isAbsolute, join as
|
|
8303
|
+
import { readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
8304
|
+
import { isAbsolute, join as join10, resolve as resolve2 } from "path";
|
|
8001
8305
|
|
|
8002
8306
|
// src/repoScan.ts
|
|
8003
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
8004
|
-
import { join as
|
|
8307
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
8308
|
+
import { join as join9 } from "path";
|
|
8005
8309
|
var SKIP = /* @__PURE__ */ new Set([
|
|
8006
8310
|
"node_modules",
|
|
8007
8311
|
"vendor",
|
|
@@ -8031,7 +8335,7 @@ function parseOriginFromGitConfig(text) {
|
|
|
8031
8335
|
}
|
|
8032
8336
|
function originOf(dir) {
|
|
8033
8337
|
try {
|
|
8034
|
-
return parseOriginFromGitConfig(
|
|
8338
|
+
return parseOriginFromGitConfig(readFileSync8(join9(dir, ".git", "config"), "utf8"));
|
|
8035
8339
|
} catch {
|
|
8036
8340
|
return null;
|
|
8037
8341
|
}
|
|
@@ -8066,7 +8370,7 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
8066
8370
|
}
|
|
8067
8371
|
for (const name of entries) {
|
|
8068
8372
|
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
8069
|
-
const child =
|
|
8373
|
+
const child = join9(dir, name);
|
|
8070
8374
|
try {
|
|
8071
8375
|
if (!statSync5(child).isDirectory()) continue;
|
|
8072
8376
|
} catch {
|
|
@@ -8088,7 +8392,7 @@ function defaultScanRoots(home, knownPaths = []) {
|
|
|
8088
8392
|
if (parent && parent !== p) add(parent);
|
|
8089
8393
|
}
|
|
8090
8394
|
for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
|
|
8091
|
-
add(
|
|
8395
|
+
add(join9(home, name));
|
|
8092
8396
|
}
|
|
8093
8397
|
return out;
|
|
8094
8398
|
}
|
|
@@ -8102,7 +8406,7 @@ function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
|
|
|
8102
8406
|
for (const part of v.split(",")) {
|
|
8103
8407
|
const p = part.trim();
|
|
8104
8408
|
if (!p) continue;
|
|
8105
|
-
const abs = p === "~" ? home : p.startsWith("~/") ?
|
|
8409
|
+
const abs = p === "~" ? home : p.startsWith("~/") ? join10(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
|
|
8106
8410
|
if (!out.includes(abs)) out.push(abs);
|
|
8107
8411
|
}
|
|
8108
8412
|
return out;
|
|
@@ -8115,16 +8419,24 @@ function normalizeFullNames(list) {
|
|
|
8115
8419
|
}
|
|
8116
8420
|
return out;
|
|
8117
8421
|
}
|
|
8422
|
+
function errorText(res) {
|
|
8423
|
+
return (res.content ?? []).map((c) => c?.text ?? "").filter(Boolean).join(" ").trim();
|
|
8424
|
+
}
|
|
8118
8425
|
async function fetchSwarmRepos(call) {
|
|
8119
8426
|
let res;
|
|
8120
8427
|
try {
|
|
8121
8428
|
res = await call("swarm_my_repos", {});
|
|
8122
|
-
} catch {
|
|
8123
|
-
return
|
|
8429
|
+
} catch (e) {
|
|
8430
|
+
return { ok: false, ...explainLinkFailure(e) };
|
|
8431
|
+
}
|
|
8432
|
+
if (!res) return { ok: false, kind: "offline", detail: "\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u0434\u0430\u043B \u043E\u0442\u0432\u0435\u0442\u0430" };
|
|
8433
|
+
if (res.isError) {
|
|
8434
|
+
return { ok: false, ...explainLinkFailure(errorText(res) || "\u0431\u0435\u0437 \u0442\u0435\u043A\u0441\u0442\u0430 \u043E\u0448\u0438\u0431\u043A\u0438") };
|
|
8124
8435
|
}
|
|
8125
|
-
if (!res || res.isError) return null;
|
|
8126
8436
|
const raw = res.structuredContent?.repos;
|
|
8127
|
-
if (!Array.isArray(raw))
|
|
8437
|
+
if (!Array.isArray(raw)) {
|
|
8438
|
+
return { ok: false, kind: "malformed", detail: `\u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 \u0441\u043F\u0438\u0441\u043A\u0430 repos (${typeof raw})` };
|
|
8439
|
+
}
|
|
8128
8440
|
const out = [];
|
|
8129
8441
|
for (const item of raw) {
|
|
8130
8442
|
if (!item || typeof item !== "object") continue;
|
|
@@ -8137,7 +8449,24 @@ async function fetchSwarmRepos(call) {
|
|
|
8137
8449
|
workspaceSlug: typeof r.workspaceSlug === "string" ? r.workspaceSlug : ""
|
|
8138
8450
|
});
|
|
8139
8451
|
}
|
|
8140
|
-
return out;
|
|
8452
|
+
return { ok: true, repos: out };
|
|
8453
|
+
}
|
|
8454
|
+
function describeReposFailure(kind, detail) {
|
|
8455
|
+
const head = "[overmind] \u043D\u0435 \u0443\u0437\u043D\u0430\u043B \u0443 \u043F\u0440\u043E\u0434\u0430, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F (swarm_my_repos) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435. ";
|
|
8456
|
+
switch (kind) {
|
|
8457
|
+
case "missing_tool":
|
|
8458
|
+
return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0442\u0430\u043A\u043E\u0439 \u0440\u0443\u0447\u043A\u0438 \u0443 \u043D\u0435\u0433\u043E \u043D\u0435\u0442 (${detail}) \u2014 \u0432\u043E\u0442 \u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \xAB\u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u0442\u0430\u0440\u0435\u0435 \u0434\u0435\u043C\u043E\u043D\u0430\xBB, \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u043F\u0440\u043E\u0434\u0430.`;
|
|
8459
|
+
case "unauthorized":
|
|
8460
|
+
return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0438 \u041D\u0415 \u041F\u0423\u0421\u0422\u0418\u041B: \u0442\u043E\u043A\u0435\u043D \u043D\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u0435\u043D (${detail}). \u041B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u0435\u0440\u0435\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439: swarm login.`;
|
|
8461
|
+
case "offline":
|
|
8462
|
+
return `${head}\u0412\u042B\u0417\u041E\u0412 \u041D\u0415 \u0414\u041E\u0428\u0401\u041B \u0434\u043E \u043F\u0440\u043E\u0434\u0430 \u2014 \u0441\u0432\u044F\u0437\u044C \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C (${detail}). \u042D\u0442\u043E \u041D\u0415 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043D\u0435 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0430\u044F \u0440\u0443\u0447\u043A\u0430: \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435, \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043F\u0440\u043E\u0434 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`;
|
|
8463
|
+
case "malformed":
|
|
8464
|
+
return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043D\u043E \u0432 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E\u043C \u0444\u043E\u0440\u043C\u0430\u0442\u0435 (${detail}).`;
|
|
8465
|
+
case "unknown":
|
|
8466
|
+
return `${head}\u041F\u041E\u0427\u0415\u041C\u0423 \u2014 \u041D\u0415\u0418\u0417\u0412\u0415\u0421\u0422\u041D\u041E (${detail}). \u041D\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0439 \u0441\u0435\u0440\u0432\u0435\u0440, \u043D\u0438 \u043E\u0431\u0440\u044B\u0432 \u0441\u0432\u044F\u0437\u0438 \u043E\u0442\u0441\u044E\u0434\u0430 \u043D\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0442: \u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u0442\u0440\u043E\u043A\u0443 \xABSwarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E \u2026\xBB \u0432\u044B\u0448\u0435 \u2014 \u0442\u0430\u043C \u043F\u0440\u0438\u0447\u0438\u043D\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0432\u0438\u0434\u0435\u043B \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440.`;
|
|
8467
|
+
default:
|
|
8468
|
+
return `${head}\u041F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0442\u043A\u0430\u0437\u043E\u043C: ${detail}.`;
|
|
8469
|
+
}
|
|
8141
8470
|
}
|
|
8142
8471
|
function emptyRepoMapState(roots) {
|
|
8143
8472
|
return { clones: [], missing: [], roots: [...roots], at: null, unknown: true };
|
|
@@ -8147,7 +8476,8 @@ async function refreshRepoMap(opts) {
|
|
|
8147
8476
|
});
|
|
8148
8477
|
const scan = opts.scan ?? scanRootsAll;
|
|
8149
8478
|
const repoAt = opts.repoAt ?? repoFullNameAt;
|
|
8150
|
-
const
|
|
8479
|
+
const answer = await fetchSwarmRepos(opts.call);
|
|
8480
|
+
const wanted = answer.ok ? answer.repos : null;
|
|
8151
8481
|
const clones = /* @__PURE__ */ new Map();
|
|
8152
8482
|
const take = (repoFullName, path, source, alternatives = []) => {
|
|
8153
8483
|
if (clones.has(repoFullName)) return;
|
|
@@ -8182,10 +8512,8 @@ async function refreshRepoMap(opts) {
|
|
|
8182
8512
|
opts.cachePath,
|
|
8183
8513
|
[...clones.values()].map((c) => ({ repoFullName: c.repoFullName, path: c.path }))
|
|
8184
8514
|
);
|
|
8185
|
-
if (!
|
|
8186
|
-
log(
|
|
8187
|
-
"[overmind] \u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F (swarm_my_repos) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435. \u0421\u0435\u0440\u0432\u0435\u0440 \u0441\u0442\u0430\u0440\u0435\u0435 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u044D\u0442\u043E \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u043F\u0440\u043E\u0434\u0430."
|
|
8188
|
-
);
|
|
8515
|
+
if (!answer.ok) {
|
|
8516
|
+
log(describeReposFailure(answer.kind, answer.detail));
|
|
8189
8517
|
return registry;
|
|
8190
8518
|
}
|
|
8191
8519
|
opts.state.unknown = false;
|
|
@@ -8199,7 +8527,7 @@ async function refreshRepoMap(opts) {
|
|
|
8199
8527
|
}
|
|
8200
8528
|
log(`[overmind] \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F (${wantedNames.length}), \u043A\u0430\u0440\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435:`);
|
|
8201
8529
|
for (const c of opts.state.clones) log(`[overmind] ${describeClone(c)}`);
|
|
8202
|
-
const prefixOf = new Map(
|
|
8530
|
+
const prefixOf = new Map(answer.repos.map((r) => [r.repoFullName, r.taskPrefix]));
|
|
8203
8531
|
for (const name of opts.state.missing) {
|
|
8204
8532
|
const prefix = prefixOf.get(name);
|
|
8205
8533
|
log(`[overmind] ${describeMissing(prefix ? `${name} (\u043F\u0440\u043E\u0435\u043A\u0442 ${prefix})` : name, opts.roots)}`);
|
|
@@ -8228,7 +8556,7 @@ function repoMapRows(state) {
|
|
|
8228
8556
|
}
|
|
8229
8557
|
function readRepoMapCache(path) {
|
|
8230
8558
|
try {
|
|
8231
|
-
const raw = JSON.parse(
|
|
8559
|
+
const raw = JSON.parse(readFileSync9(path, "utf8"));
|
|
8232
8560
|
if (!Array.isArray(raw?.entries)) return [];
|
|
8233
8561
|
return raw.entries.filter(
|
|
8234
8562
|
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
@@ -8240,7 +8568,7 @@ function readRepoMapCache(path) {
|
|
|
8240
8568
|
function writeRepoMapCache(path, entries) {
|
|
8241
8569
|
try {
|
|
8242
8570
|
const tmp = `${path}.tmp`;
|
|
8243
|
-
|
|
8571
|
+
writeFileSync8(tmp, JSON.stringify({ at: stamp(), entries }, null, 2));
|
|
8244
8572
|
renameSync5(tmp, path);
|
|
8245
8573
|
} catch {
|
|
8246
8574
|
}
|
|
@@ -8248,17 +8576,17 @@ function writeRepoMapCache(path, entries) {
|
|
|
8248
8576
|
|
|
8249
8577
|
// src/hostIdentity.ts
|
|
8250
8578
|
import { hostname } from "os";
|
|
8251
|
-
import { existsSync as existsSync7, readFileSync as
|
|
8252
|
-
import { join as
|
|
8579
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
8580
|
+
import { join as join11 } from "path";
|
|
8253
8581
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8254
8582
|
function hostIdentityPath(home) {
|
|
8255
|
-
return
|
|
8583
|
+
return join11(home, "host.json");
|
|
8256
8584
|
}
|
|
8257
8585
|
function loadHostIdentity(deps) {
|
|
8258
8586
|
const exists = deps.exists ?? existsSync7;
|
|
8259
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
8587
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync10(p, "utf8"));
|
|
8260
8588
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
8261
|
-
|
|
8589
|
+
writeFileSync9(`${p}.tmp`, data);
|
|
8262
8590
|
renameSync6(`${p}.tmp`, p);
|
|
8263
8591
|
});
|
|
8264
8592
|
const host = deps.hostname ?? hostname;
|
|
@@ -8444,12 +8772,12 @@ import { spawn } from "child_process";
|
|
|
8444
8772
|
import { closeSync as closeSync2, existsSync as existsSync8, mkdirSync as mkdirSync6, openSync as openSync2, readdirSync as readdirSync7 } from "fs";
|
|
8445
8773
|
import { createRequire } from "module";
|
|
8446
8774
|
import { homedir as homedir4 } from "os";
|
|
8447
|
-
import { dirname as dirname2, join as
|
|
8775
|
+
import { dirname as dirname2, join as join13 } from "path";
|
|
8448
8776
|
import { fileURLToPath } from "url";
|
|
8449
8777
|
|
|
8450
8778
|
// src/droneHome.ts
|
|
8451
8779
|
import { lstatSync, mkdirSync as mkdirSync5, readdirSync as readdirSync6, rmSync, statSync as statSync6, symlinkSync as symlinkSync2 } from "fs";
|
|
8452
|
-
import { join as
|
|
8780
|
+
import { join as join12, relative, sep } from "path";
|
|
8453
8781
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
8454
8782
|
function droneHomeDeny(env = process.env) {
|
|
8455
8783
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -8473,20 +8801,20 @@ function buildDroneHome(opts) {
|
|
|
8473
8801
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
8474
8802
|
for (const existing of readdirSync6(droneHome)) {
|
|
8475
8803
|
if (wanted.has(existing)) continue;
|
|
8476
|
-
const p =
|
|
8804
|
+
const p = join12(droneHome, existing);
|
|
8477
8805
|
try {
|
|
8478
8806
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
8479
8807
|
} catch {
|
|
8480
8808
|
}
|
|
8481
8809
|
}
|
|
8482
8810
|
for (const name of wanted) {
|
|
8483
|
-
const link =
|
|
8811
|
+
const link = join12(droneHome, name);
|
|
8484
8812
|
try {
|
|
8485
8813
|
lstatSync(link);
|
|
8486
8814
|
continue;
|
|
8487
8815
|
} catch {
|
|
8488
8816
|
}
|
|
8489
|
-
const target =
|
|
8817
|
+
const target = join12(realHome, name);
|
|
8490
8818
|
let isDir;
|
|
8491
8819
|
try {
|
|
8492
8820
|
isDir = statSync6(target).isDirectory();
|
|
@@ -8507,7 +8835,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
8507
8835
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
8508
8836
|
];
|
|
8509
8837
|
function realPathEnvPins(realHome) {
|
|
8510
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
8838
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join12(realHome, ...segments)]));
|
|
8511
8839
|
}
|
|
8512
8840
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
8513
8841
|
return [
|
|
@@ -8605,7 +8933,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
8605
8933
|
const env = { ...base3 };
|
|
8606
8934
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
8607
8935
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
8608
|
-
env.TURBO_CACHE_DIR =
|
|
8936
|
+
env.TURBO_CACHE_DIR = join13(worktree, ".turbo", "cache");
|
|
8609
8937
|
}
|
|
8610
8938
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
8611
8939
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -8633,16 +8961,16 @@ var DAEMON_STATE_ENV = [
|
|
|
8633
8961
|
"TASKLESS_TOKEN"
|
|
8634
8962
|
];
|
|
8635
8963
|
function bundledRoleDir(kind) {
|
|
8636
|
-
return
|
|
8964
|
+
return join13(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
8637
8965
|
}
|
|
8638
8966
|
function roleDir(worktree, kind) {
|
|
8639
|
-
return
|
|
8967
|
+
return join13(worktree, ".claude", "roles", kind);
|
|
8640
8968
|
}
|
|
8641
8969
|
function roleDirs(worktree, kind) {
|
|
8642
8970
|
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync8(dir));
|
|
8643
8971
|
}
|
|
8644
8972
|
function nextRunLogPath(runLogsDir, runId) {
|
|
8645
|
-
const dir =
|
|
8973
|
+
const dir = join13(runLogsDir, String(runId));
|
|
8646
8974
|
mkdirSync6(dir, { recursive: true });
|
|
8647
8975
|
let files = [];
|
|
8648
8976
|
try {
|
|
@@ -8654,7 +8982,7 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
8654
8982
|
const m = /^(\d+)\.json/.exec(f);
|
|
8655
8983
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
8656
8984
|
}
|
|
8657
|
-
return
|
|
8985
|
+
return join13(dir, `${next}.json`);
|
|
8658
8986
|
}
|
|
8659
8987
|
function openNextRunLog(runLogsDir, runId) {
|
|
8660
8988
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -8722,6 +9050,9 @@ function makeLauncher(cfg) {
|
|
|
8722
9050
|
SWARM_RUN_NAME: run.name,
|
|
8723
9051
|
...run.workspace_id ? { SWARM_WORKSPACE_ID: run.workspace_id } : {},
|
|
8724
9052
|
...run.reviews_run_id != null ? { SWARM_REVIEWS_RUN_ID: String(run.reviews_run_id) } : {},
|
|
9053
|
+
// TASWM-344: та же роль, что у SWARM_REVIEWS_RUN_ID выше, для верификатора — без неё
|
|
9054
|
+
// его done() в client-режиме пошёл бы веткой обычной сдачи PR, и вердикт пропал бы.
|
|
9055
|
+
...run.verifies_run_id != null ? { SWARM_VERIFIES_RUN_ID: String(run.verifies_run_id) } : {},
|
|
8725
9056
|
...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
|
|
8726
9057
|
}
|
|
8727
9058
|
};
|
|
@@ -8734,9 +9065,9 @@ function makeLauncher(cfg) {
|
|
|
8734
9065
|
}
|
|
8735
9066
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8736
9067
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8737
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(
|
|
9068
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join13(cfg.droneIsolation.swarmHome, "token"))) {
|
|
8738
9069
|
console.error(
|
|
8739
|
-
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${
|
|
9070
|
+
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join13(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
|
|
8740
9071
|
);
|
|
8741
9072
|
}
|
|
8742
9073
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -8792,8 +9123,8 @@ init_procCleanup();
|
|
|
8792
9123
|
|
|
8793
9124
|
// src/cost.ts
|
|
8794
9125
|
init_executors();
|
|
8795
|
-
import { readFileSync as
|
|
8796
|
-
import { join as
|
|
9126
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync8, renameSync as renameSync7 } from "fs";
|
|
9127
|
+
import { join as join14 } from "path";
|
|
8797
9128
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
8798
9129
|
function unprocessedLogFiles(dir) {
|
|
8799
9130
|
try {
|
|
@@ -8803,7 +9134,7 @@ function unprocessedLogFiles(dir) {
|
|
|
8803
9134
|
}
|
|
8804
9135
|
}
|
|
8805
9136
|
function accountRunCost(deps, run) {
|
|
8806
|
-
const dir =
|
|
9137
|
+
const dir = join14(deps.runLogsDir, String(run.id));
|
|
8807
9138
|
const files = unprocessedLogFiles(dir);
|
|
8808
9139
|
if (files.length === 0) return;
|
|
8809
9140
|
const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
|
|
@@ -8814,10 +9145,10 @@ function accountRunCost(deps, run) {
|
|
|
8814
9145
|
let durationMs = 0;
|
|
8815
9146
|
const processed = [];
|
|
8816
9147
|
for (const f of files) {
|
|
8817
|
-
const full =
|
|
9148
|
+
const full = join14(dir, f);
|
|
8818
9149
|
let raw = null;
|
|
8819
9150
|
try {
|
|
8820
|
-
raw =
|
|
9151
|
+
raw = readFileSync11(full, "utf8");
|
|
8821
9152
|
} catch {
|
|
8822
9153
|
continue;
|
|
8823
9154
|
}
|
|
@@ -8854,14 +9185,14 @@ function makeCostAccountant(deps) {
|
|
|
8854
9185
|
|
|
8855
9186
|
// src/runOutcome.ts
|
|
8856
9187
|
init_executors();
|
|
8857
|
-
import { readdirSync as readdirSync9, readFileSync as
|
|
8858
|
-
import { join as
|
|
9188
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync12 } from "fs";
|
|
9189
|
+
import { join as join15 } from "path";
|
|
8859
9190
|
function endedItself(outcome) {
|
|
8860
9191
|
return outcome === "graceful" || outcome === "budget";
|
|
8861
9192
|
}
|
|
8862
9193
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
8863
9194
|
function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
8864
|
-
const dir =
|
|
9195
|
+
const dir = join15(runLogsDir, String(runId));
|
|
8865
9196
|
let files;
|
|
8866
9197
|
try {
|
|
8867
9198
|
files = readdirSync9(dir);
|
|
@@ -8878,7 +9209,7 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
|
8878
9209
|
if (!latest) return "unknown";
|
|
8879
9210
|
let raw;
|
|
8880
9211
|
try {
|
|
8881
|
-
raw =
|
|
9212
|
+
raw = readFileSync12(join15(dir, latest.name), "utf8");
|
|
8882
9213
|
} catch {
|
|
8883
9214
|
return "unknown";
|
|
8884
9215
|
}
|
|
@@ -8896,20 +9227,20 @@ init_src();
|
|
|
8896
9227
|
init_git();
|
|
8897
9228
|
|
|
8898
9229
|
// src/deps.ts
|
|
8899
|
-
import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as
|
|
8900
|
-
import { join as
|
|
9230
|
+
import { existsSync as existsSync10, readdirSync as readdirSync11, readFileSync as readFileSync13 } from "fs";
|
|
9231
|
+
import { join as join18 } from "path";
|
|
8901
9232
|
import { execa } from "execa";
|
|
8902
9233
|
|
|
8903
9234
|
// src/prepareLock.ts
|
|
8904
9235
|
import { createHash } from "crypto";
|
|
8905
9236
|
import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync8, writeSync } from "fs";
|
|
8906
|
-
import { join as
|
|
9237
|
+
import { join as join17 } from "path";
|
|
8907
9238
|
import { tmpdir } from "os";
|
|
8908
9239
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
8909
9240
|
var POLL_MS = 300;
|
|
8910
9241
|
var STALE_MS = 20 * 6e4;
|
|
8911
9242
|
function machineLockPath(key, dir = tmpdir()) {
|
|
8912
|
-
return
|
|
9243
|
+
return join17(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
8913
9244
|
}
|
|
8914
9245
|
function defaultTryCreate(path, content) {
|
|
8915
9246
|
try {
|
|
@@ -8983,7 +9314,7 @@ async function runCommand(cmd, worktree) {
|
|
|
8983
9314
|
}
|
|
8984
9315
|
function safeReadText(p) {
|
|
8985
9316
|
try {
|
|
8986
|
-
return
|
|
9317
|
+
return readFileSync13(p, "utf8");
|
|
8987
9318
|
} catch {
|
|
8988
9319
|
return null;
|
|
8989
9320
|
}
|
|
@@ -9025,19 +9356,19 @@ function overridePlan(override) {
|
|
|
9025
9356
|
function detectPackageManager(worktree, io = {}) {
|
|
9026
9357
|
const exists = io.exists ?? existsSync10;
|
|
9027
9358
|
const readText = io.readText ?? safeReadText;
|
|
9028
|
-
const hasPkgJson = exists(
|
|
9359
|
+
const hasPkgJson = exists(join18(worktree, "package.json"));
|
|
9029
9360
|
if (!hasPkgJson) {
|
|
9030
|
-
if (exists(
|
|
9361
|
+
if (exists(join18(worktree, "deno.json")) || exists(join18(worktree, "deno.jsonc")) || exists(join18(worktree, "deno.lock"))) return "deno";
|
|
9031
9362
|
return null;
|
|
9032
9363
|
}
|
|
9033
|
-
const pkg = readJson(readText,
|
|
9364
|
+
const pkg = readJson(readText, join18(worktree, "package.json"));
|
|
9034
9365
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
9035
9366
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
9036
|
-
if (exists(
|
|
9037
|
-
if (exists(
|
|
9038
|
-
if (exists(
|
|
9039
|
-
if (exists(
|
|
9040
|
-
if (exists(
|
|
9367
|
+
if (exists(join18(worktree, "bun.lock")) || exists(join18(worktree, "bun.lockb"))) return "bun";
|
|
9368
|
+
if (exists(join18(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
9369
|
+
if (exists(join18(worktree, "yarn.lock"))) return "yarn";
|
|
9370
|
+
if (exists(join18(worktree, "package-lock.json"))) return "npm";
|
|
9371
|
+
if (exists(join18(worktree, "deno.lock"))) return "deno";
|
|
9041
9372
|
return "npm";
|
|
9042
9373
|
}
|
|
9043
9374
|
function jsInstallCommand(pm) {
|
|
@@ -9098,7 +9429,7 @@ function detectPlan(worktree, io) {
|
|
|
9098
9429
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
9099
9430
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
9100
9431
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
9101
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
9432
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join18(worktree, "node_modules"), source: "detect" }
|
|
9102
9433
|
];
|
|
9103
9434
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
9104
9435
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -9107,12 +9438,12 @@ function detectPlan(worktree, io) {
|
|
|
9107
9438
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
9108
9439
|
}
|
|
9109
9440
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
9110
|
-
const hit = marker.files.some((f) => exists(
|
|
9441
|
+
const hit = marker.files.some((f) => exists(join18(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
9111
9442
|
if (!hit) continue;
|
|
9112
9443
|
let ecosystem = marker.ecosystem;
|
|
9113
9444
|
let command = marker.command;
|
|
9114
9445
|
for (const refine of marker.refine ?? []) {
|
|
9115
|
-
if (!exists(
|
|
9446
|
+
if (!exists(join18(worktree, refine.file))) continue;
|
|
9116
9447
|
ecosystem = refine.ecosystem;
|
|
9117
9448
|
command = refine.command;
|
|
9118
9449
|
break;
|
|
@@ -9169,12 +9500,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
9169
9500
|
function ciWorkflowPlan(worktree, io) {
|
|
9170
9501
|
const readText = io.readText ?? safeReadText;
|
|
9171
9502
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
9172
|
-
const dir =
|
|
9503
|
+
const dir = join18(worktree, ".github", "workflows");
|
|
9173
9504
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
9174
9505
|
const seen = /* @__PURE__ */ new Set();
|
|
9175
9506
|
const commands = [];
|
|
9176
9507
|
for (const name of files.sort()) {
|
|
9177
|
-
const yaml = readText(
|
|
9508
|
+
const yaml = readText(join18(dir, name));
|
|
9178
9509
|
if (yaml === null) continue;
|
|
9179
9510
|
for (const raw of workflowRunLines(yaml)) {
|
|
9180
9511
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -9189,7 +9520,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
9189
9520
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
9190
9521
|
}
|
|
9191
9522
|
function workspaceGlobs(worktree, readText) {
|
|
9192
|
-
const yaml = readText(
|
|
9523
|
+
const yaml = readText(join18(worktree, "pnpm-workspace.yaml"));
|
|
9193
9524
|
if (yaml !== null) {
|
|
9194
9525
|
const globs = [];
|
|
9195
9526
|
let inPackages = false;
|
|
@@ -9209,7 +9540,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
9209
9540
|
}
|
|
9210
9541
|
if (globs.length) return globs;
|
|
9211
9542
|
}
|
|
9212
|
-
const pkg = readJson(readText,
|
|
9543
|
+
const pkg = readJson(readText, join18(worktree, "package.json"));
|
|
9213
9544
|
const ws = pkg?.workspaces;
|
|
9214
9545
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
9215
9546
|
return list.filter((g) => typeof g === "string");
|
|
@@ -9220,14 +9551,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
9220
9551
|
if (glob.startsWith("!")) continue;
|
|
9221
9552
|
const star = glob.indexOf("*");
|
|
9222
9553
|
if (star === -1) {
|
|
9223
|
-
const dir =
|
|
9224
|
-
if (io.exists(
|
|
9554
|
+
const dir = join18(worktree, glob);
|
|
9555
|
+
if (io.exists(join18(dir, "package.json"))) dirs.add(dir);
|
|
9225
9556
|
continue;
|
|
9226
9557
|
}
|
|
9227
|
-
const base3 =
|
|
9558
|
+
const base3 = join18(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
9228
9559
|
for (const name of io.listDirs(base3)) {
|
|
9229
|
-
const dir =
|
|
9230
|
-
if (io.exists(
|
|
9560
|
+
const dir = join18(base3, name);
|
|
9561
|
+
if (io.exists(join18(dir, "package.json"))) dirs.add(dir);
|
|
9231
9562
|
}
|
|
9232
9563
|
}
|
|
9233
9564
|
return [...dirs];
|
|
@@ -9255,11 +9586,11 @@ function workspaceBuildTargets(worktree, io = {}) {
|
|
|
9255
9586
|
if (!globs.length) return [];
|
|
9256
9587
|
const pkgs = [];
|
|
9257
9588
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
9258
|
-
const pkg = readJson(readText,
|
|
9589
|
+
const pkg = readJson(readText, join18(dir, "package.json"));
|
|
9259
9590
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
9260
9591
|
const scripts = pkg.scripts ?? {};
|
|
9261
9592
|
const entries = entryPaths(pkg);
|
|
9262
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
9593
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join18(dir, e)));
|
|
9263
9594
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
9264
9595
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
9265
9596
|
}
|
|
@@ -9287,7 +9618,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
9287
9618
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
9288
9619
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
9289
9620
|
for (const file of [".env", ".env.example"]) {
|
|
9290
|
-
const raw = readText(
|
|
9621
|
+
const raw = readText(join18(worktree, file));
|
|
9291
9622
|
if (raw === null) continue;
|
|
9292
9623
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
9293
9624
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -9307,7 +9638,7 @@ function prismaPackages(worktree, io) {
|
|
|
9307
9638
|
const readText = io.readText ?? safeReadText;
|
|
9308
9639
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
9309
9640
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
9310
|
-
return dirs.filter((dir) => exists(
|
|
9641
|
+
return dirs.filter((dir) => exists(join18(dir, "prisma", "schema.prisma")) && listDirs(join18(dir, "prisma", "migrations")).length > 0);
|
|
9311
9642
|
}
|
|
9312
9643
|
function jsExecBin(pm, command) {
|
|
9313
9644
|
switch (pm) {
|
|
@@ -9508,6 +9839,23 @@ ${notice}` : built;
|
|
|
9508
9839
|
});
|
|
9509
9840
|
}
|
|
9510
9841
|
|
|
9842
|
+
// src/spawnDefer.ts
|
|
9843
|
+
function noteDefer(episodes, key, reason, heartbeatTicks) {
|
|
9844
|
+
const prev = episodes.get(key);
|
|
9845
|
+
const ticks = (prev?.ticks ?? 0) + 1;
|
|
9846
|
+
const first = prev === void 0;
|
|
9847
|
+
const changed = first || reason !== prev.reason;
|
|
9848
|
+
const beat = !changed && heartbeatTicks > 0 && ticks % heartbeatTicks === 0;
|
|
9849
|
+
episodes.set(key, { ticks, reason });
|
|
9850
|
+
return { log: changed || beat, changed, ticks };
|
|
9851
|
+
}
|
|
9852
|
+
function clearDefer(episodes, key) {
|
|
9853
|
+
return episodes.delete(key);
|
|
9854
|
+
}
|
|
9855
|
+
function pruneDefers(episodes, live) {
|
|
9856
|
+
for (const key of episodes.keys()) if (!live.has(key)) episodes.delete(key);
|
|
9857
|
+
}
|
|
9858
|
+
|
|
9511
9859
|
// src/overmind.ts
|
|
9512
9860
|
init_git();
|
|
9513
9861
|
|
|
@@ -9596,6 +9944,13 @@ function verdictLabel(verdict) {
|
|
|
9596
9944
|
return verdict === "approve" ? "\u2705 APPROVE" : "\u274C REQUEST CHANGES";
|
|
9597
9945
|
}
|
|
9598
9946
|
async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
|
|
9947
|
+
return scanPrComments(prUrl, sinceIso, run, (body) => {
|
|
9948
|
+
const m = body.match(VERDICT_RE);
|
|
9949
|
+
if (!m) return null;
|
|
9950
|
+
return APPROVE_RE.test(m[1]) ? "approve" : "changes";
|
|
9951
|
+
});
|
|
9952
|
+
}
|
|
9953
|
+
async function scanPrComments(prUrl, sinceIso, run, pick3) {
|
|
9599
9954
|
if (!isGithubPrUrl(prUrl)) return null;
|
|
9600
9955
|
let out;
|
|
9601
9956
|
try {
|
|
@@ -9615,13 +9970,15 @@ async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
|
|
|
9615
9970
|
const c = comments[i];
|
|
9616
9971
|
if (!c?.body || !c.createdAt) continue;
|
|
9617
9972
|
if (Number.isFinite(sinceMs) && Date.parse(c.createdAt) < sinceMs) continue;
|
|
9618
|
-
const
|
|
9619
|
-
if (
|
|
9620
|
-
const verdict = APPROVE_RE.test(m[1]) ? "approve" : "changes";
|
|
9973
|
+
const verdict = pick3(c.body);
|
|
9974
|
+
if (verdict == null) continue;
|
|
9621
9975
|
return { verdict, createdAt: c.createdAt };
|
|
9622
9976
|
}
|
|
9623
9977
|
return null;
|
|
9624
9978
|
}
|
|
9979
|
+
async function latestVerifyVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
|
|
9980
|
+
return scanPrComments(prUrl, sinceIso, run, (body) => parseVerifyVerdictComment(body));
|
|
9981
|
+
}
|
|
9625
9982
|
var realPrCommitsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json commits`).stdout;
|
|
9626
9983
|
async function prHasCommitsSince(prUrl, sinceIso, run = realPrCommitsRunner) {
|
|
9627
9984
|
if (!isGithubPrUrl(prUrl)) return false;
|
|
@@ -9762,6 +10119,7 @@ init_attachments();
|
|
|
9762
10119
|
init_shotUpload();
|
|
9763
10120
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9764
10121
|
import { z } from "zod";
|
|
10122
|
+
init_src();
|
|
9765
10123
|
|
|
9766
10124
|
// src/reviewCycle.ts
|
|
9767
10125
|
function reviewTransitionPatch(run, prUrl) {
|
|
@@ -9835,6 +10193,7 @@ async function buildDroneServer(store, opts = {}) {
|
|
|
9835
10193
|
const overmindDead = async () => !await store.alive();
|
|
9836
10194
|
const slugForInstruction = (fallback) => opts.expectedTaskSlug ?? fallback ?? "<slug>";
|
|
9837
10195
|
const iAmReviewer = (run) => run?.reviews_run_id != null || run?.kind === "reviewer" || opts.reviewsRunId != null || opts.runKind === "reviewer";
|
|
10196
|
+
const iAmVerifier = (run) => run?.verifies_run_id != null || run?.kind === "verifier" || opts.verifiesRunId != null || opts.runKind === "verifier";
|
|
9838
10197
|
const freeze = async (withdraw = false) => {
|
|
9839
10198
|
let slug;
|
|
9840
10199
|
let branch;
|
|
@@ -10123,7 +10482,9 @@ ${notFoundText}` : notFoundText }],
|
|
|
10123
10482
|
"\u0420\u0430\u0431\u043E\u0442\u0430 \u0441\u0434\u0430\u043D\u0430. \u0421 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C GitHub-PR (github.com/OWNER/REPO/pull/N) \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0443\u0445\u043E\u0434\u0438\u0442 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0440\u043E\u0439 \u0434\u043E\u043C\u0435\u0440\u0436\u0438\u0442. \u0411\u0435\u0437 \u0441\u0432\u043E\u0435\u0433\u043E PR (\u0440\u0435\u0432\u044C\u044E/meta-\u0437\u0430\u0434\u0430\u0447\u0430) \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443; \u043C\u043E\u0436\u0435\u0448\u044C \u043F\u0435\u0440\u0435\u0434\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0443. summary \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0438 \u0443 \u0418\u0421\u041F\u041E\u041B\u041D\u0418\u0422\u0415\u041B\u042F, \u0438 \u0443 \u0420\u0415\u0412\u042C\u042E\u0415\u0420\u0410 \u2014 \u043E\u043D \u0443\u0445\u043E\u0434\u0438\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 (TASWM-259). \u0414\u0440\u043E\u043D-\u0420\u0415\u0412\u042C\u042E\u0415\u0420 (\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E, TASWM-148) \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043E\u0431\u044F\u0437\u0430\u043D \u043F\u0435\u0440\u0435\u0434\u0430\u0442\u044C verdict \u2014 pr_url \u0442\u043E\u0433\u0434\u0430 \u0442\u0430 \u0436\u0435 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 PR.",
|
|
10124
10483
|
{
|
|
10125
10484
|
pr_url: z.string().describe("\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0439 GitHub-PR; \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447 \u0431\u0435\u0437 PR \u2014 \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043F\u0440\u043E\u0433\u043E\u043D \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044F)"),
|
|
10126
|
-
verdict: z.enum(["approve", "changes"]).optional().describe(
|
|
10485
|
+
verdict: z.enum(["approve", "changes", ...VERIFY_VERDICTS]).optional().describe(
|
|
10486
|
+
"\u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430. \u0420\u0435\u0432\u044C\u044E\u0435\u0440 (\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E): approve | changes. \u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420 (TASWM-344): confirmed (\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E) | not_reproduced (\u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F) | found_issues (\u043D\u0430\u0448\u0451\u043B \u0432\u043E\u0442 \u044D\u0442\u043E). \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0434\u043B\u044F \u043E\u0431\u043E\u0438\u0445."
|
|
10487
|
+
),
|
|
10127
10488
|
summary: z.string().optional().describe(
|
|
10128
10489
|
"\u041E\u0442\u0447\u0451\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443, \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u043F\u0440\u0438 \u0441\u0434\u0430\u0447\u0435 \u0441 PR. \u0418\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C: \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u043A\u0430\u043A\u0438\u0435 \u0444\u0430\u0439\u043B\u044B \u0437\u0430\u0442\u0440\u043E\u043D\u0443\u0442\u044B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043F\u0440\u0438\u043D\u044F\u043B \u0438 \u0447\u0442\u043E \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C \u0437\u0430 \u0441\u043A\u043E\u0431\u043A\u0430\u043C\u0438. \u0420\u0435\u0432\u044C\u044E\u0435\u0440: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B."
|
|
10129
10490
|
),
|
|
@@ -10226,6 +10587,66 @@ ${notFoundText}` : notFoundText }],
|
|
|
10226
10587
|
\u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u041D\u0415 \u0443\u0435\u0445\u0430\u043B (${commentWarning}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0435. \u041F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0435\u0433\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR: \`gh pr comment ${trustedPrUrl} --body "${verdictText} \u2026"\`.` : "";
|
|
10227
10588
|
return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E${shotsNote(shots)}${commentNote}` }] };
|
|
10228
10589
|
}
|
|
10590
|
+
if (iAmVerifier(run) && run?.verifies_run_id == null) {
|
|
10591
|
+
return {
|
|
10592
|
+
content: [
|
|
10593
|
+
{
|
|
10594
|
+
type: "text",
|
|
10595
|
+
text: '\u0422\u044B \u0434\u0440\u043E\u043D-\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440, \u043D\u043E \u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \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 \u2014 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u043A\u0443\u0434\u0430, \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-344). \u041E\u0441\u0442\u0430\u0432\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR (`gh pr comment <pr> --body "**\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E**|**\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F**|**\u041D\u0410\u0428\u0401\u041B** \u2026"`) \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 done() \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443.'
|
|
10596
|
+
}
|
|
10597
|
+
],
|
|
10598
|
+
isError: true
|
|
10599
|
+
};
|
|
10600
|
+
}
|
|
10601
|
+
if (run?.verifies_run_id != null) {
|
|
10602
|
+
if (isTerminalStatus(run.status)) {
|
|
10603
|
+
return { content: [{ type: "text", text: "\u043F\u0440\u043E\u0433\u043E\u043D \u0443\u0436\u0435 \u0437\u0430\u043A\u0440\u044B\u0442 (PR \u0441\u043C\u0435\u0440\u0436\u0435\u043D \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D" }] };
|
|
10604
|
+
}
|
|
10605
|
+
if (!isVerifyVerdict(verdict)) {
|
|
10606
|
+
return {
|
|
10607
|
+
content: [
|
|
10608
|
+
{
|
|
10609
|
+
type: "text",
|
|
10610
|
+
text: '\u0422\u044B \u0434\u0440\u043E\u043D-\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 (TASWM-344) \u2014 done() \u0442\u0440\u0435\u0431\u0443\u0435\u0442 verdict: "confirmed" (\u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044E) | "not_reproduced" (\u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F) | "found_issues" (\u043D\u0430\u0448\u0451\u043B \u0432\u043E\u0442 \u044D\u0442\u043E). \u0427\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442, \u0438 \u0443\u0441\u043F\u0435\u0445\u043E\u043C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F: \u043F\u043E\u043A\u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443. \u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0432\u0441\u0435 \u2014 \u044D\u0442\u043E "not_reproduced" \u0441 \u0447\u0435\u0441\u0442\u043D\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0432 summary, \u0430 \u043D\u0435 \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435.'
|
|
10611
|
+
}
|
|
10612
|
+
],
|
|
10613
|
+
isError: true
|
|
10614
|
+
};
|
|
10615
|
+
}
|
|
10616
|
+
const verified = await store.getRun(run.verifies_run_id);
|
|
10617
|
+
const verdictText = verifyVerdictLabel(verdict);
|
|
10618
|
+
await store.updateRun(run.verifies_run_id, { verify_verdict: verdict });
|
|
10619
|
+
await store.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u043E\u0442 ${run.name}: ${verdictText}${summary ? ` \u2014 ${summary}` : ""}`);
|
|
10620
|
+
const verifiedSlug = reportSlug(verified?.task_slug);
|
|
10621
|
+
let shots = { attachments: [], warning: "" };
|
|
10622
|
+
let commentWarning = "";
|
|
10623
|
+
if (opts.taskless && verifiedSlug) {
|
|
10624
|
+
shots = await uploadShots();
|
|
10625
|
+
try {
|
|
10626
|
+
await opts.taskless.addComment(
|
|
10627
|
+
verifiedSlug,
|
|
10628
|
+
`\u{1F9EA} \u0412\u0415\u0420\u0414\u0418\u041A\u0422 \u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420\u0410 ${run.name}: **${verdictText}**
|
|
10629
|
+
|
|
10630
|
+
${(summary ?? "").trim()}${shots.warning}`,
|
|
10631
|
+
void 0,
|
|
10632
|
+
shots.attachments
|
|
10633
|
+
);
|
|
10634
|
+
} catch (e) {
|
|
10635
|
+
commentWarning = e instanceof Error ? e.message : String(e);
|
|
10636
|
+
await store.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 ${verifiedSlug} \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
|
|
10637
|
+
}
|
|
10638
|
+
} else {
|
|
10639
|
+
commentWarning = opts.taskless ? "\u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430" : "\u043A\u043B\u0438\u0435\u043D\u0442 Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D";
|
|
10640
|
+
await store.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
|
|
10641
|
+
}
|
|
10642
|
+
const trustedPrUrl = verified?.pr_url ?? pr_url;
|
|
10643
|
+
await store.updateRun(null, { status: "review", pr_url: trustedPrUrl });
|
|
10644
|
+
await store.addEvent(null, "pr_opened", trustedPrUrl);
|
|
10645
|
+
const commentNote = commentWarning ? `
|
|
10646
|
+
|
|
10647
|
+
\u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u041D\u0415 \u0443\u0435\u0445\u0430\u043B (${commentWarning}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0435. \u041F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0435\u0433\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR: \`gh pr comment ${trustedPrUrl} --body "${verdictText} \u2026"\`.` : "";
|
|
10648
|
+
return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0437\u0430\u043F\u0438\u0441\u0430\u043D (${verdictText})${shotsNote(shots)}${commentNote}` }] };
|
|
10649
|
+
}
|
|
10229
10650
|
if (isGithubPrUrl(pr_url)) {
|
|
10230
10651
|
const report = (summary ?? "").trim();
|
|
10231
10652
|
if (!report) {
|
|
@@ -10434,6 +10855,8 @@ function ensureRunHydrated(db, runId, info) {
|
|
|
10434
10855
|
kind: info.kind,
|
|
10435
10856
|
name: info.name,
|
|
10436
10857
|
reviews_run_id: info.reviews_run_id,
|
|
10858
|
+
verifies_run_id: info.verifies_run_id,
|
|
10859
|
+
// TASWM-344
|
|
10437
10860
|
hero_run_id: info.hero_run_id
|
|
10438
10861
|
});
|
|
10439
10862
|
}
|
|
@@ -10447,12 +10870,14 @@ async function runDroneServer(runId) {
|
|
|
10447
10870
|
const expectedTaskSlug = process.env.SWARM_TASK_SLUG || void 0;
|
|
10448
10871
|
const expectedKind = toRunKind(process.env.SWARM_RUN_KIND);
|
|
10449
10872
|
const spawnReviewsRunId = Number(process.env.SWARM_REVIEWS_RUN_ID);
|
|
10873
|
+
const spawnVerifiesRunId = Number(process.env.SWARM_VERIFIES_RUN_ID);
|
|
10450
10874
|
const common = {
|
|
10451
10875
|
taskless,
|
|
10452
10876
|
runKind: expectedKind,
|
|
10453
10877
|
prHeadBranch,
|
|
10454
10878
|
...expectedTaskSlug ? { expectedTaskSlug } : {},
|
|
10455
|
-
...Number.isInteger(spawnReviewsRunId) && spawnReviewsRunId > 0 ? { reviewsRunId: spawnReviewsRunId } : {}
|
|
10879
|
+
...Number.isInteger(spawnReviewsRunId) && spawnReviewsRunId > 0 ? { reviewsRunId: spawnReviewsRunId } : {},
|
|
10880
|
+
...Number.isInteger(spawnVerifiesRunId) && spawnVerifiesRunId > 0 ? { verifiesRunId: spawnVerifiesRunId } : {}
|
|
10456
10881
|
};
|
|
10457
10882
|
const channelUrl = process.env.SWARM_DRONE_CHANNEL_URL;
|
|
10458
10883
|
const channelToken = process.env.SWARM_DRONE_CHANNEL_TOKEN;
|
|
@@ -10473,6 +10898,7 @@ async function runDroneServer(runId) {
|
|
|
10473
10898
|
kind: expectedKind,
|
|
10474
10899
|
name: process.env.SWARM_RUN_NAME || void 0,
|
|
10475
10900
|
reviews_run_id: process.env.SWARM_REVIEWS_RUN_ID ? Number(process.env.SWARM_REVIEWS_RUN_ID) : null,
|
|
10901
|
+
verifies_run_id: process.env.SWARM_VERIFIES_RUN_ID ? Number(process.env.SWARM_VERIFIES_RUN_ID) : null,
|
|
10476
10902
|
hero_run_id: process.env.SWARM_HERO_RUN_ID ? Number(process.env.SWARM_HERO_RUN_ID) : null
|
|
10477
10903
|
});
|
|
10478
10904
|
const store = localDroneStore2(db, runId, { heartbeatPath: HEARTBEAT_PATH2, heartbeatStaleMs: HEARTBEAT_STALE_MS2 });
|
|
@@ -10668,6 +11094,9 @@ function joinCapped(labels) {
|
|
|
10668
11094
|
joined = labels.slice(0, Math.max(cut, 1)).join(", ");
|
|
10669
11095
|
return `${joined} \u0438 \u0435\u0449\u0451 ${labels.length - Math.max(cut, 1)}`;
|
|
10670
11096
|
}
|
|
11097
|
+
function autoReviewOnFor(flag, repo) {
|
|
11098
|
+
return typeof flag === "function" ? flag(repo) : flag;
|
|
11099
|
+
}
|
|
10671
11100
|
function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
10672
11101
|
const candidates = [];
|
|
10673
11102
|
const allRuns = db.listRuns().filter((r) => r.superseded_by == null);
|
|
@@ -10802,7 +11231,7 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
10802
11231
|
...hrefOf(run.task_url)
|
|
10803
11232
|
})
|
|
10804
11233
|
});
|
|
10805
|
-
} else if (opts.autoReviewEnabled && run.review_verdict == null && db.reviewerCountFor(run.id) === 0) {
|
|
11234
|
+
} else if (autoReviewOnFor(opts.autoReviewEnabled, run.repo) && run.review_verdict == null && db.reviewerCountFor(run.id) === 0) {
|
|
10806
11235
|
candidates.push({
|
|
10807
11236
|
key: `pr_no_reviewer:${run.id}`,
|
|
10808
11237
|
minDurationMs: thresholds.prNoReviewerMs,
|
|
@@ -10989,9 +11418,11 @@ init_droneChannel();
|
|
|
10989
11418
|
init_procList();
|
|
10990
11419
|
|
|
10991
11420
|
// src/mcpResilient.ts
|
|
11421
|
+
init_linkFailure();
|
|
10992
11422
|
init_taskless();
|
|
10993
11423
|
var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
|
|
10994
11424
|
var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
|
|
11425
|
+
var DEFAULT_RETRY_DELAYS_MS = [200, 800];
|
|
10995
11426
|
function errorResult(reason) {
|
|
10996
11427
|
return { isError: true, content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }] };
|
|
10997
11428
|
}
|
|
@@ -10999,11 +11430,16 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
10999
11430
|
const connect = opts.connect ?? (() => connectSwarmMcp());
|
|
11000
11431
|
const backoff = opts.backoffMs?.length ? opts.backoffMs : DEFAULT_BACKOFF_MS;
|
|
11001
11432
|
const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
|
|
11433
|
+
const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
11434
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
11002
11435
|
let caller = null;
|
|
11003
11436
|
let lastTryAt = 0;
|
|
11004
11437
|
let failures = 0;
|
|
11005
11438
|
let downSince = null;
|
|
11006
11439
|
let downReported = false;
|
|
11440
|
+
let lastConnectReason = null;
|
|
11441
|
+
let lastOkAt = null;
|
|
11442
|
+
let lastCallReason = null;
|
|
11007
11443
|
function maybeReportDown() {
|
|
11008
11444
|
if (downReported || downSince === null) return;
|
|
11009
11445
|
const downMs = Date.now() - downSince;
|
|
@@ -11011,45 +11447,65 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
11011
11447
|
downReported = true;
|
|
11012
11448
|
opts.onDown?.(downMs);
|
|
11013
11449
|
}
|
|
11014
|
-
async function ensureCaller() {
|
|
11450
|
+
async function ensureCaller(force = false) {
|
|
11015
11451
|
if (caller) return caller;
|
|
11016
11452
|
const now = Date.now();
|
|
11017
11453
|
const wait = backoff[Math.max(0, Math.min(failures - 1, backoff.length - 1))];
|
|
11018
|
-
if (lastTryAt !== 0 && now - lastTryAt < wait) return null;
|
|
11019
|
-
lastTryAt = now;
|
|
11454
|
+
if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
|
|
11455
|
+
if (!force) lastTryAt = now;
|
|
11020
11456
|
let fresh = null;
|
|
11021
11457
|
try {
|
|
11022
11458
|
fresh = await connect();
|
|
11023
|
-
|
|
11459
|
+
if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
|
|
11460
|
+
} catch (e) {
|
|
11024
11461
|
fresh = null;
|
|
11462
|
+
lastConnectReason = describeError(e);
|
|
11025
11463
|
}
|
|
11026
11464
|
if (fresh) {
|
|
11027
11465
|
caller = fresh;
|
|
11028
11466
|
failures = 0;
|
|
11467
|
+
lastConnectReason = null;
|
|
11029
11468
|
if (downReported) opts.onUp?.();
|
|
11030
11469
|
downSince = null;
|
|
11031
11470
|
downReported = false;
|
|
11032
11471
|
return caller;
|
|
11033
11472
|
}
|
|
11034
|
-
failures += 1;
|
|
11473
|
+
if (!force) failures += 1;
|
|
11035
11474
|
if (downSince === null) downSince = now;
|
|
11036
11475
|
return null;
|
|
11037
11476
|
}
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11477
|
+
const call = async (name, args) => {
|
|
11478
|
+
for (let attempt = 0; ; attempt++) {
|
|
11479
|
+
const active = await ensureCaller(attempt > 0);
|
|
11480
|
+
if (!active) {
|
|
11481
|
+
maybeReportDown();
|
|
11482
|
+
return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
|
|
11483
|
+
}
|
|
11484
|
+
try {
|
|
11485
|
+
const res = await active(name, args);
|
|
11486
|
+
lastOkAt = Date.now();
|
|
11487
|
+
lastCallReason = null;
|
|
11488
|
+
return res;
|
|
11489
|
+
} catch (e) {
|
|
11490
|
+
caller = null;
|
|
11491
|
+
if (downSince === null) downSince = Date.now();
|
|
11492
|
+
const { kind, detail: reason } = explainLinkFailure(e);
|
|
11493
|
+
lastCallReason = reason;
|
|
11494
|
+
const delay = retryDelays[attempt];
|
|
11495
|
+
if (delay === void 0 || kind !== "offline") {
|
|
11496
|
+
maybeReportDown();
|
|
11497
|
+
return errorResult(reason);
|
|
11498
|
+
}
|
|
11499
|
+
opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
|
|
11500
|
+
await sleep2(delay);
|
|
11501
|
+
}
|
|
11051
11502
|
}
|
|
11052
11503
|
};
|
|
11504
|
+
return Object.assign(call, {
|
|
11505
|
+
// Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
|
|
11506
|
+
// подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
|
|
11507
|
+
link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
|
|
11508
|
+
});
|
|
11053
11509
|
}
|
|
11054
11510
|
|
|
11055
11511
|
// src/overmind.ts
|
|
@@ -11085,6 +11541,73 @@ function workspaceAccessLines(input) {
|
|
|
11085
11541
|
return lines;
|
|
11086
11542
|
}
|
|
11087
11543
|
|
|
11544
|
+
// src/overmind.ts
|
|
11545
|
+
init_authState();
|
|
11546
|
+
|
|
11547
|
+
// src/swarmStatus.ts
|
|
11548
|
+
init_linkFailure();
|
|
11549
|
+
|
|
11550
|
+
// src/release.ts
|
|
11551
|
+
init_src();
|
|
11552
|
+
init_claudeBin();
|
|
11553
|
+
var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
11554
|
+
var LATEST_TIMEOUT_MS = 4e3;
|
|
11555
|
+
async function fetchLatestVersion(opts = {}) {
|
|
11556
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
11557
|
+
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
11558
|
+
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
11559
|
+
const ctrl = new AbortController();
|
|
11560
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
11561
|
+
try {
|
|
11562
|
+
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
11563
|
+
signal: ctrl.signal,
|
|
11564
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
11565
|
+
});
|
|
11566
|
+
if (!res.ok) return null;
|
|
11567
|
+
const body = await res.json();
|
|
11568
|
+
return typeof body.version === "string" && body.version ? body.version : null;
|
|
11569
|
+
} catch {
|
|
11570
|
+
return null;
|
|
11571
|
+
} finally {
|
|
11572
|
+
clearTimeout(timer);
|
|
11573
|
+
}
|
|
11574
|
+
}
|
|
11575
|
+
function isOutdated(current, latest) {
|
|
11576
|
+
if (!latest || !current) return false;
|
|
11577
|
+
return compareVersionDirs(latest, current) > 0;
|
|
11578
|
+
}
|
|
11579
|
+
function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
11580
|
+
if (!isOutdated(current, latest)) return null;
|
|
11581
|
+
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
11582
|
+
}
|
|
11583
|
+
|
|
11584
|
+
// src/swarmStatus.ts
|
|
11585
|
+
init_src();
|
|
11586
|
+
function classifyLinkError(e) {
|
|
11587
|
+
const { kind, detail } = explainLinkFailure(e);
|
|
11588
|
+
const short2 = detail.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
11589
|
+
const map = {
|
|
11590
|
+
unauthorized: "revoked",
|
|
11591
|
+
missing_tool: "server",
|
|
11592
|
+
offline: "network",
|
|
11593
|
+
rejected: "server",
|
|
11594
|
+
unknown: "down"
|
|
11595
|
+
};
|
|
11596
|
+
return { kind: map[kind], detail: short2 };
|
|
11597
|
+
}
|
|
11598
|
+
function composeLink(input) {
|
|
11599
|
+
const base3 = { lastOkAt: input.transport?.lastOkAt != null ? new Date(input.transport.lastOkAt).toISOString() : null, pending: input.pending, api: input.api };
|
|
11600
|
+
if (input.authDenied) return { kind: "revoked", detail: input.authDenied, ...base3 };
|
|
11601
|
+
if (!input.transport) return { kind: "no-prod", detail: "sqlite-\u0440\u0435\u0436\u0438\u043C \u2014 \u043F\u0440\u043E\u0434\u0430 \u0443 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435", ...base3 };
|
|
11602
|
+
if (input.transport.connected) return { kind: "ok", detail: "", ...base3 };
|
|
11603
|
+
if (input.transport.lastOkAt == null && input.transport.downSince == null && !input.transport.lastError) {
|
|
11604
|
+
return { kind: "starting", detail: "\u0434\u0435\u043C\u043E\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F \u2014 \u0432 \u043F\u0440\u043E\u0434 \u0435\u0449\u0451 \u043D\u0435 \u0445\u043E\u0434\u0438\u043B", ...base3 };
|
|
11605
|
+
}
|
|
11606
|
+
const why = input.transport.lastError;
|
|
11607
|
+
if (!why) return { kind: "down", detail: "", ...base3 };
|
|
11608
|
+
return { ...classifyLinkError(new Error(why)), ...base3 };
|
|
11609
|
+
}
|
|
11610
|
+
|
|
11088
11611
|
// src/overmind.ts
|
|
11089
11612
|
init_workspace();
|
|
11090
11613
|
init_attachments();
|
|
@@ -11219,10 +11742,23 @@ function makeMergeGateResolver(deps) {
|
|
|
11219
11742
|
const run = findRunByPr(deps.listRuns(), ref);
|
|
11220
11743
|
if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
|
|
11221
11744
|
const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
|
|
11222
|
-
const decision = computeMergeGate({
|
|
11745
|
+
const decision = computeMergeGate({
|
|
11746
|
+
hasRun: true,
|
|
11747
|
+
autoReview,
|
|
11748
|
+
reviewVerdict: run.review_verdict,
|
|
11749
|
+
autoVerify: deps.autoVerify === true,
|
|
11750
|
+
verifyVerdict: run.verify_verdict ?? null
|
|
11751
|
+
});
|
|
11223
11752
|
return {
|
|
11224
11753
|
...decision,
|
|
11225
|
-
run: {
|
|
11754
|
+
run: {
|
|
11755
|
+
id: run.id,
|
|
11756
|
+
task_slug: run.task_slug,
|
|
11757
|
+
name: run.name,
|
|
11758
|
+
pr_url: run.pr_url,
|
|
11759
|
+
review_verdict: run.review_verdict,
|
|
11760
|
+
verify_verdict: run.verify_verdict ?? null
|
|
11761
|
+
}
|
|
11226
11762
|
};
|
|
11227
11763
|
};
|
|
11228
11764
|
}
|
|
@@ -11665,7 +12201,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
11665
12201
|
// package.json
|
|
11666
12202
|
var package_default = {
|
|
11667
12203
|
name: "@taskless-app/swarm",
|
|
11668
|
-
version: "0.6.
|
|
12204
|
+
version: "0.6.13",
|
|
11669
12205
|
type: "module",
|
|
11670
12206
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11671
12207
|
keywords: [
|
|
@@ -11715,7 +12251,7 @@ var package_default = {
|
|
|
11715
12251
|
execa: "^10.0.0",
|
|
11716
12252
|
zod: "3.25.76"
|
|
11717
12253
|
},
|
|
11718
|
-
gitHead: "
|
|
12254
|
+
gitHead: "ea3913bf66c52d730c25ad62b7090621a5006ee7"
|
|
11719
12255
|
};
|
|
11720
12256
|
|
|
11721
12257
|
// src/version.ts
|
|
@@ -11727,6 +12263,7 @@ init_storm();
|
|
|
11727
12263
|
init_resources();
|
|
11728
12264
|
init_config();
|
|
11729
12265
|
init_droneChannel();
|
|
12266
|
+
init_authState();
|
|
11730
12267
|
init_oauth();
|
|
11731
12268
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
11732
12269
|
var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
|
|
@@ -11735,6 +12272,15 @@ var sqliteIso = (ts) => {
|
|
|
11735
12272
|
const ms = sqliteMs(ts);
|
|
11736
12273
|
return Number.isNaN(ms) ? ts : new Date(ms).toISOString();
|
|
11737
12274
|
};
|
|
12275
|
+
function stormFacts(db) {
|
|
12276
|
+
if (!db.stormActive()) return null;
|
|
12277
|
+
const since = db.currentStormStartedAt();
|
|
12278
|
+
const recs = since ? db.stormProbeRecordsSince(since) : [];
|
|
12279
|
+
return {
|
|
12280
|
+
reason: lastProbeReason(recs) === "limit" ? "limit" : "provider",
|
|
12281
|
+
hint: recs.length ? recs[recs.length - 1].resetHint ?? null : null
|
|
12282
|
+
};
|
|
12283
|
+
}
|
|
11738
12284
|
function roySnapshot(db, now = Date.now(), overmindLog = [], paused = false, daemonVersion = null, restart = null, selfHost = null) {
|
|
11739
12285
|
const all = db.listRuns();
|
|
11740
12286
|
const c = (s) => all.filter((r) => r.status === s).length;
|
|
@@ -11744,11 +12290,11 @@ function roySnapshot(db, now = Date.now(), overmindLog = [], paused = false, dae
|
|
|
11744
12290
|
if (r.status === "done" || r.status === "stopped" || r.status === "failed") return sqliteMs(r.updated_at) >= now - DAY_MS;
|
|
11745
12291
|
return true;
|
|
11746
12292
|
});
|
|
11747
|
-
const
|
|
12293
|
+
const storm = stormFacts(db);
|
|
12294
|
+
const stormActive = storm !== null;
|
|
11748
12295
|
const stormSince = db.currentStormStartedAt();
|
|
11749
|
-
const
|
|
11750
|
-
const
|
|
11751
|
-
const stormResetHint = stormRecs.length ? stormRecs[stormRecs.length - 1].resetHint ?? null : null;
|
|
12296
|
+
const stormReason = storm?.reason ?? null;
|
|
12297
|
+
const stormResetHint = storm?.hint ?? null;
|
|
11752
12298
|
return {
|
|
11753
12299
|
metrics: {
|
|
11754
12300
|
running: c("running"),
|
|
@@ -13031,7 +13577,17 @@ function daemonStatus(deps) {
|
|
|
13031
13577
|
const counts = {};
|
|
13032
13578
|
for (const r of deps.db.listRuns()) counts[r.status] = (counts[r.status] ?? 0) + 1;
|
|
13033
13579
|
const live = deps.liveSessionIds?.() ?? [];
|
|
13580
|
+
const ver = deps.daemonVersion?.() ?? null;
|
|
13581
|
+
const queuedReady = deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0).length;
|
|
13034
13582
|
return {
|
|
13583
|
+
api: deps.api ?? TASKLESS_API,
|
|
13584
|
+
queuedReady,
|
|
13585
|
+
startedAt: ver?.startedAt ?? null,
|
|
13586
|
+
latestVersion: ver?.latestVersion ?? null,
|
|
13587
|
+
link: deps.link?.() ?? null,
|
|
13588
|
+
questions: deps.db.openQuestions().length,
|
|
13589
|
+
maxDrones: deps.maxDrones?.() ?? null,
|
|
13590
|
+
storm: stormFacts(deps.db),
|
|
13035
13591
|
hostId: deps.selfHost?.hostId ?? null,
|
|
13036
13592
|
hostName: deps.selfHost?.hostName ?? null,
|
|
13037
13593
|
backend: SWARM_BACKEND,
|
|
@@ -13041,7 +13597,9 @@ function daemonStatus(deps) {
|
|
|
13041
13597
|
counts,
|
|
13042
13598
|
live,
|
|
13043
13599
|
version: PACKAGE_VERSION,
|
|
13044
|
-
repos: deps.repoMap?.() ?? []
|
|
13600
|
+
repos: deps.repoMap?.() ?? [],
|
|
13601
|
+
// TASWM-324: «нас не пускают» — то же состояние процесса, что кричит рамкой в лог.
|
|
13602
|
+
authDenied: authStateLine()
|
|
13045
13603
|
};
|
|
13046
13604
|
}
|
|
13047
13605
|
function eventsStream(db, narratorLog, control, daemonVersion, restart, liveSessionIds2, selfHost) {
|
|
@@ -13141,7 +13699,12 @@ function makeHandler(deps) {
|
|
|
13141
13699
|
if (p === "/merge-gate") {
|
|
13142
13700
|
const ref = url.searchParams.get("pr") ?? "";
|
|
13143
13701
|
if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
|
|
13144
|
-
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
|
|
13702
|
+
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
|
|
13703
|
+
listRuns: () => deps.db.listRuns(),
|
|
13704
|
+
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
13705
|
+
autoVerify: SWARM_AUTO_VERIFY
|
|
13706
|
+
// TASWM-344
|
|
13707
|
+
});
|
|
13145
13708
|
return Response.json(await resolve4(ref));
|
|
13146
13709
|
}
|
|
13147
13710
|
if (p === "/resources") return Response.json(hostResourceSnapshot());
|
|
@@ -13301,40 +13864,6 @@ function closeWebServer(server) {
|
|
|
13301
13864
|
});
|
|
13302
13865
|
}
|
|
13303
13866
|
|
|
13304
|
-
// src/release.ts
|
|
13305
|
-
init_src();
|
|
13306
|
-
init_claudeBin();
|
|
13307
|
-
var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
13308
|
-
var LATEST_TIMEOUT_MS = 4e3;
|
|
13309
|
-
async function fetchLatestVersion(opts = {}) {
|
|
13310
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
13311
|
-
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
13312
|
-
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
13313
|
-
const ctrl = new AbortController();
|
|
13314
|
-
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
13315
|
-
try {
|
|
13316
|
-
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
13317
|
-
signal: ctrl.signal,
|
|
13318
|
-
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
13319
|
-
});
|
|
13320
|
-
if (!res.ok) return null;
|
|
13321
|
-
const body = await res.json();
|
|
13322
|
-
return typeof body.version === "string" && body.version ? body.version : null;
|
|
13323
|
-
} catch {
|
|
13324
|
-
return null;
|
|
13325
|
-
} finally {
|
|
13326
|
-
clearTimeout(timer);
|
|
13327
|
-
}
|
|
13328
|
-
}
|
|
13329
|
-
function isOutdated(current, latest) {
|
|
13330
|
-
if (!latest || !current) return false;
|
|
13331
|
-
return compareVersionDirs(latest, current) > 0;
|
|
13332
|
-
}
|
|
13333
|
-
function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
13334
|
-
if (!isOutdated(current, latest)) return null;
|
|
13335
|
-
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
13336
|
-
}
|
|
13337
|
-
|
|
13338
13867
|
// src/daemonVersion.ts
|
|
13339
13868
|
function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
|
|
13340
13869
|
return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
|
|
@@ -14459,7 +14988,14 @@ async function prGateForReviewer(deps, run, prUrl, when) {
|
|
|
14459
14988
|
}
|
|
14460
14989
|
}
|
|
14461
14990
|
function doneMergedPrForSlug(db, slug) {
|
|
14462
|
-
const done = db.listRuns("done").find(
|
|
14991
|
+
const done = db.listRuns("done").find(
|
|
14992
|
+
(r) => r.task_slug === slug && r.pr_url && r.kind !== "hero" && r.resolves_run_id == null && r.reviews_run_id == null && // TASWM-446: и НЕ верификатор. «done = смержено» верно только для прогона, который PR
|
|
14993
|
+
// делал; верификатор же заканчивает в `done`, держа `pr_url` ПРОВЕРЯЕМОГО — открытого —
|
|
14994
|
+
// PR, и его завершение читалось как доказательство мержа. Дальше pre-spawn гейт закрывал
|
|
14995
|
+
// исполнителя как дубль: 17.08 так погибли пять живых прогонов подряд, а задача после
|
|
14996
|
+
// этого не бралась вовсе — следующий дрон умирал на том же месте.
|
|
14997
|
+
r.verifies_run_id == null
|
|
14998
|
+
);
|
|
14463
14999
|
return done?.pr_url ?? null;
|
|
14464
15000
|
}
|
|
14465
15001
|
async function closeDuplicateTaskRun(deps, run, mergedUrl) {
|
|
@@ -14478,7 +15014,8 @@ async function closeDuplicateTaskRun(deps, run, mergedUrl) {
|
|
|
14478
15014
|
async function closeMergedTaskRuns(deps) {
|
|
14479
15015
|
const bySlug = /* @__PURE__ */ new Map();
|
|
14480
15016
|
for (const run of deps.db.listRuns()) {
|
|
14481
|
-
if (run.kind === "hero" || run.resolves_run_id != null || run.reviews_run_id != null)
|
|
15017
|
+
if (run.kind === "hero" || run.resolves_run_id != null || run.reviews_run_id != null || run.verifies_run_id != null)
|
|
15018
|
+
continue;
|
|
14482
15019
|
let group = bySlug.get(run.task_slug);
|
|
14483
15020
|
if (!group) bySlug.set(run.task_slug, group = []);
|
|
14484
15021
|
group.push(run);
|
|
@@ -14521,20 +15058,57 @@ async function closeMergedTaskRuns(deps) {
|
|
|
14521
15058
|
}
|
|
14522
15059
|
async function closeOrphanedReviewers(deps) {
|
|
14523
15060
|
for (const rv of deps.db.listRuns()) {
|
|
14524
|
-
|
|
15061
|
+
const checkedId = rv.kind === "reviewer" ? rv.reviews_run_id : rv.kind === "verifier" ? rv.verifies_run_id : null;
|
|
15062
|
+
if (checkedId == null) continue;
|
|
14525
15063
|
if (!REVIEWER_ALIVE_STATUSES.has(rv.status)) continue;
|
|
14526
|
-
const parent = deps.db.getRun(
|
|
15064
|
+
const parent = deps.db.getRun(checkedId);
|
|
14527
15065
|
if (parent && !isTerminalStatus(parent.status)) continue;
|
|
14528
15066
|
try {
|
|
14529
15067
|
const why = parent ? `\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442 (${STATUS_META[parent.status]?.label ?? parent.status})` : "\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0438\u0441\u0447\u0435\u0437";
|
|
14530
|
-
await closeReviewerRun(deps, rv, `${why} \u2014 PR \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u0440\
|
|
14531
|
-
if (parent) deps.db.addEvent(parent.id, "step",
|
|
15068
|
+
await closeReviewerRun(deps, rv, `${why} \u2014 PR \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043D\u0430 \u0440\u0435\u0432\u044C\u044E, \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
|
|
15069
|
+
if (parent) deps.db.addEvent(parent.id, "step", `${rv.kind === "verifier" ? "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440" : "\u0440\u0435\u0432\u044C\u044E\u0435\u0440"} ${rv.name} (#${rv.id}) \u0437\u0430\u043A\u0440\u044B\u0442: ${why}`);
|
|
14532
15070
|
narrate(deps, `${droneLabel(rv)} \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u043D\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0435 \u2014 \u0433\u0430\u0448\u0443 \u0441\u0430\u043C, \u0441\u043B\u043E\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D.`);
|
|
14533
15071
|
} catch (e) {
|
|
14534
15072
|
console.error(`[overmind] \u0437\u0430\u043A\u0440\u044B\u0442\u0438\u0435 \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 #${rv.id} \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:`, e);
|
|
14535
15073
|
}
|
|
14536
15074
|
}
|
|
14537
15075
|
}
|
|
15076
|
+
var VERIFY_SPAWN_LIMIT = Number(process.env.SWARM_VERIFY_SPAWN_LIMIT) || 2;
|
|
15077
|
+
var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
|
|
15078
|
+
function countActiveVerifiers(db) {
|
|
15079
|
+
return db.listRuns().filter((r) => r.kind === "verifier" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
|
|
15080
|
+
}
|
|
15081
|
+
async function spawnVerifiersForOpenPrs(deps, stormActive) {
|
|
15082
|
+
if (!deps.spawnVerifier) return;
|
|
15083
|
+
if (stormActive) return;
|
|
15084
|
+
for (const run of deps.db.listRuns("review")) {
|
|
15085
|
+
try {
|
|
15086
|
+
if (run.kind !== "drone") continue;
|
|
15087
|
+
if (run.reviews_run_id != null || run.resolves_run_id != null || run.verifies_run_id != null) continue;
|
|
15088
|
+
if (!run.pr_url || !isGithubPrUrl(run.pr_url)) continue;
|
|
15089
|
+
if (run.verify_verdict != null) continue;
|
|
15090
|
+
if (deps.db.verifierCountFor(run.id) > 0) continue;
|
|
15091
|
+
const attempts = deps.db.verifierAttemptCountFor(run.id);
|
|
15092
|
+
if (attempts >= VERIFY_SPAWN_LIMIT) {
|
|
15093
|
+
if (!deps.db.events(run.id).some((e) => (e.text ?? "").includes(VERIFY_ESCALATION_MARK))) {
|
|
15094
|
+
deps.db.addEvent(run.id, "step", `${VERIFY_ESCALATION_MARK}: ${attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
|
|
15095
|
+
narrate(deps, `\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F ${droneLabel(run)} \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0437\u0430 ${attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`);
|
|
15096
|
+
}
|
|
15097
|
+
continue;
|
|
15098
|
+
}
|
|
15099
|
+
if (deps.resourcesOk && !deps.resourcesOk()) continue;
|
|
15100
|
+
if (deps.reviewCap !== void 0 && countActiveVerifiers(deps.db) >= deps.reviewCap) continue;
|
|
15101
|
+
const st = await deps.prStatus(run.pr_url).catch(() => null);
|
|
15102
|
+
if (prLiveness(st) !== "open") continue;
|
|
15103
|
+
if (st?.isDraft || st?.conflicting) continue;
|
|
15104
|
+
await deps.spawnVerifier(run, st?.files ?? []);
|
|
15105
|
+
narrate(deps, `PR ${droneLabel(run)} \u043E\u0442\u043A\u0440\u044B\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430: \u043F\u0443\u0441\u0442\u044C \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u0435\u0442 \u043E\u043F\u0440\u043E\u0432\u0435\u0440\u0433\u043D\u0443\u0442\u044C \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435.`);
|
|
15106
|
+
} catch (e) {
|
|
15107
|
+
deps.db.addEvent(run.id, "step", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${String(e)}`);
|
|
15108
|
+
console.error(`[overmind] \u043F\u043E\u0434\u044A\u0451\u043C \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u0434\u043B\u044F #${run.id} (${run.task_slug}) \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
15109
|
+
}
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
14538
15112
|
var CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0446\u0435\u0441\u0441) \u2014 \u0442\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log` \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0441\u0432\u043E\u0438\u0445 \u0448\u0430\u0433\u043E\u0432: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438 \u0441 \u043C\u0435\u0441\u0442\u0430 \u043E\u0431\u0440\u044B\u0432\u0430. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u041A\u041E\u041C\u041C\u0418\u0422\u042C \u0418 \u041F\u0423\u0428\u042C \u043F\u043E\u0441\u043B\u0435 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0448\u0430\u0433\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url). \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0435\u0440\u0435\u0437 MCP-\u0442\u0443\u043B ask.";
|
|
14539
15113
|
function buildCiWakeNudge(summary) {
|
|
14540
15114
|
return [
|
|
@@ -14711,6 +15285,16 @@ function ciTrustLine(prUrl, verifyMode) {
|
|
|
14711
15285
|
}
|
|
14712
15286
|
return `\u041D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 \u0442\u0435\u0441\u0442-\u0441\u044C\u044E\u0442/typecheck \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u044D\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043B CI \u043D\u0430 \u044D\u0442\u043E\u043C \u0436\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u0435 (\u0434\u0432\u043E\u0439\u043D\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043E\u0442\u043C\u0435\u043D\u0451\u043D, TASWM-249). \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0435\u0433\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0441\u0430\u043C: ${readChecks}. \u0414\u0430\u043B\u044C\u0448\u0435 \u0442\u0440\u0438 \u0440\u0430\u0437\u043D\u044B\u0445 \u0441\u043B\u0443\u0447\u0430\u044F: \u2460 \u0445\u043E\u0442\u044C \u043E\u0434\u0438\u043D \u0447\u0435\u043A \u0435\u0449\u0451 PENDING \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u043E\u043E\u0431\u0449\u0435 \u043D\u0435 \u0432\u044B\u043D\u043E\u0441\u0438: \u043D\u0435 \u043F\u043E\u0441\u0442\u0438 REQUEST CHANGES, \u043D\u0435 \u0437\u043E\u0432\u0438 done(), \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \u0437\u0430\u043C\u0435\u0442\u0438\u0442, \u0447\u0442\u043E \u0442\u044B \u0436\u0434\u0451\u0448\u044C CI, \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442 \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E, \u043A\u043E\u0433\u0434\u0430 \u0432\u0441\u0435 \u0447\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0442\u0441\u044F (\u044D\u0442\u043E \u0448\u0442\u0430\u0442\u043D\u044B\u0439 \u043F\u0443\u0442\u044C, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0437\u0432\u0430\u0442\u044C \u043D\u0435 \u043D\u0443\u0436\u043D\u043E). \u2461 \u0432\u0441\u0435 \u0447\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C \u0438 \u0435\u0441\u0442\u044C FAIL \u2014 REQUEST CHANGES, \u043D\u0430\u0437\u043E\u0432\u0438 \u043F\u043E\u0438\u043C\u0451\u043D\u043D\u043E, \u0447\u0442\u043E \u0443\u043F\u0430\u043B\u043E. \u2462 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0447\u0435\u043A\u043E\u0432 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0442\u043E\u0436\u0435 REQUEST CHANGES: \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C (\u0443 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=ci, \u0442\u043E \u0435\u0441\u0442\u044C CI \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C). ${dontTrustAuthor}`;
|
|
14713
15287
|
}
|
|
15288
|
+
var REVIEW_DEFECT_CLASSES = [
|
|
15289
|
+
"\u041A\u043B\u0430\u0441\u0441\u044B \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u0432 \u2014 \u043F\u0440\u043E\u0439\u0434\u0438 \u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u0438 \u0434\u0430\u0439 \u043E\u0442\u0432\u0435\u0442 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435; \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u2014 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442, \u043D\u043E \u043D\u0430\u0437\u0432\u0430\u043D\u043D\u044B\u0439 \u0438 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0432 \u043F\u043E\u043B\u0441\u0442\u0440\u043E\u043A\u0438 (\xAB\u0440\u0443\u0447\u0435\u043A \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E\xBB, \xAB\u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u0439 \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442\u043E\xBB). \u0428\u0435\u0441\u0442\u044C \u0440\u0430\u0437 \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u0431\u0435\u0437 \u043F\u0440\u0438\u0447\u0438\u043D \u2014 \u043D\u0435 \u0440\u0435\u0432\u044C\u044E, \u0430 \u043E\u0442\u043F\u0438\u0441\u043A\u0430:",
|
|
15290
|
+
"- [ ] \u041F\u0440\u0430\u0432\u0430 \u0430\u043A\u0442\u043E\u0440\u0430 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441: \u043A\u0442\u043E \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043A\u0438 \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u0437\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0440\u0443\u0447\u043A\u0443 \u0438\u043B\u0438 \u043D\u0430\u0436\u0430\u0442\u044C \u043A\u043D\u043E\u043F\u043A\u0443 \u2014 \u0447\u0435\u043B\u043E\u0432\u0435\u043A, \u0434\u0440\u043E\u043D \u0440\u043E\u044F, \u0433\u043E\u0441\u0442\u044C, \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A \u0427\u0423\u0416\u041E\u0413\u041E \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430? \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u043E\u0434\u0438\u043B\u0430 \u0440\u0430\u0434\u0438 \u0420\u0415\u0428\u0415\u041D\u0418\u042F \u0427\u0415\u041B\u041E\u0412\u0415\u041A\u0410 (\u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u044F, \u0430\u043F\u0440\u0443\u0432, \u043E\u043F\u043B\u0430\u0442\u0430, \u0440\u0430\u0441\u0441\u044B\u043B\u043A\u0430), \u043E\u0431\u044F\u0437\u0430\u043D\u043E \u043E\u0442\u0431\u0438\u0432\u0430\u0442\u044C \u0430\u0433\u0435\u043D\u0442\u0430 \u044F\u0432\u043D\u043E (denyAgentActor \u0438 \u0430\u043D\u0430\u043B\u043E\u0433\u0438), \u0430 \u043D\u0435 \u043F\u043E\u043B\u0430\u0433\u0430\u0442\u044C\u0441\u044F \u043D\u0430 \u0442\u043E, \u0447\u0442\u043E \u0443 \u043D\u0435\u0433\u043E \u0440\u043E\u043B\u044C \u043F\u043E\u0436\u0438\u0436\u0435 \u2014 \u0443 \u0434\u0440\u043E\u043D\u0430 \u043E\u043D\u0430 \u043E\u0431\u044B\u0447\u043D\u043E \u043A\u0430\u043A \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430. \u041B\u044E\u0431\u043E\u0439 id, \u043F\u0440\u0438\u0435\u0445\u0430\u0432\u0448\u0438\u0439 \u0432 \u0442\u0435\u043B\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u043E\u0431\u044F\u0437\u0430\u043D \u0441\u0432\u0435\u0440\u044F\u0442\u044C\u0441\u044F \u0441 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u043E\u043C \u0437\u0432\u043E\u043D\u044F\u0449\u0435\u0433\u043E: \u0440\u043E\u0443\u0442 \u0431\u0435\u0437 \u0442\u0430\u043A\u043E\u0439 \u0441\u0432\u0435\u0440\u043A\u0438 \u2014 \u043A\u0440\u043E\u0441\u0441-\u0442\u0435\u043D\u0430\u043D\u0442\u043D\u0430\u044F \u0434\u044B\u0440\u0430, \u0438 \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u044D\u0442\u043E \u0441\u0430\u043C\u044B\u0439 \u0447\u0430\u0441\u0442\u044B\u0439 \u043A\u043B\u0430\u0441\u0441.",
|
|
15291
|
+
"- [ ] \u041F\u043E\u043B\u043D\u043E\u0442\u0430 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432: PR \u0434\u043E\u0431\u0430\u0432\u0438\u043B \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0432 enum/union/\u043D\u0430\u0431\u043E\u0440 \u0441\u0442\u0440\u043E\u043A \u2014 \u043D\u0430\u0439\u0434\u0438 \u0412\u0421\u0415 \u043C\u0435\u0441\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u044D\u0442\u0438 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u044F\u044E\u0442 (\u0431\u0435\u043B\u044B\u0435 \u0441\u043F\u0438\u0441\u043A\u0438, switch, \u043C\u0430\u043F\u043F\u0438\u043D\u0433\u0438, \u0443\u0441\u043B\u043E\u0432\u0438\u044F \u0432 UI), \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044C, \u0447\u0442\u043E \u043D\u043E\u0432\u044B\u0439 \u0442\u0430\u043C \u0435\u0441\u0442\u044C. typecheck \u044D\u0442\u043E \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442, \u043A\u043E\u0433\u0434\u0430 \u0441\u043F\u0438\u0441\u043E\u043A \u2014 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435, \u0430 \u043D\u0435 exhaustive-switch. \u042D\u0442\u043E \u041E\u0414\u0418\u041D grep \u043F\u043E \u0438\u043C\u0435\u043D\u0438 \u0442\u0438\u043F\u0430 \u0438\u043B\u0438 \u043F\u043E \u0441\u0430\u043C\u043E\u043C\u0443 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044E, \u0430 \u043D\u0435 \u0447\u0442\u0435\u043D\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \xAB\u0434\u043B\u044F \u043F\u043E\u043D\u0438\u043C\u0430\u043D\u0438\u044F\xBB \u2014 \u043A\u043E\u043D\u0442\u0443\u0440 \u044D\u043A\u043E\u043D\u043E\u043C\u0438\u0438 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0440\u0435\u0449\u0430\u0435\u0442.",
|
|
15292
|
+
"- [ ] \u0412\u0441\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430: \u0443 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0438 \u043F\u043E\u0447\u0442\u0438 \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u0434\u0432\u0443\u0445 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439 (\u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E, \u043F\u0430\u0443\u0437\u0430, drain, \u043C\u043E\u043B\u0447\u0438\u0442, \u0434\u0430\u043D\u043D\u044B\u0445 \u0435\u0449\u0451 \u043D\u0435\u0442, \u0434\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043A\u0440\u044B\u0442). \u0420\u0430\u0437\u043E\u0431\u0440\u0430\u043D\u044B \u0412\u0421\u0415 \u0438\u043B\u0438 \u0442\u043E\u043B\u044C\u043A\u043E happy path \u0438 \u043E\u0434\u043D\u0430 \u043E\u0448\u0438\u0431\u043A\u0430? \u0427\u0430\u0441\u0442\u043E \u043F\u043E\u043B\u043D\u044B\u0439 \u043D\u0430\u0431\u043E\u0440 \u0443\u0436\u0435 \u043B\u0435\u0436\u0438\u0442 \u0440\u044F\u0434\u043E\u043C \u0432 \u044F\u0434\u0440\u0435 \u2014 \u043F\u043E\u0438\u0449\u0438 \u0435\u0433\u043E, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u043D\u044B\u043C.",
|
|
15293
|
+
"- [ ] \u041E\u0448\u0438\u0431\u043A\u0430 \u0434\u043E\u0445\u043E\u0434\u0438\u0442 \u0434\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430: \u043E\u0442\u043A\u0430\u0437 \u0437\u0430\u043F\u0440\u043E\u0441\u0430, \u043F\u0440\u043E\u0432\u0430\u043B \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u0438, \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u2014 \u0432\u0438\u0434\u043D\u044B \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435, \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 console.warn/\u043B\u043E\u0433. \u041A\u043D\u043E\u043F\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u043C\u043E\u043B\u0447\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0434\u0435\u043B\u0430\u043B\u0430, \u2014 \u0434\u0435\u0444\u0435\u043A\u0442, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E.",
|
|
15294
|
+
"- [ ] \u0414\u0435\u0444\u043E\u043B\u0442 \u043F\u0440\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445: \u043F\u043E\u043B\u044F \u043D\u0435\u0442, \u043E\u0442\u0432\u0435\u0442 \u043F\u0443\u0441\u0442\u043E\u0439, \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u043C\u043E\u043B\u0447\u0438\u0442 \u2014 \u043A\u0443\u0434\u0430 \u043F\u0430\u0434\u0430\u0435\u0442 \u0440\u0435\u0448\u0435\u043D\u0438\u0435? \u0414\u043B\u044F \u0443\u0434\u0430\u043B\u044F\u044E\u0449\u0438\u0445, \u043F\u0443\u0431\u043B\u0438\u043A\u0443\u044E\u0449\u0438\u0445 \u0438 \u0441\u043F\u0438\u0441\u044B\u0432\u0430\u044E\u0449\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u0435\u0444\u043E\u043B\u0442 \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C \u0432 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u0443\u044E \u0441\u0442\u043E\u0440\u043E\u043D\u0443 (\u043D\u0435 \u0434\u0435\u043B\u0430\u0442\u044C), \u0430 \xAB\u043D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445\xBB \u043D\u0435 \u0441\u043C\u0435\u0435\u0442 \u0447\u0438\u0442\u0430\u0442\u044C\u0441\u044F \u043A\u0430\u043A \xAB\u0447\u0438\u0441\u0442\u043E, \u043C\u043E\u0436\u043D\u043E\xBB.",
|
|
15295
|
+
"- [ ] \u0422\u0435\u0441\u0442\u044B PR \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E\u0442 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435. \u0415\u0441\u043B\u0438 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0423\u0416\u0415 \u043F\u043E\u0434\u043D\u044F\u0442\u043E (\u0432 worktree \u0435\u0441\u0442\u044C node_modules \u0438 \u043F\u0430\u043A\u0435\u0442\u044B \u0441\u043E\u0431\u0440\u0430\u043D\u044B): \u0441\u043D\u0438\u043C\u0438 \u0432 \u041A\u041E\u0414\u0415 \u0442\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043D\u043E\u0432\u044B\u0439 \u0442\u0435\u0441\u0442 \u044F\u043A\u043E\u0431\u044B \u0441\u0442\u043E\u0440\u043E\u0436\u0438\u0442 (\u043E\u0434\u043D\u0443 \u0441\u0442\u0440\u043E\u043A\u0443 \u2014 \u0433\u0435\u0439\u0442, \u0443\u0441\u043B\u043E\u0432\u0438\u0435, \u0432\u0430\u043B\u0438\u0434\u0430\u0446\u0438\u044E), \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u041E\u0414\u0418\u041D \u044D\u0442\u043E\u0442 \u0442\u0435\u0441\u0442 (\u043D\u0435 \u0432\u0435\u0441\u044C \u0441\u044C\u044E\u0442), \u0432\u0435\u0440\u043D\u0438 \u0444\u0430\u0439\u043B \u043A\u0430\u043A \u0431\u044B\u043B\u043E. \u0422\u0435\u0441\u0442 \u043E\u0431\u044F\u0437\u0430\u043D \u043F\u043E\u043A\u0440\u0430\u0441\u043D\u0435\u0442\u044C; \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u0437\u0435\u043B\u0451\u043D\u044B\u043C \u2014 \u043D\u0430\u0445\u043E\u0434\u043A\u0430: \u043E\u043D \u043D\u0435 \u0441\u0442\u043E\u0440\u043E\u0436\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E.",
|
|
15296
|
+
" \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u0442\u043E \u2014 \u0442\u0430\u043A \u0438 \u0431\u0443\u0434\u0435\u0442: \u0442\u0435\u0431\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0438 \u0447\u0438\u0441\u0442\u044B\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u043B. \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u0438\u0445 \u041D\u0415 \u041D\u0410\u0414\u041E, \u044D\u0442\u043E \u0434\u043E\u0440\u043E\u0436\u0435 \u0432\u0441\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E: \u0441\u043A\u0430\u0436\u0438 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435 \xAB\u043F\u0440\u043E\u0433\u043D\u0430\u0442\u044C \u043D\u0435 \u043C\u043E\u0433, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442\xBB \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0442\u0435\u0441\u0442 \u0427\u0422\u0415\u041D\u0418\u0415\u041C. \u0422\u0440\u0438 \u0441\u043F\u043E\u0441\u043E\u0431\u0430 \u0441\u043E\u0432\u0440\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C, \u0432\u0438\u0434\u043D\u044B\u0435 \u0433\u043B\u0430\u0437\u0430\u043C\u0438: (\u0430) \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0433\u043B\u043E\u0447\u0435\u043D\u043E (`try/except`, \u0437\u043E\u043D\u0442 \u0441\u0442\u043E\u0440\u043E\u0436\u0430, `.catch(() => {})`) \u2014 \u0442\u0435\u0441\u0442 \u043D\u0435 \u043F\u0430\u0434\u0430\u0435\u0442 \u0442\u0430\u043C, \u0433\u0434\u0435 \u043E\u0431\u044F\u0437\u0430\u043D; (\u0431) \u0430\u0441\u0441\u0435\u0440\u0442 \u043D\u0430 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435, \u0432\u0435\u0440\u043D\u043E\u0435 \u0432\u0441\u0435\u0433\u0434\u0430, \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u043F\u0440\u0430\u0432\u043A\u0438; (\u0432) \u0442\u0435\u0441\u0442 \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0451\u043D\u043D\u044B\u0439 \u0433\u0435\u0439\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0434\u0451\u0440\u0433\u0430\u0435\u0442 \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0439 \u043F\u0443\u0442\u044C."
|
|
15297
|
+
].join("\n");
|
|
14714
15298
|
function buildReviewChecklist(reviewRun, files, priorReview, verifyMode = SWARM_VERIFY) {
|
|
14715
15299
|
const header = files.length ? `\u0424\u0430\u0439\u043B\u044B \u0434\u0438\u0444\u0444\u0430 (${files.length}):
|
|
14716
15300
|
${files.map((f) => `- ${f}`).join("\n")}` : "\u0424\u0430\u0439\u043B\u044B \u0434\u0438\u0444\u0444\u0430: gh \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0441\u043F\u0438\u0441\u043E\u043A \u2014 \u0441\u043C. PR.";
|
|
@@ -14730,7 +15314,11 @@ ${priorReview}` : "",
|
|
|
14730
15314
|
"\u041A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0440\u0435-\u0447\u0435\u043A\u0430:",
|
|
14731
15315
|
"- [ ] \u0417\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u043F\u0440\u043E\u0448\u043B\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u044B \u2014 \u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u043F\u0443\u043D\u043A\u0442\u0443.",
|
|
14732
15316
|
`- [ ] \u0420\u0435\u0433\u0440\u0435\u0441\u0441\u0430 \u043D\u0435\u0442: ${ciTrustLine(prUrl, verifyMode)}`,
|
|
14733
|
-
"- [ ] \u041D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043D\u0435 \u043F\u0440\u0438\u0432\u043D\u0435\u0441\u043B\u0438 \u043D\u043E\u0432\u044B\u0445 \u043F\u0440\u043E\u0431\u043B\u0435\u043C
|
|
15317
|
+
"- [ ] \u041D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043D\u0435 \u043F\u0440\u0438\u0432\u043D\u0435\u0441\u043B\u0438 \u043D\u043E\u0432\u044B\u0445 \u043F\u0440\u043E\u0431\u043B\u0435\u043C: \u0433\u043E\u043D\u043A\u0438/\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/UI \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438 \u2014 \u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E \u043F\u0440\u0430\u0432\u0430 \u0430\u043A\u0442\u043E\u0440\u0430, \u0434\u0435\u0444\u043E\u043B\u0442 \u043F\u0440\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445, \u043F\u043E\u043B\u043D\u043E\u0442\u0430 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432. \u041F\u0440\u0430\u0432\u043A\u0430 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443 \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u0442\u0435 \u0436\u0435 \u043C\u0435\u0441\u0442\u0430, \u0433\u0434\u0435 \u0434\u0435\u0444\u0435\u043A\u0442 \u0438 \u0431\u044B\u043B, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0438\u043C\u0435\u043D\u043D\u043E \u043E\u043D\u0430 \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043E \u043F\u0440\u0438\u043D\u043E\u0441\u0438\u0442 \u043D\u043E\u0432\u044B\u0439.",
|
|
15318
|
+
// Единственный класс из REVIEW_DEFECT_CLASSES, который на втором круге обязателен:
|
|
15319
|
+
// правки по вердикту почти всегда приносят НОВЫЕ тесты, и именно тут прошёл #819.
|
|
15320
|
+
// Остальные пять классов заново не гоняем — ре-чек намеренно короткий (TASWM-437).
|
|
15321
|
+
"- [ ] \u041D\u043E\u0432\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u043F\u0440\u0430\u0432\u043A\u0438: \u0441\u043D\u0438\u043C\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0442\u0435\u0441\u0442 \u0441\u0442\u043E\u0440\u043E\u0436\u0438\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u041E\u0414\u0418\u041D \u044D\u0442\u043E\u0442 \u0442\u0435\u0441\u0442, \u0432\u0435\u0440\u043D\u0438 \u043A\u0430\u043A \u0431\u044B\u043B\u043E \u2014 \u043E\u043D \u043E\u0431\u044F\u0437\u0430\u043D \u043F\u043E\u043A\u0440\u0430\u0441\u043D\u0435\u0442\u044C."
|
|
14734
15322
|
].filter(Boolean).join("\n");
|
|
14735
15323
|
}
|
|
14736
15324
|
return [
|
|
@@ -14740,8 +15328,11 @@ ${priorReview}` : "",
|
|
|
14740
15328
|
"",
|
|
14741
15329
|
verifyMode === "local" ? "\u0427\u0435\u043A-\u043B\u0438\u0441\u0442 (\u0433\u0435\u0439\u0442 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0433\u043E\u043D\u044F\u0435\u0448\u044C \u0442\u044B \u0441\u0430\u043C \u2014 \u0441\u043C. \u043F\u0435\u0440\u0432\u044B\u0439 \u043F\u0443\u043D\u043A\u0442; \u0434\u0430\u043B\u044C\u0448\u0435 \u0440\u0443\u043A\u0430\u043C\u0438 \u0442\u043E, \u0447\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442):" : "\u0427\u0435\u043A-\u043B\u0438\u0441\u0442 (\u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0439 \u0440\u0443\u043A\u0430\u043C\u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0435\u0433\u043E CI \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u2014 \u043D\u0435 \u0432\u0435\u0440\u044C \u0446\u0438\u0444\u0440\u0430\u043C \u0430\u0432\u0442\u043E\u0440\u0430 PR):",
|
|
14742
15330
|
`- [ ] ${ciTrustLine(prUrl, verifyMode)}`,
|
|
15331
|
+
"",
|
|
15332
|
+
REVIEW_DEFECT_CLASSES,
|
|
15333
|
+
"",
|
|
14743
15334
|
"- [ ] \u041C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C) \u2014 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u043E\u0441\u0442\u044C \u0441\u043E \u0441\u0442\u0430\u0440\u044B\u043C\u0438 \u0434\u0430\u043D\u043D\u044B\u043C\u0438, \u043E\u0442\u043A\u0430\u0442.",
|
|
14744
|
-
"- [ ] \u0413\u043E\u043D\u043A\u0438
|
|
15335
|
+
"- [ ] \u0413\u043E\u043D\u043A\u0438 \u2014 \u043F\u043E\u0440\u044F\u0434\u043E\u043A \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0439, \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0433\u043E \u0432\u044B\u0437\u043E\u0432\u0430.",
|
|
14745
15336
|
"- [ ] UI-\u0434\u0438\u0441\u0446\u0438\u043F\u043B\u0438\u043D\u0430 (\u0434\u043B\u044F web-PR) \u2014 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (uikit), \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u044B \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (done(screenshots), TASWM-294), \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u043A PR.",
|
|
14746
15337
|
"- [ ] \u0415\u0441\u043B\u0438 \u043F\u0440\u0430\u0432\u0438\u0442\u0441\u044F router.tsx \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430 per-page meta \u0434\u043B\u044F \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 (\u0438\u043D\u0430\u0447\u0435 NL-\u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F \u043C\u043E\u043B\u0447\u0430 \u043B\u043E\u043C\u0430\u0435\u0442\u0441\u044F)."
|
|
14747
15338
|
].join("\n");
|
|
@@ -14767,9 +15358,16 @@ var REVIEW_SCOPE_BLOCK = [
|
|
|
14767
15358
|
"\u2022 \u0417\u0430\u0445\u043E\u0434\u043E\u0432 \u043D\u0430 \u043E\u0434\u0438\u043D \u0432\u043E\u043F\u0440\u043E\u0441 \u2014 \u043E\u0434\u0438\u043D. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u043E\u0442\u0432\u0435\u0442\u0430 \u0441 \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u0440\u0430\u0437\u0430 (\u0438\u043B\u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 \u043C\u043E\u043B\u0447\u0438\u0442) \u2014 \u044D\u0442\u043E \u0443\u0436\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0430: \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435, \u0430 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0439 \u0442\u0440\u0435\u0442\u0438\u0439 \u043A\u0440\u0443\u0433 \u043F\u043E\u0438\u0441\u043A\u0430.",
|
|
14768
15359
|
"\u2022 \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0441\u043F\u0435\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0412\u0421\u0415\u0413\u0414\u0410 \u043B\u0443\u0447\u0448\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u0442: \u043E\u0434\u043D\u043E\u0433\u043E \u043E\u0431\u043E\u0441\u043D\u043E\u0432\u0430\u043D\u043D\u043E\u0433\u043E \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0447\u0442\u043E\u0431\u044B PR \u043F\u043E\u0435\u0445\u0430\u043B \u043D\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0443. \u041A\u043E\u043F\u0430\u0435\u0448\u044C \u0434\u043E\u043B\u0433\u043E \u2014 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0439\u0441\u044F \u0438 \u0432\u044B\u043D\u043E\u0441\u0438 \u0442\u043E, \u0447\u0442\u043E \u0435\u0441\u0442\u044C, \u0447\u0435\u0441\u0442\u043D\u043E \u043D\u0430\u0437\u0432\u0430\u0432, \u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043C\u043E\u0442\u0440\u0435\u043B."
|
|
14769
15360
|
].join("\n");
|
|
15361
|
+
var REVIEW_MISSION_BLOCK = [
|
|
15362
|
+
"\u{1F3AF} \u0422\u0412\u041E\u042F \u0420\u0410\u0411\u041E\u0422\u0410 \u2014 \u041D\u0410\u0419\u0422\u0418 \u0414\u0415\u0424\u0415\u041A\u0422\u042B. \u041F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0435\u0442 PR, \u0440\u0430\u0431\u043E\u0442\u043E\u0439 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: \u044D\u0442\u043E \u0430\u0432\u0442\u043E\u0440 \u0443\u0436\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043B \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438. \u0422\u044B \u0438\u0449\u0435\u0448\u044C, \u0447\u0442\u043E \u0441\u043B\u043E\u043C\u0430\u0435\u0442\u0441\u044F.",
|
|
15363
|
+
"\u2022 \u0417\u0435\u043B\u0451\u043D\u044B\u0439 CI \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0434\u0435\u0444\u0435\u043A\u0442\u0430 \u043D\u0435\u0442: \u043E\u043D \u0433\u043E\u043D\u044F\u0435\u0442 \u0442\u0435\u0441\u0442\u044B \u0430\u0432\u0442\u043E\u0440\u0430 \u0438 \u0442\u0438\u043F\u044B. \u0421\u043C\u043E\u0442\u0440\u0438 \u0440\u043E\u0432\u043D\u043E \u0442\u043E, \u0447\u0435\u0433\u043E \u043E\u043D \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u2014 \u043F\u0440\u0430\u0432\u0430 \u0430\u043A\u0442\u043E\u0440\u0430, \u043C\u0443\u043B\u044C\u0442\u0438\u0442\u0435\u043D\u0430\u043D\u0442\u043D\u043E\u0441\u0442\u044C, \u0434\u0435\u0444\u043E\u043B\u0442\u044B \u043F\u0440\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u0434\u0430\u043D\u043D\u044B\u0445, \u043D\u0435\u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u043D\u044B\u0435 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u0438 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F, \u0442\u0435\u043A\u0441\u0442 \u043E\u0448\u0438\u0431\u043A\u0438 \u0434\u043B\u044F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u043F\u0440\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438.",
|
|
15364
|
+
"\u2022 \u041D\u0430\u0445\u043E\u0434\u043A\u0430 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0421\u0426\u0415\u041D\u0410\u0420\u0418\u0415\u041C: \xAB\u0442\u0430\u043A\u043E\u0439 \u0432\u0445\u043E\u0434 \u0438\u043B\u0438 \u0442\u0430\u043A\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u2192 \u0432\u043E\u0442 \u0442\u0430\u043A\u043E\u0439 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\xBB. \xAB\u0412\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\xBB, \xAB\u0441\u0442\u043E\u0438\u0442 \u043F\u0435\u0440\u0435\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C\xBB, \xAB\u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043F\u043E\u043A\u0440\u044B\u0442\u044C \u0442\u0435\u0441\u0442\u0430\u043C\u0438\xBB \u2014 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0438; \u0442\u0430\u043A\u043E\u0435 \u043D\u0435 \u043F\u0438\u0448\u0438.",
|
|
15365
|
+
"\u2022 \u041D\u043E\u043B\u044C \u043D\u0430\u0445\u043E\u0434\u043E\u043A \u2014 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442, \u043D\u043E \u0442\u043E\u0433\u0434\u0430 \u043D\u0430\u0437\u043E\u0432\u0438, \u0427\u0422\u041E \u0438\u043C\u0435\u043D\u043D\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u043A\u0430\u043A\u0438\u043C \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0435\u043C. APPROVE \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u043E\u0439 \u0447\u0438\u0442\u0430\u0435\u0442 \u043A\u0430\u043A \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437, \u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 PR \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u0440\u0443\u0447\u043D\u0443\u044E."
|
|
15366
|
+
].join("\n");
|
|
14770
15367
|
function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
|
|
14771
15368
|
const name = reviewer.name || `#${reviewer.id}`;
|
|
14772
15369
|
const prUrl = reviewRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
|
|
15370
|
+
const classesStep = reviewRun.review_cycle > 0 ? "" : "\u043F\u0440\u043E\u0439\u0434\u0438 \u043A\u043B\u0430\u0441\u0441\u044B \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u0432 (\u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u0442\u043E\u0436\u0435), ";
|
|
14773
15371
|
return [
|
|
14774
15372
|
`\u0422\u044B \u2014 \u0434\u0440\u043E\u043D-\u0420\u0415\u0412\u042C\u042E\u0415\u0420 ${name} \u0440\u043E\u044F, headless-\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C (\u041D\u0415 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0447\u0430\u0442). \u0410\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E (TASWM-148): \u0440\u043E\u0439 \u0441\u0430\u043C \u043F\u043E\u0434\u043D\u044F\u043B \u0442\u0435\u0431\u044F \u043D\u0430 PR \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F \u2014 \u0442\u044B \u041D\u0415 \u0432\u044B\u0431\u0440\u0430\u043D \u0438\u043C \u0438 \u043E\u0442 \u043D\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0448\u044C.`,
|
|
14775
15373
|
"",
|
|
@@ -14783,7 +15381,12 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
14783
15381
|
`\u26A0\uFE0F \u042D\u0442\u043E\u0442 PR \u043E\u0442\u043A\u0440\u044B\u0442 \u0432 \u0432\u0435\u0442\u043A\u0443 \`${reviewRun.base_branch}\` (\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u0430\u044F \u043F\u0430\u0447\u043A\u0430), \u0430 \u041D\u0415 \u0432 \u0432\u0435\u0442\u043A\u0443 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E. \u0414\u0438\u0444 \u0441\u043C\u043E\u0442\u0440\u0438 \u0441\u0442\u0440\u043E\u0433\u043E \u043E\u0442 \u043D\u0435\u0451: \`git diff origin/${reviewRun.base_branch}...HEAD\` (\u0441\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 \u043D\u0438\u0436\u0435 \u0443\u0436\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D gh \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u044D\u0442\u043E\u0439 \u0431\u0430\u0437\u044B). \u0421\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0435 \u0441 main \u043F\u043E\u043A\u0430\u0437\u0430\u043B\u043E \u0431\u044B \u0442\u0435\u0431\u0435 \u0447\u0443\u0436\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u0430\u0447\u043A\u0438 \u043A\u0430\u043A \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u044D\u0442\u043E\u0433\u043E \u0434\u0440\u043E\u043D\u0430 \u2014 \u043D\u0435 \u0434\u0435\u043B\u0430\u0439 \u0442\u0430\u043A.`
|
|
14784
15382
|
] : [],
|
|
14785
15383
|
"",
|
|
14786
|
-
|
|
15384
|
+
// TASWM-437: мутационная проверка теста — это правка файла, и без явного исключения
|
|
15385
|
+
// ревьюер честно откажется её делать. Разрешаем ровно её: локально, с возвратом файла,
|
|
15386
|
+
// без коммита и пуша (worktree ревьюера всё равно удаляется после прогона).
|
|
15387
|
+
"\u26A0\uFE0F \u0422\u042B \u041D\u0415 \u041F\u0420\u0410\u0412\u0418\u0428\u042C \u041A\u041E\u0414 PR \u0418 \u041D\u0415 \u041F\u0423\u0428\u0418\u0428\u042C \u041A\u041E\u041C\u041C\u0418\u0422\u042B. \u0422\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0438 \u0432\u044B\u043D\u0435\u0441\u0442\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442. \u0415\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u0430\u044F \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D\u043D\u0430\u044F \u043F\u0440\u0430\u0432\u043A\u0430 \u2014 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u043C\u0443\u0442\u0430\u0446\u0438\u044F \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0442\u0435\u0441\u0442\u0430 (\u043F\u0443\u043D\u043A\u0442 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442\u0430 \u043F\u0440\u043E \u0442\u0435\u0441\u0442\u044B): \u0441\u043D\u044F\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043F\u0440\u043E\u0433\u043D\u0430\u043B \u043E\u0434\u0438\u043D \u0442\u0435\u0441\u0442, \u0432\u0435\u0440\u043D\u0443\u043B \u0444\u0430\u0439\u043B (`git checkout -- <\u0444\u0430\u0439\u043B>`). \u0412\u043E\u0437\u0432\u0440\u0430\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u0414\u041E \u043A\u043E\u043D\u0446\u0430 \u0441\u0435\u0441\u0441\u0438\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0448\u044C\u0441\u044F \u0434\u043E\u0441\u0440\u043E\u0447\u043D\u043E (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u0443\u0445\u043E\u0434\u0438\u0448\u044C \u0436\u0434\u0430\u0442\u044C CI): `git status` \u0432 worktree \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C \u0447\u0438\u0441\u0442. \u041A\u043E\u043C\u043C\u0438\u0442\u0438\u0442\u044C, \u043F\u0443\u0448\u0438\u0442\u044C \u0438 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C \u0441\u0432\u043E\u0438 PR \u043D\u0435\u043B\u044C\u0437\u044F \u043D\u0438\u0447\u0435\u0433\u043E \u0438 \u043D\u0438\u043A\u043E\u0433\u0434\u0430.",
|
|
15388
|
+
"",
|
|
15389
|
+
REVIEW_MISSION_BLOCK,
|
|
14787
15390
|
"",
|
|
14788
15391
|
REVIEW_SCOPE_BLOCK,
|
|
14789
15392
|
// TASWM-283: ревьюер читает ТУ ЖЕ задачу и так же слеп к её картинкам — без макета
|
|
@@ -14795,11 +15398,11 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
14795
15398
|
"",
|
|
14796
15399
|
"\u041F\u043E\u0440\u044F\u0434\u043E\u043A:",
|
|
14797
15400
|
...verifyMode === "local" ? [
|
|
14798
|
-
|
|
15401
|
+
`1. \u041F\u0440\u043E\u0439\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435: \u0433\u0435\u0439\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u0421\u0410\u041C \u0432 \u0441\u0432\u043E\u0451\u043C worktree (\u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0437\u0435\u043B\u0451\u043D\u043E\u0433\u043E \u2014 \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=local), ${classesStep}\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/\u0433\u043E\u043D\u043A\u0438/UI/per-page meta \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438.`,
|
|
14799
15402
|
" \u0427\u0435\u043A\u043E\u0432 \u043D\u0430 \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u043D\u0435\u0442 (\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 CI) \u2014 \u044D\u0442\u043E \u041D\u0415 \u0434\u0435\u0444\u0435\u043A\u0442 PR \u0438 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434 \u0434\u043B\u044F REQUEST CHANGES: \u043D\u0435 \u0436\u0434\u0438 \u0438\u0445, \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0439 \u0437\u0430\u0432\u0435\u0441\u0442\u0438 workflow, \u0441\u0443\u0434\u0438 \u043F\u043E \u0441\u0432\u043E\u0435\u043C\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0443. \u041A\u0440\u0430\u0441\u043D\u044B\u0439 \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u0412\u0428\u0418\u0419\u0421\u042F \u0447\u0435\u043A, \u0435\u0441\u043B\u0438 CI \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432\u0441\u0451-\u0442\u0430\u043A\u0438 \u0435\u0441\u0442\u044C, \u2014 \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u0431\u043B\u043E\u043A.",
|
|
14800
15403
|
`2. \u041F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0430 \u0441\u0430\u043C PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u0435\u0433\u043E \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041D\u0430\u0447\u043D\u0438 \u0441 **APPROVE** \u0438\u043B\u0438 **REQUEST CHANGES** \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u043C\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0430: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B (\u043F\u0440\u0438\u0432\u0435\u0434\u0438 \u0432\u044B\u0432\u043E\u0434 \u0441\u0432\u043E\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430), \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B; \u0434\u043B\u044F REQUEST CHANGES \u2014 \u0442\u043E\u0447\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B/\u0441\u0442\u0440\u043E\u043A\u0438 \u0438 \u0447\u0442\u043E \u0447\u0438\u043D\u0438\u0442\u044C. \u041F\u0440\u0435\u0442\u0435\u043D\u0437\u0438\u044F \u043A \u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (\u043D\u0435\u0442 CI, \u043A\u043E\u043D\u0444\u0438\u0433\u0438, \u0434\u0435\u043F\u043B\u043E\u0439) \u2014 \u043D\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E PR: \u0441\u0447\u0438\u0442\u0430\u0435\u0448\u044C, \u0447\u0442\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E \u043D\u0443\u0436\u0435\u043D CI, \u2014 \u0441\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u0432 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0435, \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0442 \u043D\u0435\u0451 \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442.`
|
|
14801
15404
|
] : [
|
|
14802
|
-
|
|
15405
|
+
`1. \u041F\u0440\u043E\u0439\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435: \u0441\u0442\u0430\u0442\u0443\u0441 CI \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u043A\u043E\u043C\u043C\u0438\u0442\u0443 \u0447\u0438\u0442\u0430\u0439 \u0441\u0430\u043C (\u043D\u0435 \u0432\u0435\u0440\u044C \u043E\u0442\u0447\u0451\u0442\u0443 \u0430\u0432\u0442\u043E\u0440\u0430 PR, \u043D\u043E \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 CI \u0432\u0440\u0443\u0447\u043D\u0443\u044E), ${classesStep}\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438/\u0433\u043E\u043D\u043A\u0438/UI/per-page meta \u043F\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\u0441\u0442\u0438.`,
|
|
14803
15406
|
" \u0415\u0441\u043B\u0438 \u0445\u043E\u0442\u044C \u043E\u0434\u0438\u043D \u0447\u0435\u043A \u0435\u0449\u0451 PENDING \u2014 \u043D\u0430 \u044D\u0442\u043E\u043C \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0441\u044C: \u0448\u0430\u0433\u0438 2 \u0438 3 \u043D\u0438\u0436\u0435 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438 \u0446\u0435\u043B\u0438\u043A\u043E\u043C, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0435 \u0432\u044B\u043D\u043E\u0441\u0438, \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E (\u0441\u043C. \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0432\u044B\u0448\u0435, \u043F\u043E\u0447\u0435\u043C\u0443 \u044D\u0442\u043E \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E \u0438 \u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0439\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435).",
|
|
14804
15407
|
`2. \u0427\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C (\u0437\u0435\u043B\u0451\u043D\u044B\u0435 \u0438\u043B\u0438 \u0441 \u043A\u0440\u0430\u0441\u043D\u044B\u043C) \u0438\u043B\u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0438\u0445 \u0432\u043E\u0432\u0441\u0435 \u043D\u0435\u0442 \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0430 \u0441\u0430\u043C PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u0435\u0433\u043E \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041D\u0430\u0447\u043D\u0438 \u0441 **APPROVE** \u0438\u043B\u0438 **REQUEST CHANGES** \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u043C\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0430: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B, \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B; \u0434\u043B\u044F REQUEST CHANGES \u2014 \u0442\u043E\u0447\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B/\u0441\u0442\u0440\u043E\u043A\u0438 \u0438 \u0447\u0442\u043E \u0447\u0438\u043D\u0438\u0442\u044C.`
|
|
14805
15408
|
],
|
|
@@ -14836,6 +15439,7 @@ async function manageStorm(deps, now, worktreeExists, hasTranscript) {
|
|
|
14836
15439
|
const consec = trailingProbeSuccesses(recs);
|
|
14837
15440
|
if (stormShouldEnd({ consecutiveProbeOk: consec, externalDown: lastExternalDown(recs) === true }, cfg)) {
|
|
14838
15441
|
db.endStorm(`${STORM_END_TEXT} (${consec} \u0443\u0441\u043F\u0435\u0448\u043D\u044B\u0435 health-probe \u043F\u043E\u0434\u0440\u044F\u0434)`);
|
|
15442
|
+
if (deps.spawnDefer) clearDefer(deps.spawnDefer, "storm");
|
|
14839
15443
|
narrate(deps, "\u0428\u0442\u043E\u0440\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0437\u0430\u043C\u043E\u0440\u043E\u0436\u0435\u043D\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B.");
|
|
14840
15444
|
await resurrectStormDead(deps, worktreeExists, hasTranscript);
|
|
14841
15445
|
return false;
|
|
@@ -15117,8 +15721,10 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
15117
15721
|
if (stats.count >= CI_WAKE_LIMIT || !canContinue) {
|
|
15118
15722
|
const why = !canContinue ? "worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : `${CI_WAKE_LIMIT} \u043F\u043E\u0431\u0443\u0434(\u043E\u043A) \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 CI \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E`;
|
|
15119
15723
|
deps.db.updateRun(reviewer.id, { status: "stalled" });
|
|
15120
|
-
|
|
15121
|
-
|
|
15724
|
+
const freshWanted = await projectWantsReviewer(deps, run);
|
|
15725
|
+
const freshPlan = freshWanted ? "\u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR" : "\u0441\u0432\u0435\u0436\u0435\u0433\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E: \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E";
|
|
15726
|
+
deps.db.addEvent(reviewer.id, "stalled", `CI \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${why} \u2014 ${freshPlan}`);
|
|
15727
|
+
narrate(deps, `${droneLabel(reviewer)} \u0436\u0434\u0430\u043B CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}) \u2014 ${freshPlan}.`);
|
|
15122
15728
|
if (deps.watchdog?.notify) {
|
|
15123
15729
|
const signal = {
|
|
15124
15730
|
key: `reviewer_ci_wake_failed:${reviewer.id}`,
|
|
@@ -15127,7 +15733,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
15127
15733
|
// поднимает свежего ревьюера на том же PR. Решать человеку тут нечего; если и
|
|
15128
15734
|
// свежий не доедет, конвейер встанет и об этом скажет уже pr_stuck_* пушем.
|
|
15129
15735
|
level: "feed",
|
|
15130
|
-
body: `\u0420\u0435\u0432\u044C\u044E\u0435\u0440 ${droneLabel(reviewer)} \u0434\u043E\u0436\u0434\u0430\u043B\u0441\u044F \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}). \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR \u2014 ${run.pr_url ?? "?"}.`,
|
|
15736
|
+
body: `\u0420\u0435\u0432\u044C\u044E\u0435\u0440 ${droneLabel(reviewer)} \u0434\u043E\u0436\u0434\u0430\u043B\u0441\u044F \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}). ${freshWanted ? "\u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR" : "\u0421\u0432\u0435\u0436\u0435\u0433\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E (\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E)"} \u2014 ${run.pr_url ?? "?"}.`,
|
|
15131
15737
|
runId: reviewer.id,
|
|
15132
15738
|
...hrefOf(run.task_url)
|
|
15133
15739
|
};
|
|
@@ -15137,7 +15743,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
15137
15743
|
console.error(`[overmind] \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u0435 \u043E \u043F\u043E\u0442\u0435\u0440\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 #${reviewer.id} \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u043E:`, e);
|
|
15138
15744
|
}
|
|
15139
15745
|
}
|
|
15140
|
-
await spawnFreshReviewerAfterStall(deps, run, why);
|
|
15746
|
+
if (freshWanted) await spawnFreshReviewerAfterStall(deps, run, why);
|
|
15141
15747
|
return true;
|
|
15142
15748
|
}
|
|
15143
15749
|
try {
|
|
@@ -15159,6 +15765,10 @@ function worktreeAndTranscriptAlive(deps, worktree) {
|
|
|
15159
15765
|
const transcript = deps.hasTranscript ?? executorHasSession;
|
|
15160
15766
|
return exists(worktree) && transcript(worktree);
|
|
15161
15767
|
}
|
|
15768
|
+
async function projectWantsReviewer(deps, run) {
|
|
15769
|
+
if (!deps.autoReviewForRepo) return true;
|
|
15770
|
+
return await deps.autoReviewForRepo(effectiveRepo(deps, run)).catch(() => true);
|
|
15771
|
+
}
|
|
15162
15772
|
async function spawnFreshReviewerAfterStall(deps, run, why) {
|
|
15163
15773
|
if (!deps.spawnReviewer) return;
|
|
15164
15774
|
const live = liveOrWaitingReviewers(deps.db, run.id);
|
|
@@ -15251,23 +15861,23 @@ async function freshChangesWakeAllowed(deps, run, prUrl, now) {
|
|
|
15251
15861
|
const moved = await deps.commitsSince(prUrl, verdictAt ?? run.created_at).catch(() => true);
|
|
15252
15862
|
return !moved;
|
|
15253
15863
|
}
|
|
15254
|
-
async function
|
|
15255
|
-
if (!deps.db.refreshRun) return
|
|
15864
|
+
async function prodOverrideReason(deps, run, allowed) {
|
|
15865
|
+
if (!deps.db.refreshRun) return null;
|
|
15256
15866
|
let fresh;
|
|
15257
15867
|
try {
|
|
15258
15868
|
fresh = await deps.db.refreshRun(run.id);
|
|
15259
15869
|
} catch {
|
|
15260
|
-
return
|
|
15870
|
+
return null;
|
|
15261
15871
|
}
|
|
15262
|
-
if (!fresh) return
|
|
15263
|
-
if (allowed.includes(fresh.status)) return
|
|
15872
|
+
if (!fresh) return "\u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442 \u0432 \u043C\u043E\u0451\u043C \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u2014 \u043F\u0440\u043E\u0434 \u043E\u0442\u0434\u0430\u043B \u0435\u0433\u043E \u0434\u0440\u0443\u0433\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 (TASWM-300)";
|
|
15873
|
+
if (allowed.includes(fresh.status)) return null;
|
|
15264
15874
|
deps.db.addEvent(
|
|
15265
15875
|
run.id,
|
|
15266
15876
|
"step",
|
|
15267
15877
|
`\u043F\u0440\u043E\u0434 \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \xAB${fresh.status}\xBB (\u0443 \u043C\u0435\u043D\u044F \u0431\u044B\u043B\u043E \xAB${run.status}\xBB) \u2014 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E, \u0441\u0442\u0430\u0442\u0443\u0441 \u043F\u0440\u043E\u0434\u0430 \u0441\u0438\u043B\u044C\u043D\u0435\u0435 (TASWM-300)`
|
|
15268
15878
|
);
|
|
15269
15879
|
narrate(deps, `${droneLabel(run)} \u2014 \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0443\u0436\u0435 \xAB${fresh.status}\xBB, \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E: \u043F\u0440\u043E\u0434 \u0441\u0438\u043B\u044C\u043D\u0435\u0435 \u043C\u043E\u0435\u0433\u043E \u0437\u0435\u0440\u043A\u0430\u043B\u0430.`);
|
|
15270
|
-
return
|
|
15880
|
+
return `\u043F\u0440\u043E\u0434 \u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043B \u043F\u0440\u043E\u0433\u043E\u043D, \u043F\u043E\u043A\u0430 \u0442\u043E\u0442 \u0436\u0434\u0430\u043B: \u0442\u0430\u043C \u0443\u0436\u0435 \xAB${fresh.status}\xBB, \u0443 \u043C\u0435\u043D\u044F \u0431\u044B\u043B\u043E \xAB${run.status}\xBB (TASWM-300)`;
|
|
15271
15881
|
}
|
|
15272
15882
|
async function envGateAllows(deps, run) {
|
|
15273
15883
|
if (!deps.envGate) return true;
|
|
@@ -15373,13 +15983,18 @@ async function projectQuotaGateAllows(deps, run) {
|
|
|
15373
15983
|
return true;
|
|
15374
15984
|
}
|
|
15375
15985
|
function pruneGateLogs(deps) {
|
|
15376
|
-
if (!deps.envGateLogged?.size && !deps.exclusiveLogged?.size && !deps.overlapLogged?.size) return;
|
|
15986
|
+
if (!deps.envGateLogged?.size && !deps.exclusiveLogged?.size && !deps.overlapLogged?.size && !deps.spawnDefer?.size) return;
|
|
15377
15987
|
const open = /* @__PURE__ */ new Set();
|
|
15378
15988
|
for (const run of deps.db.listRuns()) if (!isTerminalStatus(run.status)) open.add(run.id);
|
|
15379
15989
|
for (const map of [deps.envGateLogged, deps.exclusiveLogged, deps.overlapLogged]) {
|
|
15380
15990
|
if (!map) continue;
|
|
15381
15991
|
for (const id of [...map.keys()]) if (!open.has(id)) map.delete(id);
|
|
15382
15992
|
}
|
|
15993
|
+
if (deps.spawnDefer) {
|
|
15994
|
+
const live = /* @__PURE__ */ new Set(["storm", "control-pause"]);
|
|
15995
|
+
for (const id of open) live.add(`run:${id}`);
|
|
15996
|
+
pruneDefers(deps.spawnDefer, live);
|
|
15997
|
+
}
|
|
15383
15998
|
}
|
|
15384
15999
|
async function projectQuotaBlockedRunIds(deps) {
|
|
15385
16000
|
const blocked = /* @__PURE__ */ new Set();
|
|
@@ -15417,8 +16032,38 @@ function noteExhaustedDeps(deps, run, verdicts) {
|
|
|
15417
16032
|
narrate(deps, `${droneLabel(run)} \u2014 ${text}`);
|
|
15418
16033
|
}
|
|
15419
16034
|
}
|
|
16035
|
+
function deferSpawn(deps, run, reason, opts = {}) {
|
|
16036
|
+
const eps = deps.spawnDefer ??= /* @__PURE__ */ new Map();
|
|
16037
|
+
const d = noteDefer(eps, `run:${run.id}`, reason, GATE_LOG_HEARTBEAT_TICKS);
|
|
16038
|
+
if (d.changed && opts.event !== false) deps.db.addEvent(run.id, "step", `\u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D: ${reason} \u2014 \u0436\u0434\u0443 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 (TASWM-333)`);
|
|
16039
|
+
const shout = opts.logKey ? noteDefer(eps, opts.logKey, reason, GATE_LOG_HEARTBEAT_TICKS) : d;
|
|
16040
|
+
if (shout.log && !opts.quiet) {
|
|
16041
|
+
const suffix = shout.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${shout.ticks}`;
|
|
16042
|
+
const who = opts.logKey ? "" : `${droneLabel(run)} (${run.task_slug}) \u2014 `;
|
|
16043
|
+
console.error(`[overmind] \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D: ${who}${reason}${suffix}`);
|
|
16044
|
+
}
|
|
16045
|
+
return "skipped";
|
|
16046
|
+
}
|
|
16047
|
+
function noteStormStall(deps) {
|
|
16048
|
+
const waiting = deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0);
|
|
16049
|
+
if (waiting.length === 0) return;
|
|
16050
|
+
const startedAt = deps.db.currentStormStartedAt();
|
|
16051
|
+
const reason = `\u0440\u043E\u0439 \u0432 \u0448\u0442\u043E\u0440\u043C\u043E\u0432\u043E\u043C \u0440\u0435\u0436\u0438\u043C\u0435${startedAt ? ` \u0441 ${startedAt} UTC` : ""} \u2014 \u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C \u0434\u043E \u043E\u0442\u0431\u043E\u044F (TASWM-23)`;
|
|
16052
|
+
const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), "storm", reason, GATE_LOG_HEARTBEAT_TICKS);
|
|
16053
|
+
if (d.log) {
|
|
16054
|
+
const suffix = d.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${d.ticks}`;
|
|
16055
|
+
console.error(`[overmind] \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D: ${reason}; \u0436\u0434\u0443\u0442 ${waiting.length} \u043F\u0440\u043E\u0433\u043E\u043D(\u043E\u0432)${suffix}`);
|
|
16056
|
+
}
|
|
16057
|
+
for (const run of waiting) deferSpawn(deps, run, reason, { quiet: true });
|
|
16058
|
+
}
|
|
15420
16059
|
async function trySpawnQueuedRun(deps, run) {
|
|
15421
|
-
if (deps.control?.paused)
|
|
16060
|
+
if (deps.control?.paused) {
|
|
16061
|
+
const why = deps.control.state?.reason;
|
|
16062
|
+
return deferSpawn(deps, run, `\u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435${why ? ` \u2014 ${why}` : ""} (\u043F\u0430\u0443\u0437\u0430)`, {
|
|
16063
|
+
logKey: "control-pause"
|
|
16064
|
+
// причина одна на машину — в лог уходит одной строкой, а не по строке на прогон
|
|
16065
|
+
});
|
|
16066
|
+
}
|
|
15422
16067
|
const mergedUrl = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
|
|
15423
16068
|
if (mergedUrl) {
|
|
15424
16069
|
await closeDuplicateTaskRun(deps, run, mergedUrl);
|
|
@@ -15428,15 +16073,20 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
15428
16073
|
return "closed";
|
|
15429
16074
|
}
|
|
15430
16075
|
const depVerdicts = deps.db.depVerdicts(run.depends_on);
|
|
15431
|
-
|
|
16076
|
+
const unmet = depVerdicts.filter((d) => !d.met);
|
|
16077
|
+
if (unmet.length) return deferSpawn(deps, run, `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438: ${unmet.map((d) => d.slug).join(", ")}`);
|
|
15432
16078
|
if (!await envGateAllows(deps, run)) return "skipped";
|
|
15433
16079
|
if (!await projectQuotaGateAllows(deps, run)) return "skipped";
|
|
15434
|
-
|
|
15435
|
-
if (
|
|
16080
|
+
const prodOverride = await prodOverrideReason(deps, run, ["queued"]);
|
|
16081
|
+
if (prodOverride) return deferSpawn(deps, run, prodOverride, { event: false });
|
|
16082
|
+
if (deps.db.takeRun && !await deps.db.takeRun(run.id)) {
|
|
16083
|
+
return deferSpawn(deps, run, "\u043D\u0435 \u0432\u0437\u044F\u043B \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u0441\u0432\u043E\u044E \u043C\u0430\u0448\u0438\u043D\u0443 \u2014 \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0434\u0430\u043B \u0435\u0433\u043E \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u0434\u0435\u043C\u043E\u043D\u0443 (TASWM-281)");
|
|
16084
|
+
}
|
|
15436
16085
|
noteExhaustedDeps(deps, run, depVerdicts);
|
|
15437
16086
|
await fillSuggestedArea(deps, run);
|
|
15438
16087
|
try {
|
|
15439
16088
|
await deps.spawn(run);
|
|
16089
|
+
if (deps.spawnDefer) clearDefer(deps.spawnDefer, `run:${run.id}`);
|
|
15440
16090
|
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)}.`);
|
|
15441
16091
|
return "spawned";
|
|
15442
16092
|
} catch (e) {
|
|
@@ -15449,7 +16099,7 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
15449
16099
|
}
|
|
15450
16100
|
return "skipped";
|
|
15451
16101
|
}
|
|
15452
|
-
if (e instanceof AreaBusyError) return
|
|
16102
|
+
if (e instanceof AreaBusyError) return deferSpawn(deps, run, e.message);
|
|
15453
16103
|
deps.db.updateRun(run.id, { status: "failed" });
|
|
15454
16104
|
deps.db.addEvent(run.id, "failed", String(e));
|
|
15455
16105
|
return "failed";
|
|
@@ -15820,6 +16470,17 @@ async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
|
|
|
15820
16470
|
}
|
|
15821
16471
|
return true;
|
|
15822
16472
|
}
|
|
16473
|
+
async function salvageVerifyVerdictFromPrComment(deps, vr, verifiesRunId, since, how) {
|
|
16474
|
+
if (!deps.verifyVerdictComment || !vr.pr_url) return false;
|
|
16475
|
+
const found = await deps.verifyVerdictComment(vr.pr_url, since).catch(() => null);
|
|
16476
|
+
if (!found) return false;
|
|
16477
|
+
const verdictText = verifyVerdictLabel(found.verdict);
|
|
16478
|
+
deps.db.updateRun(verifiesRunId, { verify_verdict: found.verdict });
|
|
16479
|
+
deps.db.addEvent(verifiesRunId, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D \u0438\u0437 PR-\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430 (${how}): ${verdictText}`);
|
|
16480
|
+
await closeReviewerRun(deps, vr, `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C (${verdictText}), ${how} \u2014 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u043A\u0440\u044B\u0442\u0430 \u0431\u0435\u0437 \u0441\u043F\u0430\u0432\u043D\u0430 \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430`);
|
|
16481
|
+
narrate(deps, `${droneLabel(vr)} \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 (${verdictText}) \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u0430\u043B, \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E.`);
|
|
16482
|
+
return true;
|
|
16483
|
+
}
|
|
15823
16484
|
async function failReviewerRun(deps, reviewer, reviewsRunId, opts) {
|
|
15824
16485
|
if (!opts.keepWorktree && reviewer.worktree_path) {
|
|
15825
16486
|
await deps.removeWorktree(reviewer.repo, reviewer.worktree_path).catch(() => {
|
|
@@ -16022,6 +16683,7 @@ async function tickOnce(deps) {
|
|
|
16022
16683
|
stormActive = await manageStorm(deps, now, worktreeExists, hasTranscript);
|
|
16023
16684
|
}
|
|
16024
16685
|
const droneCap = deps.maxDrones ? await deps.maxDrones() : Infinity;
|
|
16686
|
+
if (stormActive) noteStormStall(deps);
|
|
16025
16687
|
if (deps.resourcesOk) {
|
|
16026
16688
|
await manageRelief(deps, now, droneCap, stormActive);
|
|
16027
16689
|
} else if (!stormActive) {
|
|
@@ -16065,6 +16727,26 @@ async function tickOnce(deps) {
|
|
|
16065
16727
|
narrate(deps, `${droneLabel(run)} \u0432\u044B\u043D\u0435\u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0440\u0435\u0432\u044C\u044E \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
|
|
16066
16728
|
continue;
|
|
16067
16729
|
}
|
|
16730
|
+
if (run.verifies_run_id != null) {
|
|
16731
|
+
const verified = deps.db.getRun(run.verifies_run_id);
|
|
16732
|
+
const sameCycle = verified != null && (verified.review_cycle ?? 0) === (run.review_cycle ?? 0);
|
|
16733
|
+
if (verified && sameCycle && verified.verify_verdict == null) {
|
|
16734
|
+
if (await salvageVerifyVerdictFromPrComment(deps, run, run.verifies_run_id, run.created_at, "\u043F\u043E\u0437\u0432\u0430\u043B done(), \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B")) continue;
|
|
16735
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
16736
|
+
});
|
|
16737
|
+
deps.db.updateRun(run.id, { status: "failed" });
|
|
16738
|
+
deps.db.addEvent(run.id, "failed", "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u0430\u0441\u044C");
|
|
16739
|
+
deps.db.addEvent(run.verifies_run_id, "step", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 ${run.name} (#${run.id}) \u0437\u0430\u043A\u0440\u044B\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434`);
|
|
16740
|
+
narrate(deps, `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B, \u043D\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438, \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
|
|
16741
|
+
continue;
|
|
16742
|
+
}
|
|
16743
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
16744
|
+
});
|
|
16745
|
+
deps.db.updateRun(run.id, { status: "done" });
|
|
16746
|
+
deps.db.addEvent(run.id, "done", "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D");
|
|
16747
|
+
narrate(deps, `${droneLabel(run)} \u0432\u044B\u043D\u0435\u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
|
|
16748
|
+
continue;
|
|
16749
|
+
}
|
|
16068
16750
|
if (!run.pr_url) continue;
|
|
16069
16751
|
const prUrl = run.pr_url;
|
|
16070
16752
|
const st = await deps.prStatus(prUrl);
|
|
@@ -16125,7 +16807,7 @@ async function tickOnce(deps) {
|
|
|
16125
16807
|
if (run.review_verdict === "changes" && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
|
|
16126
16808
|
continue;
|
|
16127
16809
|
}
|
|
16128
|
-
|
|
16810
|
+
const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
|
|
16129
16811
|
// …и НИ ОДНОГО живого/ждущего ревьюера ЛЮБОГО цикла (TASWM-299). reviewerCountFor
|
|
16130
16812
|
// выше скоупится по review_cycle, поэтому сама по себе она не инвариант «один
|
|
16131
16813
|
// ревьюер на PR», а лишь идемпотентность внутри круга: фикс-итерация бампит цикл, и
|
|
@@ -16133,7 +16815,8 @@ async function tickOnce(deps) {
|
|
|
16133
16815
|
// такого уже закрыла (тот же тик, выше) — этот гейт держит инвариант СТРУКТУРНО, а
|
|
16134
16816
|
// не порядком веток в 900-строчной функции: увидели живого — значит закрывать его
|
|
16135
16817
|
// рано, спавнить второго нельзя.
|
|
16136
|
-
liveOrWaitingReviewers(deps.db, run.id).length === 0
|
|
16818
|
+
liveOrWaitingReviewers(deps.db, run.id).length === 0;
|
|
16819
|
+
if (reviewerGatesOk && deps.spawnReviewer && await projectWantsReviewer(deps, run)) {
|
|
16137
16820
|
if (run.review_cycle >= REVIEW_CYCLE_LIMIT) {
|
|
16138
16821
|
if (!deps.db.events(run.id).some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_CYCLE_ESCALATION_MARK))) {
|
|
16139
16822
|
deps.db.addEvent(run.id, "step", `${REVIEW_CYCLE_ESCALATION_MARK}: ${run.review_cycle} \u0446\u0438\u043A\u043B(\u043E\u0432) \u0440\u0435\u0432\u044C\u044E-\u0444\u0438\u043A\u0441\u043E\u0432 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
|
|
@@ -16202,6 +16885,7 @@ async function tickOnce(deps) {
|
|
|
16202
16885
|
}
|
|
16203
16886
|
await closeMergedTaskRuns(deps);
|
|
16204
16887
|
await closeOrphanedReviewers(deps);
|
|
16888
|
+
await spawnVerifiersForOpenPrs(deps, stormActive);
|
|
16205
16889
|
refreshReviewPhases(deps);
|
|
16206
16890
|
let liveChildPpids;
|
|
16207
16891
|
try {
|
|
@@ -16213,7 +16897,7 @@ async function tickOnce(deps) {
|
|
|
16213
16897
|
for (const run of [...deps.db.listRuns("running"), ...deps.db.listRuns("blocked")]) {
|
|
16214
16898
|
const alive = run.pid != null && deps.isAlive(run.pid);
|
|
16215
16899
|
if (!alive && deps.db.takeRun && !await deps.db.takeRun(run.id)) continue;
|
|
16216
|
-
if (!alive &&
|
|
16900
|
+
if (!alive && await prodOverrideReason(deps, run, ["running", "blocked"])) continue;
|
|
16217
16901
|
const outcome = alive ? "unknown" : deps.exitOutcome?.(run) ?? "unknown";
|
|
16218
16902
|
if (!alive && deps.accountCost) {
|
|
16219
16903
|
try {
|
|
@@ -16284,7 +16968,7 @@ async function tickOnce(deps) {
|
|
|
16284
16968
|
continue;
|
|
16285
16969
|
}
|
|
16286
16970
|
}
|
|
16287
|
-
if (!alive && outcome === "graceful" && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.kind !== "hero") {
|
|
16971
|
+
if (!alive && outcome === "graceful" && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
|
|
16288
16972
|
const wt = run.worktree_path;
|
|
16289
16973
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
16290
16974
|
if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
|
|
@@ -16309,7 +16993,7 @@ async function tickOnce(deps) {
|
|
|
16309
16993
|
narrate(deps, `${droneLabel(run)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432 \u043B\u043E\u0433\u0435 (TASWM-229).`);
|
|
16310
16994
|
continue;
|
|
16311
16995
|
}
|
|
16312
|
-
if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
|
|
16996
|
+
if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
|
|
16313
16997
|
const wt = run.worktree_path;
|
|
16314
16998
|
const wtAlive = !!wt && worktreeExists(wt);
|
|
16315
16999
|
const canContinue = wtAlive && hasTranscript(wt);
|
|
@@ -16345,6 +17029,10 @@ async function tickOnce(deps) {
|
|
|
16345
17029
|
});
|
|
16346
17030
|
if (salvaged) continue;
|
|
16347
17031
|
}
|
|
17032
|
+
if (!alive && run.status === "running" && run.verifies_run_id != null && run.pr_url && deps.verifyVerdictComment) {
|
|
17033
|
+
const salvaged = await salvageVerifyVerdictFromPrComment(deps, run, run.verifies_run_id, run.created_at, "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u044B\u0448\u0435\u043B, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done()");
|
|
17034
|
+
if (salvaged) continue;
|
|
17035
|
+
}
|
|
16348
17036
|
if (!alive && run.status === "running" && run.reviews_run_id != null && run.pr_url && deps.ci) {
|
|
16349
17037
|
const parent = deps.db.getRun(run.reviews_run_id);
|
|
16350
17038
|
const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
|
|
@@ -16435,9 +17123,17 @@ async function tickOnce(deps) {
|
|
|
16435
17123
|
const az = deps.anomalies;
|
|
16436
17124
|
let ready = [];
|
|
16437
17125
|
try {
|
|
17126
|
+
const autoReviewSnapshot = /* @__PURE__ */ new Map();
|
|
17127
|
+
if (deps.autoReviewForRepo && az.autoReviewEnabled) {
|
|
17128
|
+
for (const r of deps.db.listRuns("review")) {
|
|
17129
|
+
if (!r.pr_url || autoReviewSnapshot.has(r.repo)) continue;
|
|
17130
|
+
autoReviewSnapshot.set(r.repo, await deps.autoReviewForRepo(effectiveRepo(deps, r)).catch(() => true));
|
|
17131
|
+
}
|
|
17132
|
+
}
|
|
16438
17133
|
const candidates = [
|
|
16439
17134
|
...buildAnomalyCandidates(deps.db, now, az.thresholds, {
|
|
16440
|
-
|
|
17135
|
+
// Машинный ответ, уточнённый проектом (TASWM-396): нет снимка — как раньше.
|
|
17136
|
+
autoReviewEnabled: (repo) => az.autoReviewEnabled && (autoReviewSnapshot.get(repo) ?? true),
|
|
16441
17137
|
...az.outboxPendingCount ? { outboxPendingCount: az.outboxPendingCount() } : {},
|
|
16442
17138
|
// resource-gate-stall (TASWM-212): та же причина, что видит лог manageRelief — один
|
|
16443
17139
|
// deps.gateReason() на тик (кэш hostReading() внутри resources.ts, лишнего спавна нет).
|
|
@@ -16633,6 +17329,76 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
16633
17329
|
throw e;
|
|
16634
17330
|
}
|
|
16635
17331
|
}
|
|
17332
|
+
var VERIFY_SCOPE_BLOCK = [
|
|
17333
|
+
"\u{1F4B8} \u041A\u041E\u041D\u0422\u0423\u0420 \u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0426\u0418\u0418 (\u0436\u0451\u0441\u0442\u043A\u043E). \u0421\u0435\u0441\u0441\u0438\u044F \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430 \u0431\u044E\u0434\u0436\u0435\u0442\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0438 \u0441\u0440\u0435\u0436\u0435\u0442 \u0442\u0435\u0431\u044F \u0431\u0435\u0437 \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F \u2014 \u0443\u043F\u0440\u0451\u0448\u044C\u0441\u044F, \u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u043E\u0432\u0441\u0435. \u041F\u043E\u044D\u0442\u043E\u043C\u0443:",
|
|
17334
|
+
"\u2022 \u0422\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0448\u044C \u0417\u0410\u042F\u0412\u041B\u0415\u041D\u041D\u041E\u0415, \u0430 \u043D\u0435 \u0432\u0435\u0441\u044C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439. \u0421\u043F\u0438\u0441\u043E\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u2014 \u0438\u0437 \u043E\u0442\u0447\u0451\u0442\u0430 \u0434\u0440\u043E\u043D\u0430 \u0438 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438; \u0432\u0441\u0451 \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u0442\u0440\u043E\u0433\u0430\u0439, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u043E \u043B\u043E\u043C\u0430\u0435\u0442\u0441\u044F \u0442\u0432\u043E\u0435\u0439 \u0436\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u043E\u0439.",
|
|
17335
|
+
"\u2022 \u041F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u0434\u0432\u0443\u0445 \u0437\u0430\u0445\u043E\u0434\u043E\u0432. \u041D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u043E\u0441\u044C \u2014 \u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0447\u0430\u0441\u0442\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 (\xAB\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0430 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434 \u0447\u0438\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0435\u043A\u0442.",
|
|
17336
|
+
"\u2022 \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u044B\u0432\u0430\u0439, \u043E\u0434\u0438\u043D \u0432\u043E\u043F\u0440\u043E\u0441 \u2014 \u043E\u0434\u0438\u043D \u0437\u0430\u0445\u043E\u0434. \u041D\u0435 \u0441\u043E\u0448\u043B\u043E\u0441\u044C \u0441\u043E \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0440\u0430\u0437\u0430 \u2014 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u043A\u0430\u043A \u043D\u0430\u0445\u043E\u0434\u043A\u0443, \u0430 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0439 \u0442\u0440\u0435\u0442\u0438\u0439 \u043A\u0440\u0443\u0433.",
|
|
17337
|
+
"\u2022 \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0441\u043F\u0435\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0412\u0421\u0415\u0413\u0414\u0410 \u043B\u0443\u0447\u0448\u0435 \u0438\u0434\u0435\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u0442. \u0427\u0435\u0441\u0442\u043D\u043E \u043D\u0430\u0437\u043E\u0432\u0438, \u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043C\u043E\u0442\u0440\u0435\u043B."
|
|
17338
|
+
].join("\n");
|
|
17339
|
+
function buildVerifierPrompt(verifiedRun, verifier, branch, files, attachmentsBlock, verifyMode = SWARM_VERIFY) {
|
|
17340
|
+
const name = verifier.name || `#${verifier.id}`;
|
|
17341
|
+
const prUrl = verifiedRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
|
|
17342
|
+
const taskUrl = verifiedRun.task_url ? ` \u0417\u0430\u0434\u0430\u0447\u0430: ${verifiedRun.task_url}.` : "";
|
|
17343
|
+
const filesBlock = files.length ? `\u0424\u0430\u0439\u043B\u044B \u0434\u0438\u0444\u0444\u0430 (${files.length}): ${files.slice(0, 40).join(", ")}${files.length > 40 ? " \u2026" : ""}.` : "\u0421\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 \u0434\u0438\u0444\u0444\u0430 gh \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u2014 \u0432\u043E\u0437\u044C\u043C\u0438 \u0435\u0433\u043E \u0441\u0430\u043C: `gh pr diff --name-only`.";
|
|
17344
|
+
return [
|
|
17345
|
+
`\u0422\u044B \u2014 \u0434\u0440\u043E\u043D-\u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420 ${name} \u0440\u043E\u044F, headless-\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C (\u041D\u0415 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0447\u0430\u0442). \u0420\u043E\u0439 \u043F\u043E\u0434\u043D\u044F\u043B \u0442\u0435\u0431\u044F \u0441\u0430\u043C \u043D\u0430 PR \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F: \u0442\u044B \u043E\u0442 \u043D\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0448\u044C \u0438 \u0435\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0435\u0448\u044C.`,
|
|
17346
|
+
"",
|
|
17347
|
+
HEADLESS_BLOCK,
|
|
17348
|
+
"",
|
|
17349
|
+
`\u0417\u0410\u0414\u0410\u0427\u0410: \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u041F\u0420\u0410\u0412\u0414\u0410 \u041B\u0418 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0442\u043E, \u0447\u0442\u043E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043E \u0432 PR ${prUrl} \u043F\u0440\u043E\u0433\u043E\u043D\u0430 ${verifiedRun.task_slug} (\u0434\u0440\u043E\u043D ${verifiedRun.name}).${taskUrl} \u0422\u044B \u0432 git-worktree \u0441 \u0447\u0435\u043A\u0430\u0443\u0442\u043E\u043C \u0432\u0435\u0442\u043A\u0438 \`${branch}\` (detached, \u043A\u043E\u043D\u0447\u0438\u043A origin) \u2014 \u043A\u043E\u0434 \u0443\u0436\u0435 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435.`,
|
|
17350
|
+
...verifiedRun.base_branch ? [`\u26A0\uFE0F PR \u043E\u0442\u043A\u0440\u044B\u0442 \u0432 \u0432\u0435\u0442\u043A\u0443 \`${verifiedRun.base_branch}\`, \u0430 \u043D\u0435 \u0432 \u0432\u0435\u0442\u043A\u0443 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E: \u0441\u0440\u0430\u0432\u043D\u0438\u0432\u0430\u0439 \u0441 \u043D\u0435\u0439 (\`git diff origin/${verifiedRun.base_branch}...HEAD\`), \u0438\u043D\u0430\u0447\u0435 \u043F\u0440\u0438\u043C\u0435\u0448\u044C \u0447\u0443\u0436\u0438\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u0430\u0447\u043A\u0438 \u0437\u0430 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u044D\u0442\u043E\u0433\u043E \u0434\u0440\u043E\u043D\u0430.`] : [],
|
|
17351
|
+
filesBlock,
|
|
17352
|
+
"",
|
|
17353
|
+
"\u26A0\uFE0F \u0422\u0412\u041E\u042F \u0417\u0410\u0414\u0410\u0427\u0410 \u2014 \u041E\u041F\u0420\u041E\u0412\u0415\u0420\u0413\u041D\u0423\u0422\u042C, \u0410 \u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042C. \u0410\u0432\u0442\u043E\u0440 \u0440\u0430\u0431\u043E\u0442\u044B \u2014 \u043F\u043B\u043E\u0445\u043E\u0439 \u0435\u0451 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0438\u0439: \u043E\u043D \u0438\u0449\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043B. \u0422\u044B \u043D\u0430\u043D\u044F\u0442 \u043D\u0430 \u043E\u0431\u0440\u0430\u0442\u043D\u043E\u0435. \u0418\u0441\u0445\u043E\u0434\u0438 \u0438\u0437 \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442, \u0438 \u0434\u043E\u0431\u0440\u043E\u0441\u043E\u0432\u0435\u0441\u0442\u043D\u043E \u043F\u043E\u043F\u044B\u0442\u0430\u0439\u0441\u044F \u044D\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C. \u041D\u0435 \u043D\u0430\u0448\u0451\u043B \u2014 \u0442\u043E\u0433\u0434\u0430 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E\u0433\u0434\u0430 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0439.",
|
|
17354
|
+
"\u26A0\uFE0F \u0424\u0410\u0419\u041B\u042B \u041D\u0415 \u041F\u0420\u0410\u0412\u0418\u0428\u042C, \u041A\u041E\u041C\u041C\u0418\u0422\u042B \u041D\u0415 \u041F\u0423\u0428\u0418\u0428\u042C. \u0427\u0438\u043D\u0438\u0442\u044C \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u0435 \u0431\u0443\u0434\u0435\u0442 \u0430\u0432\u0442\u043E\u0440. \u0422\u0432\u043E\u0439 \u043F\u0440\u043E\u0434\u0443\u043A\u0442 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442.",
|
|
17355
|
+
"\u26A0\uFE0F \u041F\u0415\u0420\u0415\u0421\u041A\u0410\u0417 \u0414\u0418\u0424\u0424\u0410 \u2014 \u041D\u0415 \u0412\u0415\u0420\u0414\u0418\u041A\u0422. \xAB\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u044B\u0433\u043B\u044F\u0434\u044F\u0442 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E\xBB, \xAB\u043A\u043E\u0434 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0435\xBB \u2014 \u044D\u0442\u043E \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430, \u0430 \u043C\u043D\u0435\u043D\u0438\u0435 \u043E \u0442\u0435\u043A\u0441\u0442\u0435. \u0414\u0438\u0444\u0444 \u0443\u0436\u0435 \u0447\u0438\u0442\u0430\u0435\u0442 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u0434\u0440\u043E\u043D-\u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u0442\u044B \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0448\u044C \u0437\u0430 \u041F\u041E\u0412\u0415\u0414\u0415\u041D\u0418\u0415. \u041A\u0430\u0436\u0434\u044B\u0439 \u043F\u0443\u043D\u043A\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u043D \u043E\u043F\u0438\u0440\u0430\u0442\u044C\u0441\u044F \u043D\u0430 \u0442\u043E, \u0447\u0442\u043E \u0442\u044B \u0417\u0410\u041F\u0423\u0421\u0422\u0418\u041B \u0438 \u0423\u0412\u0418\u0414\u0415\u041B (\u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u043B\u043E\u0433, \u043E\u0442\u0432\u0435\u0442 \u0440\u0443\u0447\u043A\u0438, \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435 \u044D\u043A\u0440\u0430\u043D\u0430).",
|
|
17356
|
+
"",
|
|
17357
|
+
VERIFY_SCOPE_BLOCK,
|
|
17358
|
+
...attachmentsBlock ? ["", attachmentsBlock] : [],
|
|
17359
|
+
"",
|
|
17360
|
+
"\u041F\u043E\u0440\u044F\u0434\u043E\u043A:",
|
|
17361
|
+
"1. \u0412\u044B\u044F\u0441\u043D\u0438, \u0427\u0422\u041E \u0418\u041C\u0415\u041D\u041D\u041E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043E: `gh pr view " + prUrl + " --body` (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 PR) + read_task() (\u0442\u0430\u043C \u0436\u0435 \u043E\u0442\u0447\u0451\u0442 \u0434\u0440\u043E\u043D\u0430 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0438 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438). \u0412\u044B\u043F\u0438\u0448\u0438 \u0441\u0435\u0431\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0445 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0439 \u2014 \u043F\u043E \u043D\u0438\u043C \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C.",
|
|
17362
|
+
"2. \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u0415\u0414\u0418 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435. \u0422\u0435\u0441\u0442\u044B \u2014 \u043D\u0435 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0441\u0430\u043C\u0438 \u043F\u043E \u0441\u0435\u0431\u0435: \u0438\u0445 \u043F\u0438\u0441\u0430\u043B \u0442\u043E\u0442 \u0436\u0435 \u0430\u0432\u0442\u043E\u0440. \u0413\u0434\u0435 \u043C\u043E\u0436\u043D\u043E \u2014 \u0433\u043E\u043D\u044F\u0439 \u0444\u0438\u0447\u0443 \u0442\u0430\u043A, \u043A\u0430\u043A \u0435\u0451 \u0443\u0432\u0438\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A (\u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0441\u0442\u0435\u043A/\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u0443/\u0434\u0451\u0440\u043D\u0438 \u0440\u0443\u0447\u043A\u0443; \u0440\u0435\u0446\u0435\u043F\u0442 \u2014 README \u0438 \u0441\u043A\u0438\u043B\u043B\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F). \u041D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u2014 \u044D\u0442\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \xAB\u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u043E\u0434\u0438\u0442\u0441\u044F\xBB, \u0438 \u043E\u043D \u0446\u0435\u043D\u043D\u0435\u0435 \u0432\u0435\u0436\u043B\u0438\u0432\u043E\u0433\u043E \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F.",
|
|
17363
|
+
"3. \u041F\u041E\u0418\u0429\u0418, \u0427\u0422\u041E \u0421\u041B\u041E\u041C\u0410\u041B\u041E\u0421\u042C \u0420\u042F\u0414\u041E\u041C. \u0421\u043E\u0441\u0435\u0434\u043D\u0438\u0435 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0438 \u0442\u043E\u0439 \u0436\u0435 \u0437\u043E\u043D\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0434\u0438\u0444\u0444 \u0437\u0430\u0434\u0435\u043B \u043A\u043E\u0441\u0432\u0435\u043D\u043D\u043E; \u0441\u0442\u0430\u0440\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 (\u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u2014 \u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u043E\u0441\u0442\u044C \u0438 \u043E\u0442\u043A\u0430\u0442); \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u0432\u044B\u0437\u043E\u0432 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C); \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u044B\u0439 \u0432\u044B\u0437\u043E\u0432. " + (verifyMode === "local" ? "\u0413\u0435\u0439\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u0441\u0430\u043C (\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=local, CI \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0431\u044B\u0442\u044C \u0432\u043E\u0432\u0441\u0435 \u2014 \u0435\u0433\u043E \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u041D\u0415 \u0434\u0435\u0444\u0435\u043A\u0442 PR \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0434\u043E)." : "\u0422\u0435\u0441\u0442-\u0441\u044C\u044E\u0442 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u043D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 \u2014 \u0435\u0433\u043E \u0443\u0436\u0435 \u043F\u0440\u043E\u0433\u043D\u0430\u043B CI \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 (`gh pr checks " + prUrl + "`); \u0442\u0432\u043E\u0451 \u0434\u0435\u043B\u043E \u0442\u043E, \u0447\u0435\u0433\u043E \u0432 CI \u043D\u0435\u0442."),
|
|
17364
|
+
"4. \u041F\u0420\u041E\u0414\u0410\u0412\u0418 \u041A\u0420\u0410\u0419\u041D\u0418\u0415 \u0421\u041B\u0423\u0427\u0410\u0418: \u043F\u0443\u0441\u0442\u043E/\u043D\u043E\u043B\u044C/\u043E\u0447\u0435\u043D\u044C \u0434\u043B\u0438\u043D\u043D\u043E\u0435, \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0435 \u043F\u0440\u0430\u0432\u0430, \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430\u044F \u0441\u0435\u0442\u044C, \u0433\u043E\u043D\u043A\u0430, \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u0430\u044F \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0430. \u0425\u043E\u0442\u044F \u0431\u044B \u0434\u0432\u0430 \u0442\u0430\u043A\u0438\u0445 \u0437\u0430\u0445\u043E\u0434\u0430 \u2014 \u044D\u0442\u043E \u043C\u0438\u043D\u0438\u043C\u0443\u043C \u0434\u043E\u0431\u0440\u043E\u0441\u043E\u0432\u0435\u0441\u0442\u043D\u043E\u0439 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u043E\u043F\u0440\u043E\u0432\u0435\u0440\u0433\u043D\u0443\u0442\u044C.",
|
|
17365
|
+
`5. \u041E\u0441\u0442\u0430\u0432\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043D\u0430 PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u0435\u0433\u043E \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041F\u0415\u0420\u0412\u041E\u0419 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \u2014 \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u043E \u0438\u0437 \u0417\u0410\u0413\u041B\u0410\u0412\u041D\u042B\u041C\u0418: **\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E** / **\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F** / **\u041D\u0410\u0428\u0401\u041B**. \u0414\u0430\u043B\u044C\u0448\u0435: \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B (\u043A\u043E\u043C\u0430\u043D\u0434\u0430\u043C\u0438), \u0447\u0442\u043E \u0443\u0432\u0438\u0434\u0435\u043B, \u0438 \u2014 \u0434\u043B\u044F \u0434\u0432\u0443\u0445 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0445 \u2014 \u043A\u0430\u043A \u044D\u0442\u043E \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C.`,
|
|
17366
|
+
`6. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "confirmed" | "not_reproduced" | "found_issues", summary: "\u0447\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B, \u0447\u0442\u043E \u0443\u0432\u0438\u0434\u0435\u043B, \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B"). \u0420\u043E\u0439 \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0440\u0430\u0437\u043D\u0435\u0441\u0451\u0442 summary \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u043A\u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 \u2014 \u0442\u0440\u0435\u0442\u044C\u0435\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442: \u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u043E\u0432\u0441\u0435 \u2014 \u044D\u0442\u043E "not_reproduced" \u0441 \u0447\u0435\u0441\u0442\u043D\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0432 summary.`,
|
|
17367
|
+
"",
|
|
17368
|
+
"\u0427\u0442\u043E \u041D\u0415 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C: \u0432\u043A\u0443\u0441\u043E\u0432\u044B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438 \u043A\u043E\u0434\u0430, \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 CI \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u0447\u0443\u0436\u0438\u0435 \u0441\u043B\u043E\u043C\u0430\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B, \u043D\u0435 \u043E\u0442\u043D\u043E\u0441\u044F\u0449\u0438\u0435\u0441\u044F \u043A \u044D\u0442\u043E\u043C\u0443 \u0434\u0438\u0444\u0444\u0443. \u0417\u0430\u043C\u0435\u0442\u0438\u043B \u0442\u0430\u043A\u043E\u0435 \u2014 \u0441\u043A\u0430\u0436\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 (\u0438\u043B\u0438 \u0447\u0435\u0440\u0435\u0437 note), \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0442 \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442."
|
|
17369
|
+
].join("\n");
|
|
17370
|
+
}
|
|
17371
|
+
async function spawnVerifier(deps, verifiedRun, files) {
|
|
17372
|
+
const repo = normalizeRepo(verifiedRun.repo || deps.repoFallback);
|
|
17373
|
+
const branch = await worktreeBranchFor(verifiedRun, deps.prHeadBranch);
|
|
17374
|
+
const verifyMode = deps.verifyMode ? await deps.verifyMode(repo).catch(() => void 0) : void 0;
|
|
17375
|
+
const verifier = deps.db.createRun({
|
|
17376
|
+
task_slug: verifiedRun.task_slug,
|
|
17377
|
+
repo,
|
|
17378
|
+
kind: "verifier",
|
|
17379
|
+
verifies_run_id: verifiedRun.id,
|
|
17380
|
+
review_cycle: verifiedRun.review_cycle,
|
|
17381
|
+
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
17382
|
+
workspace_id: verifiedRun.workspace_id
|
|
17383
|
+
});
|
|
17384
|
+
const wt = join29(deps.worktreesDir, basename4(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
17385
|
+
try {
|
|
17386
|
+
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
17387
|
+
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
17388
|
+
const attachments = await deps.taskAttachments?.(verifiedRun.task_slug, wt, verifiedRun.workspace_id);
|
|
17389
|
+
const prompt = buildVerifierPrompt(verifiedRun, verifier, branch, files, attachments, verifyMode);
|
|
17390
|
+
const pid = deps.launch({ worktree: wt, run: verifier, prompt });
|
|
17391
|
+
deps.db.updateRun(verifier.id, { status: "running", worktree_path: wt, branch, pid, pr_url: verifiedRun.pr_url });
|
|
17392
|
+
deps.db.addEvent(verifiedRun.id, "step", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u0434\u043D\u044F\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 ${verifier.name} (#${verifier.id}) \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u043D\u0430 \u043E\u043F\u0440\u043E\u0432\u0435\u0440\u0436\u0435\u043D\u0438\u0435`);
|
|
17393
|
+
return deps.db.getRun(verifier.id);
|
|
17394
|
+
} catch (e) {
|
|
17395
|
+
await deps.git.removeWorktree(repo, wt).catch(() => {
|
|
17396
|
+
});
|
|
17397
|
+
deps.db.updateRun(verifier.id, { status: "failed" });
|
|
17398
|
+
deps.db.addEvent(verifier.id, "failed", `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C: ${String(e)}`);
|
|
17399
|
+
throw e;
|
|
17400
|
+
}
|
|
17401
|
+
}
|
|
16636
17402
|
async function respawnRun(deps, run, mode, nudge) {
|
|
16637
17403
|
const repo = normalizeRepo(run.repo || deps.repoFallback);
|
|
16638
17404
|
const wt = run.worktree_path || (mode === "fresh" ? droneWorktreePath(deps.worktreesDir, repo, run.task_slug) : null);
|
|
@@ -16751,7 +17517,11 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
16751
17517
|
}
|
|
16752
17518
|
const call = resilientSwarmMcp({
|
|
16753
17519
|
onDown: (ms) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u0440\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0441\u0434\u0430\u0447\u0438 \u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u044B \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u044E\u0442.`),
|
|
16754
|
-
onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
|
|
17520
|
+
onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438."),
|
|
17521
|
+
// Ретрай не молчит: обрыв, переигранный в том же вызове, всё равно виден.
|
|
17522
|
+
// Без этой строки самолечение связи — единственный путь, о котором в логе
|
|
17523
|
+
// ни слова, и разбор «почему тик подтормозил» упирается в тишину.
|
|
17524
|
+
onRetry: ({ tool, attempt, reason }) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP: ${tool} \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F (${reason}) \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 ${attempt}.`)
|
|
16755
17525
|
});
|
|
16756
17526
|
const explicitRegistry = await buildRepoRegistry(repoPaths);
|
|
16757
17527
|
if (repoPaths.length > 0 && explicitRegistry.length < repoPaths.length) {
|
|
@@ -16793,6 +17563,8 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
16793
17563
|
reconciled: rec.reconciled,
|
|
16794
17564
|
host,
|
|
16795
17565
|
repoMap,
|
|
17566
|
+
prodMcp: call,
|
|
17567
|
+
// TASWM-325: состояние связи для `swarm status`
|
|
16796
17568
|
// Периодическая пересборка (шаг 0a4 тика): применяем новый реестр к ЖИВОМУ клиенту —
|
|
16797
17569
|
// и список для claim/heartbeat, и обратную карту owner/repo → путь.
|
|
16798
17570
|
refreshRepoMap: async () => {
|
|
@@ -16861,7 +17633,8 @@ async function runDaemon(opts) {
|
|
|
16861
17633
|
reconciled,
|
|
16862
17634
|
host,
|
|
16863
17635
|
repoMap: repoMapState,
|
|
16864
|
-
refreshRepoMap: rebuildRepoMap
|
|
17636
|
+
refreshRepoMap: rebuildRepoMap,
|
|
17637
|
+
prodMcp
|
|
16865
17638
|
} = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
|
|
16866
17639
|
await preflightAuth();
|
|
16867
17640
|
const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
|
|
@@ -16966,7 +17739,15 @@ async function runDaemon(opts) {
|
|
|
16966
17739
|
const client = db;
|
|
16967
17740
|
heartbeat = {
|
|
16968
17741
|
push: async () => {
|
|
16969
|
-
const
|
|
17742
|
+
const stormActive = client.stormActive();
|
|
17743
|
+
const stormStartedAt = stormActive ? client.currentStormStartedAt() : null;
|
|
17744
|
+
const reply = await client.heartbeat(
|
|
17745
|
+
daemonVersion?.get() ?? null,
|
|
17746
|
+
control.state ?? null,
|
|
17747
|
+
lastOwnCapacity,
|
|
17748
|
+
hostResourcesForProd(),
|
|
17749
|
+
{ active: stormActive, reason: stormStartedAt ? `\u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441 ${stormStartedAt} UTC` : null }
|
|
17750
|
+
);
|
|
16970
17751
|
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
16971
17752
|
if (reply.commands.length) onHostCommands(reply.commands);
|
|
16972
17753
|
if (reply.logRequests.length) {
|
|
@@ -17180,6 +17961,36 @@ async function runDaemon(opts) {
|
|
|
17180
17961
|
verdictCommentFn = (prUrl, sinceIso) => latestVerdictComment(prUrl, sinceIso);
|
|
17181
17962
|
reviewCutoffAtValue = reviewCutoffAt();
|
|
17182
17963
|
}
|
|
17964
|
+
let spawnVerifierFn;
|
|
17965
|
+
let verifyVerdictCommentFn;
|
|
17966
|
+
if (SWARM_AUTO_VERIFY) {
|
|
17967
|
+
verifyVerdictCommentFn = (prUrl, sinceIso) => latestVerifyVerdictComment(prUrl, sinceIso);
|
|
17968
|
+
spawnVerifierFn = (verifiedRun, files) => spawnVerifier(
|
|
17969
|
+
{
|
|
17970
|
+
db,
|
|
17971
|
+
repoFallback: opts.repo,
|
|
17972
|
+
worktreesDir,
|
|
17973
|
+
launch,
|
|
17974
|
+
git: { prepareDetachedWorktree, setWorktreeIdentity, removeWorktree },
|
|
17975
|
+
prHeadBranch,
|
|
17976
|
+
...verifyModeReader ? { verifyMode: verifyModeReader } : {},
|
|
17977
|
+
...tl?.fetchFile ? {
|
|
17978
|
+
taskAttachments: async (slug, worktree, workspaceId) => {
|
|
17979
|
+
const client = tl.forWorkspace(workspaceId);
|
|
17980
|
+
const task = await client.getTask(slug);
|
|
17981
|
+
return prepareTaskAttachments({
|
|
17982
|
+
worktree,
|
|
17983
|
+
refs: task?.attachments,
|
|
17984
|
+
fetchFile: client.fetchFile.bind(client)
|
|
17985
|
+
});
|
|
17986
|
+
}
|
|
17987
|
+
} : {}
|
|
17988
|
+
},
|
|
17989
|
+
verifiedRun,
|
|
17990
|
+
files
|
|
17991
|
+
).then(() => {
|
|
17992
|
+
});
|
|
17993
|
+
}
|
|
17183
17994
|
const narrator = makeOvermindNarrator();
|
|
17184
17995
|
const bootPause = loadPauseState(SWARM_HOME);
|
|
17185
17996
|
const control = { paused: bootPause.paused, state: bootPause.paused ? bootPause : null };
|
|
@@ -17474,7 +18285,10 @@ async function runDaemon(opts) {
|
|
|
17474
18285
|
// Авто-ревью (TASWM-148): ключи есть только когда SWARM_AUTO_REVIEW включён (дефолт да) —
|
|
17475
18286
|
// spawnReviewerFn/verdictCommentFn/reviewCutoffAtValue выставляются В ОДНОМ if(SWARM_AUTO_REVIEW)
|
|
17476
18287
|
// блоке выше, поэтому один и тот же гейт на все три (без отдельных тернарных условий).
|
|
17477
|
-
...spawnReviewerFn ? { spawnReviewer: spawnReviewerFn, verdictComment: verdictCommentFn, reviewCap: SWARM_REVIEW_CAP, reviewCutoffAt: reviewCutoffAtValue } : {}
|
|
18288
|
+
...spawnReviewerFn ? { spawnReviewer: spawnReviewerFn, verdictComment: verdictCommentFn, reviewCap: SWARM_REVIEW_CAP, reviewCutoffAt: reviewCutoffAtValue } : {},
|
|
18289
|
+
// Верификация (TASWM-344) — свой тумблер, независимый от авто-ревью: проверить, правда
|
|
18290
|
+
// ли работает заявленное, осмысленно и там, где ревью диффа выключено.
|
|
18291
|
+
...spawnVerifierFn ? { spawnVerifier: spawnVerifierFn, verifyVerdictComment: verifyVerdictCommentFn } : {}
|
|
17478
18292
|
};
|
|
17479
18293
|
let web;
|
|
17480
18294
|
let droneChannel = null;
|
|
@@ -17705,14 +18519,41 @@ async function runDaemon(opts) {
|
|
|
17705
18519
|
// печатает `swarm status`. Без него человек снова гадал бы, какие репозитории машина
|
|
17706
18520
|
// считает своими и в каком дереве работает при двух копиях одного клона.
|
|
17707
18521
|
repoMap: () => repoMapRows(repoMapState),
|
|
18522
|
+
// TASWM-325: СВЯЗЬ С ПРОДОМ и ёмкость — то, чего `swarm status` не мог узнать ниоткуда.
|
|
18523
|
+
// Собираем из трёх источников, каждый из которых знает свою часть: отказ по токену —
|
|
18524
|
+
// authState (TASWM-324, он ловит 401 на подключении, где его только и видно), живо ли
|
|
18525
|
+
// соединение — транспорт (mcpResilient.link), буфер и момент удачного пульса — клиент.
|
|
18526
|
+
// Мерить связь по возврату прод-вызова НЕЛЬЗЯ: боевой каллер не бросает, а отдаёт
|
|
18527
|
+
// {isError:true}, и такой замер показывал бы «связь есть» на мёртвом проде.
|
|
18528
|
+
// В sqlite-режиме клиента нет вовсе — раздел честно молчит, а не рисует зелёное.
|
|
18529
|
+
...db instanceof SwarmClient ? {
|
|
18530
|
+
link: () => ({
|
|
18531
|
+
...composeLink({
|
|
18532
|
+
api: TASKLESS_API,
|
|
18533
|
+
authDenied: authStateLine(),
|
|
18534
|
+
transport: prodMcp?.link() ?? null,
|
|
18535
|
+
pending: db.pendingCount(),
|
|
18536
|
+
now: Date.now()
|
|
18537
|
+
}),
|
|
18538
|
+
heardAt: db.lastHeardAt()
|
|
18539
|
+
})
|
|
18540
|
+
} : {},
|
|
18541
|
+
// Потолок дронов — УЖЕ посчитанный тиком (lastDroneCap), без похода в прод из ручки
|
|
18542
|
+
// статуса: /status обязан отвечать мгновенно и при лежащем проде.
|
|
18543
|
+
maxDrones: () => lastDroneCap,
|
|
17708
18544
|
// Гейт мержа (TASWM-355): ручка /merge-gate отвечает по ЖИВОЙ настройке autoReview того
|
|
17709
18545
|
// проекта, которому принадлежит PR (ридер выше), а не по глобальному env машины. Именно
|
|
17710
18546
|
// этот ответ и превращается в запрет: PreToolUse-хук на `gh pr merge` спрашивает его
|
|
17711
18547
|
// через `swarm merge-gate` и отказывает в команде, пока вердикта ревьюера нет.
|
|
18548
|
+
// TASWM-396: тот же ридер решает и подъём ревьюера, не только мерж — иначе проектная
|
|
18549
|
+
// настройка «ревью не нужно» ничего не выключала, а машина продолжала платить.
|
|
18550
|
+
...autoReviewProject ? { autoReviewForRepo: autoReviewProject } : {},
|
|
17712
18551
|
mergeGate: makeMergeGateResolver({
|
|
17713
18552
|
listRuns: () => db.listRuns(),
|
|
17714
18553
|
...autoReviewProject ? { autoReview: autoReviewProject } : {},
|
|
17715
|
-
fallbackAutoReview: SWARM_AUTO_REVIEW
|
|
18554
|
+
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
18555
|
+
autoVerify: SWARM_AUTO_VERIFY
|
|
18556
|
+
// TASWM-344: вторая ось приёмки — вердикт верификатора
|
|
17716
18557
|
}),
|
|
17717
18558
|
// Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
|
|
17718
18559
|
// narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
|
|
@@ -17760,6 +18601,11 @@ async function runDaemon(opts) {
|
|
|
17760
18601
|
console.log(
|
|
17761
18602
|
`overmind \u043F\u043E\u0434\u043D\u044F\u0442. default-repo=${opts.repo || "(\u043D\u0435\u0442)"} backend=${SWARM_BACKEND} taskless=${tl ? "on" : "off (slug-only)"}`
|
|
17762
18603
|
);
|
|
18604
|
+
const denied = authStateLine();
|
|
18605
|
+
if (denied) {
|
|
18606
|
+
console.error(`[overmind] \u26D4 ${denied}`);
|
|
18607
|
+
console.error("[overmind] \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u041D\u0415 \u0432\u044B\u0431\u0440\u0430\u043D \u2014 \u043E\u043D \u043D\u0430\u0432\u044F\u0437\u0430\u043D \u043E\u0442\u043A\u0430\u0437\u043E\u043C: \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443, \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox.");
|
|
18608
|
+
}
|
|
17763
18609
|
console.log(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430: http://127.0.0.1:${web.port} \u2190 \u043E\u0442\u043A\u0440\u043E\u0439 \u0438 \u0432\u043E\u0439\u0434\u0438 \u0447\u0435\u0440\u0435\u0437 Taskless`);
|
|
17764
18610
|
const interval = opts.intervalMs ?? 4e3;
|
|
17765
18611
|
writeHeartbeat(HEARTBEAT_PATH, Date.now());
|
|
@@ -17847,6 +18693,7 @@ export {
|
|
|
17847
18693
|
STALL_FLOOR_MS,
|
|
17848
18694
|
STALL_MS,
|
|
17849
18695
|
STORM_DEAD_LIMIT,
|
|
18696
|
+
VERIFY_SPAWN_LIMIT,
|
|
17850
18697
|
WATCHDOG_PARK_REASON,
|
|
17851
18698
|
buildChangesWakeNudge,
|
|
17852
18699
|
buildCiReadyNudge,
|
|
@@ -17856,6 +18703,7 @@ export {
|
|
|
17856
18703
|
buildResolverPrompt,
|
|
17857
18704
|
buildReviewChecklist,
|
|
17858
18705
|
buildReviewerPrompt,
|
|
18706
|
+
buildVerifierPrompt,
|
|
17859
18707
|
cancelRun,
|
|
17860
18708
|
hasLiveDescendant,
|
|
17861
18709
|
lastChangesSummary,
|
|
@@ -17870,6 +18718,7 @@ export {
|
|
|
17870
18718
|
reviveStuck,
|
|
17871
18719
|
runDaemon,
|
|
17872
18720
|
spawnReviewer,
|
|
18721
|
+
spawnVerifier,
|
|
17873
18722
|
syncCrashedOnStartup,
|
|
17874
18723
|
tickOnce,
|
|
17875
18724
|
verdictCommentMark,
|