@taskless-app/swarm 0.6.48 → 0.6.50
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 +262 -25
- package/dist/overmind.js +485 -53
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -666,7 +666,7 @@ var init_modelPolicy = __esm({
|
|
|
666
666
|
label: "Anthropic",
|
|
667
667
|
executorId: "claude-code",
|
|
668
668
|
selectable: true,
|
|
669
|
-
roles: { executor: "claude-opus-5", review: "claude-sonnet-5" }
|
|
669
|
+
roles: { executor: "claude-opus-5-5", review: "claude-sonnet-5" }
|
|
670
670
|
},
|
|
671
671
|
// TASWM-565 · шаг 1 из четырёх: провайдер в реестре ЕСТЬ, выбрать его ещё нельзя.
|
|
672
672
|
// Имён моделей здесь нет СОЗНАТЕЛЬНО: у владельца подписка, состав моделей в ней нам
|
|
@@ -1277,6 +1277,9 @@ var init_cost = __esm({
|
|
|
1277
1277
|
});
|
|
1278
1278
|
|
|
1279
1279
|
// ../swarm-core/src/modelPricing.ts
|
|
1280
|
+
function cacheReadFactorOf(pricing) {
|
|
1281
|
+
return pricing.cacheReadFactor ?? CACHE_READ_FACTOR;
|
|
1282
|
+
}
|
|
1280
1283
|
function findModelPricing(model, table = MODEL_PRICING) {
|
|
1281
1284
|
if (!model) return void 0;
|
|
1282
1285
|
const exact = table[model];
|
|
@@ -1298,6 +1301,10 @@ var init_modelPricing = __esm({
|
|
|
1298
1301
|
// и «почему прогон вчера стоил иначе» приходится выяснять по коду, а не по таблице.
|
|
1299
1302
|
// Меняется цена — меняется строка, одним видимым коммитом.
|
|
1300
1303
|
"claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
|
|
1304
|
+
// Opus 5.5 — 4/20, и чтение из кеша у него 5 % входа ($0.20), а не обычные 10 %.
|
|
1305
|
+
// У дронов чтение из кеша — основная часть токенов: с общим множителем прогон на
|
|
1306
|
+
// Opus 5.5 стоил бы в расчёте почти вдвое больше настоящего.
|
|
1307
|
+
"claude-opus-5-5": { inputPerMTok: 4, outputPerMTok: 20, cacheReadFactor: 0.05 },
|
|
1301
1308
|
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1302
1309
|
"claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
1303
1310
|
// Прогоны и вызовы на 4.x остаются в истории — строки держим ради их стоимости.
|
|
@@ -1716,13 +1723,61 @@ function similarTasks(model, text, limit = 5) {
|
|
|
1716
1723
|
}
|
|
1717
1724
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1718
1725
|
}
|
|
1726
|
+
function historyFiles(model) {
|
|
1727
|
+
let idx = historyIndex.get(model);
|
|
1728
|
+
if (!idx) {
|
|
1729
|
+
const files = new Set(model.entries.flatMap((e) => e.files));
|
|
1730
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
1731
|
+
for (const f of files) {
|
|
1732
|
+
const base3 = f.slice(f.lastIndexOf("/") + 1);
|
|
1733
|
+
byBase.set(base3, [...byBase.get(base3) ?? [], f]);
|
|
1734
|
+
}
|
|
1735
|
+
idx = { files, byBase };
|
|
1736
|
+
historyIndex.set(model, idx);
|
|
1737
|
+
}
|
|
1738
|
+
return idx;
|
|
1739
|
+
}
|
|
1740
|
+
function namedPaths(model, text, fileExists) {
|
|
1741
|
+
const { files, byBase } = historyFiles(model);
|
|
1742
|
+
const known = (p) => files.has(p) || (fileExists?.(p) ?? false);
|
|
1743
|
+
const raw = [...new Set((text.match(PATH_CANDIDATE) ?? []).map((c) => c.replace(/^\.?\/+/, "").replace(/\.+$/, "")))].filter((c) => c && !c.split("/").includes(".."));
|
|
1744
|
+
raw.sort((a, b) => b.split("/").length - a.split("/").length);
|
|
1745
|
+
const out = [];
|
|
1746
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
1747
|
+
const take = (p) => {
|
|
1748
|
+
if (out.includes(p)) return;
|
|
1749
|
+
out.push(p);
|
|
1750
|
+
dirs.add(dirOf(p));
|
|
1751
|
+
};
|
|
1752
|
+
for (const c of raw) {
|
|
1753
|
+
if (known(c)) {
|
|
1754
|
+
take(c);
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
const base3 = c.slice(c.lastIndexOf("/") + 1);
|
|
1758
|
+
let matches = (byBase.get(base3) ?? []).filter((f) => f === c || f.endsWith(`/${c}`));
|
|
1759
|
+
if (matches.length > 1) matches = matches.filter((f) => dirs.has(dirOf(f)));
|
|
1760
|
+
if (matches.length === 1) {
|
|
1761
|
+
take(matches[0]);
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
if (!c.includes("/") && fileExists) {
|
|
1765
|
+
const sibling = [...dirs].map((d) => d ? `${d}/${c}` : c).filter(fileExists);
|
|
1766
|
+
if (sibling.length === 1) take(sibling[0]);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return out;
|
|
1770
|
+
}
|
|
1719
1771
|
function predictFiles(model, opts) {
|
|
1720
1772
|
if (!hasZoneModel(model)) return [];
|
|
1721
1773
|
const limit = opts.limit ?? 40;
|
|
1722
1774
|
const best = /* @__PURE__ */ new Map();
|
|
1775
|
+
const named = opts.text ? namedPaths(model, opts.text, opts.fileExists) : [];
|
|
1776
|
+
for (const path of named) best.set(path, { path, weight: 1, why: "\u043D\u0430\u0437\u0432\u0430\u043D \u0432 \u0437\u0430\u0434\u0430\u0447\u0435", named: true });
|
|
1777
|
+
const factor = named.length ? NAMED_BACKGROUND_FACTOR : 1;
|
|
1723
1778
|
const put = (path, weight, why) => {
|
|
1724
1779
|
const cur = best.get(path);
|
|
1725
|
-
if (!cur || cur.weight < weight) best.set(path, { path, weight, why });
|
|
1780
|
+
if (!cur || cur.weight < weight * factor) best.set(path, { path, weight: weight * factor, why });
|
|
1726
1781
|
};
|
|
1727
1782
|
if (opts.text) {
|
|
1728
1783
|
const sim = similarTasks(model, opts.text, opts.similar ?? 5);
|
|
@@ -1739,12 +1794,18 @@ function predictFiles(model, opts) {
|
|
|
1739
1794
|
}
|
|
1740
1795
|
return [...best.values()].sort((a, b) => b.weight - a.weight || a.path.localeCompare(b.path)).slice(0, limit);
|
|
1741
1796
|
}
|
|
1797
|
+
function heldByPrediction(predicted) {
|
|
1798
|
+
const named = predicted.filter((p) => p.named);
|
|
1799
|
+
return (named.length ? named : predicted).map((p) => p.path);
|
|
1800
|
+
}
|
|
1742
1801
|
function overlapRisk(predicted, actual, opts = {}) {
|
|
1743
1802
|
const o = { ...OVERLAP_DEFAULTS, ...opts };
|
|
1744
1803
|
const actualSet = new Set(actual);
|
|
1745
1804
|
const actualDirs = new Set([...actualSet].map(dirOf).filter(Boolean));
|
|
1746
1805
|
const totalWeight = predicted.reduce((a, p) => a + p.weight, 0);
|
|
1747
1806
|
if (!totalWeight) return { overlap: false, score: 0, sharedFiles: [], sharedDirs: [] };
|
|
1807
|
+
const hasNamed = predicted.some((p) => p.named);
|
|
1808
|
+
let namedHit = false;
|
|
1748
1809
|
let hit = 0;
|
|
1749
1810
|
let strong = false;
|
|
1750
1811
|
const sharedFiles = [];
|
|
@@ -1753,24 +1814,26 @@ function overlapRisk(predicted, actual, opts = {}) {
|
|
|
1753
1814
|
if (actualSet.has(p.path)) {
|
|
1754
1815
|
hit += p.weight;
|
|
1755
1816
|
sharedFiles.push(p.path);
|
|
1756
|
-
if (p.
|
|
1817
|
+
if (p.named) namedHit = true;
|
|
1818
|
+
if (p.weight >= o.strongFile && (p.named || !hasNamed)) strong = true;
|
|
1757
1819
|
continue;
|
|
1758
1820
|
}
|
|
1759
1821
|
const d = dirOf(p.path);
|
|
1760
1822
|
if (d && actualDirs.has(d)) {
|
|
1761
1823
|
hit += p.weight * 0.25;
|
|
1762
1824
|
sharedDirs.add(d);
|
|
1825
|
+
if (p.named) namedHit = true;
|
|
1763
1826
|
}
|
|
1764
1827
|
}
|
|
1765
1828
|
const score = Math.min(1, hit / totalWeight);
|
|
1766
1829
|
return {
|
|
1767
|
-
overlap: strong || score >= o.threshold,
|
|
1830
|
+
overlap: strong || score >= o.threshold && (!hasNamed || namedHit),
|
|
1768
1831
|
score,
|
|
1769
1832
|
sharedFiles: sharedFiles.slice(0, 10),
|
|
1770
1833
|
sharedDirs: [...sharedDirs].slice(0, 5)
|
|
1771
1834
|
};
|
|
1772
1835
|
}
|
|
1773
|
-
var SPINE_ZONE, DEFAULTS, EMPTY_ZONE_MODEL, GENERIC_DIRS, WORKSPACE_DIRS, GENERIC_TEST_DIRS, STOP_TERMS, OVERLAP_DEFAULTS;
|
|
1836
|
+
var SPINE_ZONE, DEFAULTS, EMPTY_ZONE_MODEL, GENERIC_DIRS, WORKSPACE_DIRS, GENERIC_TEST_DIRS, STOP_TERMS, PATH_CANDIDATE, historyIndex, NAMED_BACKGROUND_FACTOR, OVERLAP_DEFAULTS;
|
|
1774
1837
|
var init_codeZones = __esm({
|
|
1775
1838
|
"../swarm-core/src/codeZones.ts"() {
|
|
1776
1839
|
"use strict";
|
|
@@ -1849,6 +1912,9 @@ var init_codeZones = __esm({
|
|
|
1849
1912
|
"\u043F\u0435\u0440\u0444",
|
|
1850
1913
|
"wip"
|
|
1851
1914
|
]);
|
|
1915
|
+
PATH_CANDIDATE = /[\w@.\-/]+\.[A-Za-z][A-Za-z0-9]{0,5}/g;
|
|
1916
|
+
historyIndex = /* @__PURE__ */ new WeakMap();
|
|
1917
|
+
NAMED_BACKGROUND_FACTOR = 0.1;
|
|
1852
1918
|
OVERLAP_DEFAULTS = { threshold: 0.2, strongFile: 0.35 };
|
|
1853
1919
|
}
|
|
1854
1920
|
});
|
|
@@ -2168,7 +2234,7 @@ function costFromUsage(usage, model, table = MODEL_PRICING) {
|
|
|
2168
2234
|
const output = usage.output_tokens ?? 0;
|
|
2169
2235
|
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
2170
2236
|
const cacheRead = usage.cache_read_input_tokens ?? 0;
|
|
2171
|
-
const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok *
|
|
2237
|
+
const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * cacheReadFactorOf(pricing)) / 1e6;
|
|
2172
2238
|
return { costUsd };
|
|
2173
2239
|
}
|
|
2174
2240
|
var init_pricing = __esm({
|
|
@@ -3480,6 +3546,18 @@ var init_zoneNotes = __esm({
|
|
|
3480
3546
|
}
|
|
3481
3547
|
});
|
|
3482
3548
|
|
|
3549
|
+
// src/store.ts
|
|
3550
|
+
function isCleanResolve(run) {
|
|
3551
|
+
return (run.stop_reason ?? "").includes(RESOLVE_CLEAN_MARK);
|
|
3552
|
+
}
|
|
3553
|
+
var RESOLVE_CLEAN_MARK;
|
|
3554
|
+
var init_store = __esm({
|
|
3555
|
+
"src/store.ts"() {
|
|
3556
|
+
"use strict";
|
|
3557
|
+
RESOLVE_CLEAN_MARK = "\u0447\u0438\u0441\u0442\u043E\u0435 \u0432\u043B\u0438\u0432\u0430\u043D\u0438\u0435 \u0431\u0430\u0437\u044B";
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
|
|
3483
3561
|
// src/agentToken.ts
|
|
3484
3562
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
3485
3563
|
import { join as join8 } from "path";
|
|
@@ -6234,6 +6312,10 @@ function rowToRun(row) {
|
|
|
6234
6312
|
// вычисляемая фаза ревью (TASWM-230)
|
|
6235
6313
|
review_cycle: row.review_cycle ?? 0,
|
|
6236
6314
|
// старые прогоны без колонки → 0
|
|
6315
|
+
// TASWM-577: намерение смержить (человек отметил «пора влить»). В sqlite-бэкенде оно
|
|
6316
|
+
// живёт только здесь — ставить его некому, кроме самого демона, зато читает его
|
|
6317
|
+
// реконсайл, и колонка нужна, чтобы отметка пережила рестарт.
|
|
6318
|
+
merge_requested_at: row.merge_requested_at ?? null,
|
|
6237
6319
|
task_title: row.task_title ?? null,
|
|
6238
6320
|
task_desc: row.task_desc ?? null,
|
|
6239
6321
|
// TASWM-556: когда заведена сама задача — по ней проверка дубля отличает свой
|
|
@@ -6271,6 +6353,7 @@ var init_db = __esm({
|
|
|
6271
6353
|
init_names2();
|
|
6272
6354
|
init_zoneNotes();
|
|
6273
6355
|
init_storm();
|
|
6356
|
+
init_store();
|
|
6274
6357
|
init_src();
|
|
6275
6358
|
TERMINAL_SQL = TERMINAL_STATUSES.map((s) => `'${s}'`).join(",");
|
|
6276
6359
|
RESOLVER_TERMINAL_SQL = [...TERMINAL_STATUSES, "stalled"].map((s) => `'${s}'`).join(",");
|
|
@@ -6308,6 +6391,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
6308
6391
|
verifies_run_id INTEGER,
|
|
6309
6392
|
verify_verdict TEXT,
|
|
6310
6393
|
review_cycle INTEGER NOT NULL DEFAULT 0,
|
|
6394
|
+
merge_requested_at TEXT,
|
|
6311
6395
|
task_title TEXT,
|
|
6312
6396
|
task_desc TEXT,
|
|
6313
6397
|
task_created_at TEXT,
|
|
@@ -6402,6 +6486,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6402
6486
|
"ALTER TABLE runs ADD COLUMN verify_verdict TEXT",
|
|
6403
6487
|
// Авто-ревью v2 (TASWM-162): номер цикла ревью — пере-ревью после фикс-итерации.
|
|
6404
6488
|
"ALTER TABLE runs ADD COLUMN review_cycle INTEGER NOT NULL DEFAULT 0",
|
|
6489
|
+
// TASWM-577: намерение смержить — по нему рой вливает PR сам, по очереди мержа.
|
|
6490
|
+
"ALTER TABLE runs ADD COLUMN merge_requested_at TEXT",
|
|
6405
6491
|
// Вычисляемая фаза ревью (TASWM-230): человеко-фраза «чего ждёт PR», её считает демон.
|
|
6406
6492
|
"ALTER TABLE runs ADD COLUMN review_phase TEXT",
|
|
6407
6493
|
// Приоритетная очередь (TASWM-269): явный приоритет прогона, выше — раньше из queued.
|
|
@@ -6624,9 +6710,19 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6624
6710
|
).get(reviewRunId);
|
|
6625
6711
|
return row ? rowToRun(row) : null;
|
|
6626
6712
|
}
|
|
6627
|
-
/**
|
|
6713
|
+
/**
|
|
6714
|
+
* Сколько резолверов С ДРОНОМ уже поднимали на этот review-PR — потолок авто-попыток.
|
|
6715
|
+
*
|
|
6716
|
+
* TASWM-575: чистые вливания базы (RESOLVE_CLEAN_MARK в stop_reason — git свёл сам, дрон
|
|
6717
|
+
* не поднимался, стоимость ноль) в счёт НЕ идут. Потолок защищает от карусели ДРОНОВ на
|
|
6718
|
+
* нетривиальном конфликте (TASWM-127); бесплатное вливание не карусель, а на оживлённом
|
|
6719
|
+
* main оно случается на каждый чужой мерж — три сдвига main выбирали потолок, и PR с
|
|
6720
|
+
* отставшей базой застревал молча (гейт мержа не пускает base_stale, а вливать уже
|
|
6721
|
+
* некому). Всё остальное (failed/stalled резолверы, в т.ч. транзиентные сбои ОС) считается
|
|
6722
|
+
* как раньше — иначе потолок потерял бы смысл на органических смертях.
|
|
6723
|
+
*/
|
|
6628
6724
|
resolverCountFor(reviewRunId) {
|
|
6629
|
-
const row = this.db.prepare("SELECT COUNT(*) n FROM runs WHERE resolves_run_id = ?").get(reviewRunId);
|
|
6725
|
+
const row = this.db.prepare("SELECT COUNT(*) n FROM runs WHERE resolves_run_id = ? AND (stop_reason IS NULL OR stop_reason NOT LIKE ?)").get(reviewRunId, `%${RESOLVE_CLEAN_MARK}%`);
|
|
6630
6726
|
return row.n;
|
|
6631
6727
|
}
|
|
6632
6728
|
/**
|
|
@@ -7177,7 +7273,7 @@ var init_droneStore = __esm({
|
|
|
7177
7273
|
|
|
7178
7274
|
// src/overmind.ts
|
|
7179
7275
|
import { homedir as homedir7 } from "os";
|
|
7180
|
-
import { existsSync as existsSync20, readFileSync as readFileSync25, readdirSync as readdirSync14, renameSync as renameSync12 } from "fs";
|
|
7276
|
+
import { existsSync as existsSync20, readFileSync as readFileSync25, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync13 } from "fs";
|
|
7181
7277
|
import { basename as basename6, dirname as dirname5, join as join34 } from "path";
|
|
7182
7278
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
7183
7279
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -7312,6 +7408,7 @@ function renderPendingRulesBlock(rules) {
|
|
|
7312
7408
|
|
|
7313
7409
|
// src/swarmClient.ts
|
|
7314
7410
|
init_zoneNotes();
|
|
7411
|
+
init_store();
|
|
7315
7412
|
init_toolFailure();
|
|
7316
7413
|
|
|
7317
7414
|
// src/logTail.ts
|
|
@@ -7524,6 +7621,12 @@ function depsFailureRetriable(code) {
|
|
|
7524
7621
|
function runRef(runKey) {
|
|
7525
7622
|
return { __runRef: runKey };
|
|
7526
7623
|
}
|
|
7624
|
+
function isoOrNull(v) {
|
|
7625
|
+
if (v == null) return null;
|
|
7626
|
+
const raw = v instanceof Date ? v.toISOString() : typeof v === "string" ? v : "";
|
|
7627
|
+
const at = Date.parse(raw);
|
|
7628
|
+
return Number.isNaN(at) ? null : new Date(at).toISOString();
|
|
7629
|
+
}
|
|
7527
7630
|
function isRunRef(v) {
|
|
7528
7631
|
return typeof v === "object" && v !== null && "__runRef" in v;
|
|
7529
7632
|
}
|
|
@@ -7991,6 +8094,17 @@ var SwarmClient = class _SwarmClient {
|
|
|
7991
8094
|
runKeyOf(id) {
|
|
7992
8095
|
return this.runs.get(id)?.runKey ?? null;
|
|
7993
8096
|
}
|
|
8097
|
+
/**
|
|
8098
|
+
* Cuid прогона В ПРОДЕ — им адресуются прод-РУЧКИ, а не MCP-тулы (TASWM-577: демон
|
|
8099
|
+
* вливает PR тем же `POST /swarm/merge`, что и кнопка, и снимает намерение тем же
|
|
8100
|
+
* `DELETE /swarm/merge-request/:runId`). null — прогон до прода ещё не долетел
|
|
8101
|
+
* (создан этим демоном, флаш не прошёл): адресовать его там нечем, и вызывающий
|
|
8102
|
+
* обязан прочитать это как «сейчас нельзя», а не как ошибку.
|
|
8103
|
+
*/
|
|
8104
|
+
prodRunId(id) {
|
|
8105
|
+
const key = this.runs.get(id)?.runKey;
|
|
8106
|
+
return key ? this.prodIdFor(key) ?? null : null;
|
|
8107
|
+
}
|
|
7994
8108
|
runByKey(key) {
|
|
7995
8109
|
const direct = [...this.runs.values()].find((r) => r.runKey === key);
|
|
7996
8110
|
if (direct) return this.mat(direct);
|
|
@@ -8061,8 +8175,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
8061
8175
|
const cands = [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId && !RESOLVER_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
|
|
8062
8176
|
return cands.length ? this.mat(cands[0]) : null;
|
|
8063
8177
|
}
|
|
8178
|
+
// Потолок авто-попыток: чистые вливания базы (дрон не поднимался, стоимость ноль) не
|
|
8179
|
+
// считаются — см. db.ts:resolverCountFor и store.ts:RESOLVE_CLEAN_MARK (TASWM-575).
|
|
8064
8180
|
resolverCountFor(reviewRunId) {
|
|
8065
|
-
return [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId).length;
|
|
8181
|
+
return [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId && !isCleanResolve(r)).length;
|
|
8066
8182
|
}
|
|
8067
8183
|
// failed/stalled ревьюеры (сорвался спавн ДО вердикта) в счёт НЕ идут; stopped/superseded
|
|
8068
8184
|
// тоже (TASWM-253: оператор закрыл зомби-ревьюера/сбросил попытки ревью — POST /swarm/
|
|
@@ -9535,6 +9651,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
9535
9651
|
verify_verdict: null,
|
|
9536
9652
|
review_phase: null,
|
|
9537
9653
|
// вычисляемая фаза ревью (TASWM-230) — прод хранит и раздаёт
|
|
9654
|
+
// TASWM-577: намерение смержить — прод-поле (его ставит человек ручкой
|
|
9655
|
+
// /swarm/merge-request), заполняется ниже из ответа.
|
|
9656
|
+
merge_requested_at: null,
|
|
9538
9657
|
// Локальная бухгалтерия (в прод-схему не уходит) — на гидрации из прода дефолт 0;
|
|
9539
9658
|
// существующая локальная запись НЕ перетирается (поле не в field-copy ниже),
|
|
9540
9659
|
// в отличие от respawns, — авто-ревью v2, TASWM-162.
|
|
@@ -9570,6 +9689,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
9570
9689
|
local.task_title = pr.taskTitle ?? local.task_title;
|
|
9571
9690
|
local.task_desc = pr.taskDesc ?? local.task_desc;
|
|
9572
9691
|
local.task_created_at = pr.taskCreatedAt ?? local.task_created_at;
|
|
9692
|
+
if ("mergeRequestedAt" in pr) local.merge_requested_at = isoOrNull(pr.mergeRequestedAt);
|
|
9573
9693
|
local.name = pr.droneName ?? local.name;
|
|
9574
9694
|
local.kind = pr.role ?? local.kind;
|
|
9575
9695
|
const heroProd = pr.heroId ?? null;
|
|
@@ -9927,6 +10047,7 @@ function parseDecision(v) {
|
|
|
9927
10047
|
}
|
|
9928
10048
|
|
|
9929
10049
|
// src/overmind.ts
|
|
10050
|
+
init_store();
|
|
9930
10051
|
init_config();
|
|
9931
10052
|
init_heartbeat();
|
|
9932
10053
|
|
|
@@ -12359,7 +12480,7 @@ function parkReasonUnretriable(reason) {
|
|
|
12359
12480
|
// package.json
|
|
12360
12481
|
var package_default = {
|
|
12361
12482
|
name: "@taskless-app/swarm",
|
|
12362
|
-
version: "0.6.
|
|
12483
|
+
version: "0.6.50",
|
|
12363
12484
|
type: "module",
|
|
12364
12485
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
12365
12486
|
keywords: [
|
|
@@ -12410,7 +12531,7 @@ var package_default = {
|
|
|
12410
12531
|
execa: "^10.0.0",
|
|
12411
12532
|
zod: "3.25.76"
|
|
12412
12533
|
},
|
|
12413
|
-
gitHead: "
|
|
12534
|
+
gitHead: "859ffe2d91bf4c791af067147515b14a41b96739"
|
|
12414
12535
|
};
|
|
12415
12536
|
|
|
12416
12537
|
// src/version.ts
|
|
@@ -12728,18 +12849,48 @@ async function prHeadBranch(prUrl, run = realRunner) {
|
|
|
12728
12849
|
return null;
|
|
12729
12850
|
}
|
|
12730
12851
|
}
|
|
12731
|
-
|
|
12852
|
+
function prRepoPath(prUrl) {
|
|
12732
12853
|
const m = prUrl.trim().match(/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/\d+/i);
|
|
12733
|
-
|
|
12734
|
-
|
|
12854
|
+
return isGithubPrUrl(prUrl) && m ? `${m[1]}/${m[2]}` : null;
|
|
12855
|
+
}
|
|
12856
|
+
async function prBaseSync(prUrl, run = realRunner) {
|
|
12857
|
+
const repoPath = prRepoPath(prUrl);
|
|
12858
|
+
if (!repoPath) return null;
|
|
12735
12859
|
try {
|
|
12736
12860
|
const j = JSON.parse(await run(["pr", "view", prUrl, "--json", "baseRefName,headRefOid"]));
|
|
12737
12861
|
const base3 = j.baseRefName;
|
|
12738
12862
|
const head = j.headRefOid;
|
|
12739
12863
|
if (!base3 || !head) return null;
|
|
12740
|
-
const out = await run(["api", `repos/${
|
|
12864
|
+
const out = await run(["api", `repos/${repoPath}/compare/${base3}...${head}?per_page=1`, "--jq", ".behind_by"]);
|
|
12741
12865
|
const behindBy = Number(out.trim());
|
|
12742
|
-
return Number.isFinite(behindBy) && behindBy >= 0 ? { behindBy, base: base3 } : null;
|
|
12866
|
+
return Number.isFinite(behindBy) && behindBy >= 0 ? { behindBy, base: base3, headSha: head } : null;
|
|
12867
|
+
} catch {
|
|
12868
|
+
return null;
|
|
12869
|
+
}
|
|
12870
|
+
}
|
|
12871
|
+
var ACTIONS_RUN_OK = /* @__PURE__ */ new Set(["success", "skipped", "neutral"]);
|
|
12872
|
+
async function prActionsRuns(prUrl, headSha2, run = realRunner) {
|
|
12873
|
+
const repoPath = prRepoPath(prUrl);
|
|
12874
|
+
if (!repoPath || !/^[0-9a-f]{7,40}$/i.test(headSha2)) return null;
|
|
12875
|
+
try {
|
|
12876
|
+
const j = JSON.parse(
|
|
12877
|
+
await run(["api", `repos/${repoPath}/actions/runs?head_sha=${headSha2}&per_page=100&exclude_pull_requests=true`])
|
|
12878
|
+
);
|
|
12879
|
+
const runs = j.workflow_runs;
|
|
12880
|
+
if (!Array.isArray(runs)) return null;
|
|
12881
|
+
if (typeof j.total_count === "number" && j.total_count > runs.length) return null;
|
|
12882
|
+
const latest = /* @__PURE__ */ new Map();
|
|
12883
|
+
for (const r of runs) {
|
|
12884
|
+
const key = `${r.workflow_id ?? r.name ?? "?"}:${r.event ?? "?"}`;
|
|
12885
|
+
const seen = latest.get(key);
|
|
12886
|
+
if (!seen || (r.id ?? 0) > (seen.id ?? 0)) latest.set(key, r);
|
|
12887
|
+
}
|
|
12888
|
+
const label2 = (r, what) => `${r.name || "workflow"} (${what || "?"})`;
|
|
12889
|
+
return {
|
|
12890
|
+
total: runs.length,
|
|
12891
|
+
active: runs.filter((r) => r.status !== "completed").map((r) => label2(r, r.status)),
|
|
12892
|
+
failed: [...latest.values()].filter((r) => r.status === "completed" && !ACTIONS_RUN_OK.has(r.conclusion ?? "")).map((r) => label2(r, r.conclusion))
|
|
12893
|
+
};
|
|
12743
12894
|
} catch {
|
|
12744
12895
|
return null;
|
|
12745
12896
|
}
|
|
@@ -15303,22 +15454,10 @@ function mergeQueueCandidates(runs, repo, repoFallback) {
|
|
|
15303
15454
|
}
|
|
15304
15455
|
var BASE_TTL_MS = 2e4;
|
|
15305
15456
|
function makeMergeQueueResolver(deps) {
|
|
15306
|
-
const
|
|
15457
|
+
const read = deps.headState ?? makeHeadStateReader(deps);
|
|
15307
15458
|
const evaluate = async (e, prUrl, ready) => {
|
|
15308
|
-
const
|
|
15309
|
-
|
|
15310
|
-
const ttl = deps.ttlMs ?? BASE_TTL_MS;
|
|
15311
|
-
if (!cached || now - cached.at >= ttl) {
|
|
15312
|
-
const base3 = await deps.baseSync(prUrl).catch(() => null);
|
|
15313
|
-
let checks = "none";
|
|
15314
|
-
if (base3?.behindBy === 0 && deps.checks) {
|
|
15315
|
-
const ci = await deps.checks(prUrl).catch(() => null);
|
|
15316
|
-
checks = ci ? ci.bucket : null;
|
|
15317
|
-
}
|
|
15318
|
-
cached = { at: now, base: base3, checks };
|
|
15319
|
-
memo.set(prUrl, cached);
|
|
15320
|
-
}
|
|
15321
|
-
return computeMergeQueue({ entry: e, ready, behindBy: cached.base ? cached.base.behindBy : null, checks: cached.checks });
|
|
15459
|
+
const st = await read(prUrl);
|
|
15460
|
+
return computeMergeQueue({ entry: e, ready, behindBy: st.behindBy, checks: st.checks });
|
|
15322
15461
|
};
|
|
15323
15462
|
return async (run) => {
|
|
15324
15463
|
if (!deps.baseSync || !run.pr_url) return null;
|
|
@@ -15353,6 +15492,124 @@ function makeMergeQueueResolver(deps) {
|
|
|
15353
15492
|
function entryOf(r) {
|
|
15354
15493
|
return { runId: r.id, prNumber: prNumberOf(r.pr_url), slug: r.task_slug };
|
|
15355
15494
|
}
|
|
15495
|
+
function makeHeadStateReader(deps) {
|
|
15496
|
+
const memo = /* @__PURE__ */ new Map();
|
|
15497
|
+
return async (prUrl) => {
|
|
15498
|
+
const now = Date.now();
|
|
15499
|
+
const ttl = deps.ttlMs ?? BASE_TTL_MS;
|
|
15500
|
+
const cached = memo.get(prUrl);
|
|
15501
|
+
if (cached && now - cached.at < ttl) return { behindBy: cached.behindBy, checks: cached.checks, headSha: cached.headSha };
|
|
15502
|
+
const base3 = deps.baseSync ? await deps.baseSync(prUrl).catch(() => null) : null;
|
|
15503
|
+
let checks = "none";
|
|
15504
|
+
if (base3?.behindBy === 0 && deps.checks) {
|
|
15505
|
+
const ci = await deps.checks(prUrl).catch(() => null);
|
|
15506
|
+
checks = ci ? ci.bucket : null;
|
|
15507
|
+
}
|
|
15508
|
+
const fresh = { behindBy: base3 ? base3.behindBy : null, checks, headSha: base3?.headSha ?? null };
|
|
15509
|
+
memo.set(prUrl, { at: now, ...fresh });
|
|
15510
|
+
return fresh;
|
|
15511
|
+
};
|
|
15512
|
+
}
|
|
15513
|
+
function hasMergeIntent(r) {
|
|
15514
|
+
return !!(r.merge_requested_at ?? "").trim();
|
|
15515
|
+
}
|
|
15516
|
+
var NO_CI_GRACE_MS = 10 * 6e4;
|
|
15517
|
+
var shortSha = (sha) => sha.slice(0, 7);
|
|
15518
|
+
function makeMergeIntentResolver(deps) {
|
|
15519
|
+
const read = deps.headState ?? makeHeadStateReader(deps);
|
|
15520
|
+
const firstSeen = /* @__PURE__ */ new Map();
|
|
15521
|
+
const ciSettled = async (prUrl, st, now) => {
|
|
15522
|
+
const sha = st.headSha;
|
|
15523
|
+
if (!sha) {
|
|
15524
|
+
return { action: "wait", code: "checks_unknown", reason: "gh \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043B HEAD PR \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0434\u043E\u0431\u0435\u0436\u0430\u043B \u043B\u0438 \u043D\u0430 \u043D\u0451\u043C CI. \u0421\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" };
|
|
15525
|
+
}
|
|
15526
|
+
if (!deps.actionsRuns) {
|
|
15527
|
+
return { action: "wait", code: "checks_unknown", reason: "\u0440\u043E\u044E \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 HEAD PR \u2014 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0432\u0441\u043B\u0435\u043F\u0443\u044E \u043D\u0435 \u0432\u043B\u0438\u0432\u0430\u044E" };
|
|
15528
|
+
}
|
|
15529
|
+
const runs = await deps.actionsRuns(prUrl, sha).catch(() => null);
|
|
15530
|
+
if (!runs) {
|
|
15531
|
+
return { action: "wait", code: "checks_unknown", reason: `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 ${shortSha(sha)} \u2014 \u0441\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C` };
|
|
15532
|
+
}
|
|
15533
|
+
if (runs.failed.length > 0) {
|
|
15534
|
+
return { action: "clear", code: "checks_red", reason: `\u043D\u0430 ${shortSha(sha)} \u0443\u043F\u0430\u043B \u043F\u0440\u043E\u0433\u043E\u043D GitHub Actions: ${runs.failed.join(", ")} \u2014 \u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u043D\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430` };
|
|
15535
|
+
}
|
|
15536
|
+
if (runs.active.length > 0) {
|
|
15537
|
+
return {
|
|
15538
|
+
action: "wait",
|
|
15539
|
+
code: "checks_pending",
|
|
15540
|
+
reason: `\u043D\u0430 ${shortSha(sha)} \u0435\u0449\u0451 \u0438\u0434\u0443\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions: ${runs.active.join(", ")} \u2014 \u0432\u0438\u0434\u0438\u043C\u044B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043C\u043E\u0433\u0443\u0442 \u0431\u044B\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C\u0438, \u043F\u043E\u043A\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u0432\u043E\u043B\u043D\u0430 \u0434\u0436\u043E\u0431 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430`
|
|
15541
|
+
};
|
|
15542
|
+
}
|
|
15543
|
+
if (st.checks === "none") {
|
|
15544
|
+
if (runs.total > 0) {
|
|
15545
|
+
return { action: "wait", code: "checks_pending", reason: `\u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 ${shortSha(sha)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C, \u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0443 PR \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 GitHub \u0438\u0445 \u043F\u043E\u043A\u0430\u0436\u0435\u0442` };
|
|
15546
|
+
}
|
|
15547
|
+
const seen = firstSeen.get(prUrl);
|
|
15548
|
+
const waited = seen && seen.sha === sha ? now - seen.at : 0;
|
|
15549
|
+
const graceMin = NO_CI_GRACE_MS / 6e4;
|
|
15550
|
+
if (waited < NO_CI_GRACE_MS) {
|
|
15551
|
+
return {
|
|
15552
|
+
action: "wait",
|
|
15553
|
+
code: "checks_pending",
|
|
15554
|
+
reason: `\u043D\u0430 ${shortSha(sha)} \u043F\u043E\u043A\u0430 \u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0438 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430 GitHub Actions \u2014 CI \u043C\u043E\u0433 \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u0434\u0436\u043E\u0431\u044B \u043F\u043E\u0441\u043B\u0435 \u043F\u0443\u0448\u0430. \u0416\u0434\u0443 \u0434\u043E ${graceMin} \u043C\u0438\u043D (\u043F\u0440\u043E\u0448\u043B\u043E ${Math.floor(waited / 6e4)})`
|
|
15555
|
+
};
|
|
15556
|
+
}
|
|
15557
|
+
return { action: "merge", code: "queue_head", reason: `\u0435\u0433\u043E \u0447\u0435\u0440\u0451\u0434 \u0438 \u0431\u0430\u0437\u0430 \u0441\u0432\u0435\u0436\u0430\u044F, \u0430 ${shortSha(sha)} ${graceMin} \u043C\u0438\u043D \u043F\u0440\u043E\u0441\u0442\u043E\u044F\u043B \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0439 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0438 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 GitHub Actions \u2014 CI \u0443 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0435\u0442` };
|
|
15558
|
+
}
|
|
15559
|
+
return { action: "merge", code: "queue_head", reason: `\u0435\u0433\u043E \u0447\u0435\u0440\u0451\u0434, \u0431\u0430\u0437\u0430 \u0441\u0432\u0435\u0436\u0430\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430 ${shortSha(sha)} \u0437\u0435\u043B\u0451\u043D\u044B\u0435 \u0438 \u0432\u0441\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B GitHub Actions \u043D\u0430 \u043D\u0451\u043C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u044B` };
|
|
15560
|
+
};
|
|
15561
|
+
const confirmLive = async (prUrl, sha, verdict) => {
|
|
15562
|
+
if (!sha) {
|
|
15563
|
+
return { action: "wait", code: "checks_unknown", reason: "gh \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043B HEAD PR \u2014 \u043D\u0435 \u043F\u043E\u043D\u044F\u0442\u044C, \u043F\u0440\u043E \u043A\u0430\u043A\u043E\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u044D\u0442\u043E\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442; \u0441\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" };
|
|
15564
|
+
}
|
|
15565
|
+
const live = deps.baseSync ? await deps.baseSync(prUrl).catch(() => null) : null;
|
|
15566
|
+
if (!live || !live.headSha) {
|
|
15567
|
+
return { action: "wait", code: "base_unknown", reason: "\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0436\u0438\u0432\u043E\u0439 HEAD PR \u2014 \u0441\u043F\u0440\u043E\u0448\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C" };
|
|
15568
|
+
}
|
|
15569
|
+
if (verdict.action === "merge" && live.behindBy > 0) {
|
|
15570
|
+
return {
|
|
15571
|
+
action: "refresh_base",
|
|
15572
|
+
code: "base_stale",
|
|
15573
|
+
reason: `\u0431\u0430\u0437\u0430 \u0443\u0448\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434 \u043D\u0430 ${live.behindBy} \u043A\u043E\u043C\u043C\u0438\u0442(\u043E\u0432) \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0433\u043E \u043C\u0435\u0440\u0436\u0430 \u2014 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u043B\u0438\u0442\u044C main \u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F CI \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0439 \u0431\u0430\u0437\u0435`
|
|
15574
|
+
};
|
|
15575
|
+
}
|
|
15576
|
+
if (live.headSha !== sha) {
|
|
15577
|
+
return { action: "wait", code: "checks_pending", reason: `\u043D\u0430 PR \u043D\u043E\u0432\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 ${shortSha(live.headSha)} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E \u0437\u0430\u043D\u043E\u0432\u043E` };
|
|
15578
|
+
}
|
|
15579
|
+
return verdict;
|
|
15580
|
+
};
|
|
15581
|
+
return async (run) => {
|
|
15582
|
+
if (!deps.baseSync || !run.pr_url || !hasMergeIntent(run)) return null;
|
|
15583
|
+
const num = prNumberOf(run.pr_url);
|
|
15584
|
+
if (num == null) return null;
|
|
15585
|
+
const marked = mergeQueueCandidates(deps.listRuns(), effectiveRepoOf(run.repo, deps.repoFallback), deps.repoFallback).filter(hasMergeIntent);
|
|
15586
|
+
const mine = marked.find((c) => prNumberOf(c.pr_url) === num);
|
|
15587
|
+
if (!mine) return null;
|
|
15588
|
+
const queue = orderMergeQueue(marked.map(entryOf));
|
|
15589
|
+
const entry = entryOf(mine);
|
|
15590
|
+
const position = queue.findIndex((e) => e.runId === entry.runId) + 1;
|
|
15591
|
+
const head = queue[0];
|
|
15592
|
+
if (head.runId !== entry.runId) {
|
|
15593
|
+
return {
|
|
15594
|
+
action: "wait",
|
|
15595
|
+
code: "queue_wait",
|
|
15596
|
+
reason: `\u0440\u043E\u0439 \u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443: \u0441\u0435\u0439\u0447\u0430\u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u044C PR #${head.prNumber}${head.slug ? ` (${head.slug})` : ""}, \u044D\u0442\u043E\u0442 \u2014 ${position}-\u0439 \u0438\u0437 ${queue.length}`,
|
|
15597
|
+
position,
|
|
15598
|
+
size: queue.length
|
|
15599
|
+
};
|
|
15600
|
+
}
|
|
15601
|
+
const st = await read(mine.pr_url);
|
|
15602
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
15603
|
+
if (st.headSha) {
|
|
15604
|
+
const seen = firstSeen.get(mine.pr_url);
|
|
15605
|
+
if (!seen || seen.sha !== st.headSha) firstSeen.set(mine.pr_url, { sha: st.headSha, at: now });
|
|
15606
|
+
}
|
|
15607
|
+
const d = computeMergeQueue({ entry, ready: queue, behindBy: st.behindBy, checks: st.checks });
|
|
15608
|
+
const verdict = d.code === "queue_head" ? await ciSettled(mine.pr_url, st, now) : { action: d.code === "base_stale" ? "refresh_base" : d.code === "checks_red" ? "clear" : "wait", code: d.code, reason: d.reason };
|
|
15609
|
+
const final = verdict.action === "merge" || verdict.action === "clear" ? await confirmLive(mine.pr_url, st.headSha, verdict) : verdict;
|
|
15610
|
+
return { ...final, position, size: queue.length, ...st.headSha ? { headSha: st.headSha } : {} };
|
|
15611
|
+
};
|
|
15612
|
+
}
|
|
15356
15613
|
function needsBaseRefresh(d) {
|
|
15357
15614
|
return d?.code === "base_stale";
|
|
15358
15615
|
}
|
|
@@ -15384,9 +15641,15 @@ function makeMergeGateParts(deps) {
|
|
|
15384
15641
|
});
|
|
15385
15642
|
};
|
|
15386
15643
|
const repoOfRun = deps.repoFullNameOf ? (r) => deps.repoFullNameOf(effectiveRepoOf(r.repo, deps.repoFallback)) : void 0;
|
|
15644
|
+
const headState = makeHeadStateReader({
|
|
15645
|
+
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
15646
|
+
...deps.checks ? { checks: deps.checks } : {},
|
|
15647
|
+
...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
|
|
15648
|
+
});
|
|
15387
15649
|
const queueFor = makeMergeQueueResolver({
|
|
15388
15650
|
listRuns: deps.listRuns,
|
|
15389
15651
|
gateAllows: async (r) => (await gateFor(r)).allowed,
|
|
15652
|
+
headState,
|
|
15390
15653
|
...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
|
|
15391
15654
|
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
15392
15655
|
...deps.checks ? { checks: deps.checks } : {},
|
|
@@ -15412,7 +15675,68 @@ function makeMergeGateParts(deps) {
|
|
|
15412
15675
|
}
|
|
15413
15676
|
};
|
|
15414
15677
|
};
|
|
15415
|
-
|
|
15678
|
+
const intentFor = makeMergeIntentResolver({
|
|
15679
|
+
listRuns: deps.listRuns,
|
|
15680
|
+
headState,
|
|
15681
|
+
...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
|
|
15682
|
+
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
15683
|
+
...deps.checks ? { checks: deps.checks } : {},
|
|
15684
|
+
...deps.actionsRuns ? { actionsRuns: deps.actionsRuns } : {},
|
|
15685
|
+
...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
|
|
15686
|
+
});
|
|
15687
|
+
return { gate, queue: queueFor, intent: intentFor };
|
|
15688
|
+
}
|
|
15689
|
+
|
|
15690
|
+
// src/prodMerge.ts
|
|
15691
|
+
init_config();
|
|
15692
|
+
init_taskless();
|
|
15693
|
+
var TIMEOUT_MS = 3e4;
|
|
15694
|
+
async function tokenFor(api, deps) {
|
|
15695
|
+
return deps.token ? deps.token() : resolveApiToken(api);
|
|
15696
|
+
}
|
|
15697
|
+
async function prodMergePr(runId, sha, deps = {}) {
|
|
15698
|
+
const api = deps.api ?? TASKLESS_API;
|
|
15699
|
+
const doFetch = deps.fetchFn ?? fetch;
|
|
15700
|
+
try {
|
|
15701
|
+
const token = await tokenFor(api, deps);
|
|
15702
|
+
if (!token) return { ok: false, error: "\u043D\u0435\u0447\u0435\u043C \u0445\u043E\u0434\u0438\u0442\u044C \u0432 \u043F\u0440\u043E\u0434 (\u043D\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u0430)" };
|
|
15703
|
+
const res = await doFetch(restEndpoint(api, "/swarm/merge"), {
|
|
15704
|
+
method: "POST",
|
|
15705
|
+
headers: { Authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
15706
|
+
body: JSON.stringify(sha ? { runId, sha } : { runId }),
|
|
15707
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
15708
|
+
});
|
|
15709
|
+
if (!res.ok) {
|
|
15710
|
+
const text = await res.text().catch(() => "");
|
|
15711
|
+
let code;
|
|
15712
|
+
try {
|
|
15713
|
+
code = JSON.parse(text).error;
|
|
15714
|
+
} catch {
|
|
15715
|
+
code = void 0;
|
|
15716
|
+
}
|
|
15717
|
+
if (code === "head_moved") return { ok: false, error: "HEAD PR \u0441\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438", headMoved: true };
|
|
15718
|
+
return { ok: false, error: `\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B (${res.status}): ${text.slice(0, 300)}` };
|
|
15719
|
+
}
|
|
15720
|
+
return { ok: true };
|
|
15721
|
+
} catch (e) {
|
|
15722
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
15723
|
+
}
|
|
15724
|
+
}
|
|
15725
|
+
async function prodClearMergeIntent(runId, deps = {}) {
|
|
15726
|
+
const api = deps.api ?? TASKLESS_API;
|
|
15727
|
+
const doFetch = deps.fetchFn ?? fetch;
|
|
15728
|
+
try {
|
|
15729
|
+
const token = await tokenFor(api, deps);
|
|
15730
|
+
if (!token) return false;
|
|
15731
|
+
const res = await doFetch(restEndpoint(api, `/swarm/merge-request/${encodeURIComponent(runId)}`), {
|
|
15732
|
+
method: "DELETE",
|
|
15733
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
15734
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
15735
|
+
});
|
|
15736
|
+
return res.ok;
|
|
15737
|
+
} catch {
|
|
15738
|
+
return false;
|
|
15739
|
+
}
|
|
15416
15740
|
}
|
|
15417
15741
|
|
|
15418
15742
|
// src/envGate.ts
|
|
@@ -18000,8 +18324,8 @@ async function headSha(repo) {
|
|
|
18000
18324
|
function runText(run) {
|
|
18001
18325
|
return [run.task_title ?? "", run.task_desc ?? ""].join(" ").trim();
|
|
18002
18326
|
}
|
|
18003
|
-
function predictForRun(model, run) {
|
|
18004
|
-
return predictFiles(model, { zone: run.area, text: runText(run) });
|
|
18327
|
+
function predictForRun(model, run, fileExists) {
|
|
18328
|
+
return predictFiles(model, { zone: run.area, text: runText(run), ...fileExists ? { fileExists } : {} });
|
|
18005
18329
|
}
|
|
18006
18330
|
function short(files, n = 3) {
|
|
18007
18331
|
return files.slice(0, n).join(", ") + (files.length > n ? ` \u0438 \u0435\u0449\u0451 ${files.length - n}` : "");
|
|
@@ -18016,17 +18340,17 @@ var SPINE_PREDICTION_WEIGHT = 0.7;
|
|
|
18016
18340
|
function spineOfPrediction(spine, predicted) {
|
|
18017
18341
|
return predicted.filter((p) => spine.has(p.path) && p.weight >= SPINE_PREDICTION_WEIGHT).map((p) => p.path);
|
|
18018
18342
|
}
|
|
18019
|
-
function decideConflict({ model, candidate, active }) {
|
|
18343
|
+
function decideConflict({ model, candidate, active, fileExists }) {
|
|
18020
18344
|
if ((candidate.kind ?? "drone") !== "drone") return { kind: "clear" };
|
|
18021
18345
|
if (!hasZoneModel(model)) return { kind: "unknown" };
|
|
18022
|
-
const predicted = predictForRun(model, candidate);
|
|
18346
|
+
const predicted = predictForRun(model, candidate, fileExists);
|
|
18023
18347
|
if (!predicted.length) return { kind: "unknown" };
|
|
18024
18348
|
if (!active.length) return { kind: "clear" };
|
|
18025
18349
|
const spine = new Set(model.spine);
|
|
18026
18350
|
const candidateSpine = new Set(spineOfPrediction(spine, predicted));
|
|
18027
18351
|
if (candidateSpine.size) {
|
|
18028
18352
|
for (const w of active) {
|
|
18029
|
-
const theirSpine = w.actual ? w.files.filter((f) => spine.has(f)) : spineOfPrediction(spine, predictForRun(model, w.run));
|
|
18353
|
+
const theirSpine = w.actual ? w.files.filter((f) => spine.has(f)) : spineOfPrediction(spine, predictForRun(model, w.run, fileExists));
|
|
18030
18354
|
const shared = theirSpine.filter((f) => candidateSpine.has(f));
|
|
18031
18355
|
if (!shared.length) continue;
|
|
18032
18356
|
return {
|
|
@@ -18150,6 +18474,8 @@ var EXITED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u
|
|
|
18150
18474
|
var STORM_DEAD_LIMIT = Number(process.env.SWARM_STORM_DEAD_LIMIT) || 3;
|
|
18151
18475
|
var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS) || 3 * 60 * 1e3;
|
|
18152
18476
|
var RESOLVE_LIMIT = Number(process.env.SWARM_RESOLVE_LIMIT) || 3;
|
|
18477
|
+
var BASE_MERGED_MARK = "\u0431\u0430\u0437\u0430 \u0432\u043B\u0438\u0442\u0430 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438";
|
|
18478
|
+
var BASE_NOOP_MARK = "\u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E";
|
|
18153
18479
|
var REVIEW_SPAWN_LIMIT = Number(process.env.SWARM_REVIEW_SPAWN_LIMIT) || 3;
|
|
18154
18480
|
var REVIEW_CYCLE_LIMIT = Number(process.env.SWARM_REVIEW_CYCLE_LIMIT) || 3;
|
|
18155
18481
|
var CI_WAKE_LIMIT = Number(process.env.SWARM_CI_WAKE_LIMIT) || 3;
|
|
@@ -18231,14 +18557,46 @@ function lastConflictAttemptMs(db, runId) {
|
|
|
18231
18557
|
if (!evs.length) return null;
|
|
18232
18558
|
return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
|
|
18233
18559
|
}
|
|
18560
|
+
function lastBaseMergeWasNoop(db, runId) {
|
|
18561
|
+
const outcomes = db.events(runId).filter((e) => e.kind === "conflict" && ((e.text ?? "").includes(BASE_NOOP_MARK) || (e.text ?? "").includes(BASE_MERGED_MARK)));
|
|
18562
|
+
return (outcomes[outcomes.length - 1]?.text ?? "").includes(BASE_NOOP_MARK);
|
|
18563
|
+
}
|
|
18564
|
+
function resolveEscalated(db, runId) {
|
|
18565
|
+
return db.events(runId).some((e) => e.kind === "conflict" && (e.text ?? "").includes(RESOLVE_ESCALATION_MARK));
|
|
18566
|
+
}
|
|
18234
18567
|
async function needsBaseRefreshNow(deps, run, stormActive, now) {
|
|
18235
18568
|
if (!deps.mergeQueue || !deps.resolveConflict || stormActive) return false;
|
|
18236
18569
|
if (deps.db.activeResolverFor(run.id)) return false;
|
|
18237
|
-
if (deps.db
|
|
18570
|
+
if (resolveEscalated(deps.db, run.id)) return false;
|
|
18238
18571
|
const last = lastConflictAttemptMs(deps.db, run.id);
|
|
18239
18572
|
if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) return false;
|
|
18240
18573
|
return needsBaseRefresh(await deps.mergeQueue(run).catch(() => null));
|
|
18241
18574
|
}
|
|
18575
|
+
async function dropMergeIntent(deps, run, reason) {
|
|
18576
|
+
if (!deps.clearMergeIntent) return false;
|
|
18577
|
+
if (!await deps.clearMergeIntent(run).catch(() => false)) return false;
|
|
18578
|
+
deps.db.updateRun(run.id, { merge_requested_at: null });
|
|
18579
|
+
deps.db.addEvent(run.id, "step", `\u043C\u0435\u0440\u0436 \u0441\u043D\u044F\u0442: ${reason}`);
|
|
18580
|
+
narrate(deps, `\u0421\u043D\u044F\u043B \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u0438\u0435 \u0441\u043C\u0435\u0440\u0436\u0438\u0442\u044C PR ${droneLabel(run)} \u2014 ${reason}.`);
|
|
18581
|
+
return true;
|
|
18582
|
+
}
|
|
18583
|
+
function mergeIntentDropReason(deps, run, conflicting) {
|
|
18584
|
+
if (needsFixIteration(run)) return "\u043F\u043E PR \u043F\u0440\u0438\u0448\u0451\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB \u2014 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430";
|
|
18585
|
+
if (conflicting && resolveEscalated(deps.db, run.id)) return "\u0440\u043E\u0439 \u043D\u0435 \u0441\u043C\u043E\u0433 \u0441\u0432\u0435\u0441\u0442\u0438 \u0432\u0435\u0442\u043A\u0443 \u0441 \u0431\u0430\u0437\u043E\u0439 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
|
|
18586
|
+
return null;
|
|
18587
|
+
}
|
|
18588
|
+
var MERGE_INTENT_FAIL_MARK = "\u043C\u0435\u0440\u0436 \u043F\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u0438\u044E \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B";
|
|
18589
|
+
var MERGE_INTENT_FAIL_NOTE_MS = 60 * 6e4;
|
|
18590
|
+
function noteMergeIntentFailure(deps, run, error, now) {
|
|
18591
|
+
const text = `${MERGE_INTENT_FAIL_MARK}: ${error} \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C`;
|
|
18592
|
+
const prev = deps.db.events(run.id).filter((e) => (e.text ?? "").startsWith(MERGE_INTENT_FAIL_MARK));
|
|
18593
|
+
const last = prev[prev.length - 1];
|
|
18594
|
+
if (last && last.text === text) {
|
|
18595
|
+
const at = Date.parse(last.at.replace(" ", "T") + "Z");
|
|
18596
|
+
if (!Number.isNaN(at) && now - at < MERGE_INTENT_FAIL_NOTE_MS) return;
|
|
18597
|
+
}
|
|
18598
|
+
deps.db.addEvent(run.id, "step", text);
|
|
18599
|
+
}
|
|
18242
18600
|
function lastCiCheckMs(db, runId) {
|
|
18243
18601
|
const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked");
|
|
18244
18602
|
if (!evs.length) return null;
|
|
@@ -19669,6 +20027,13 @@ async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback)
|
|
|
19669
20027
|
if (!hasZoneModel(model)) return { kind: "unknown" };
|
|
19670
20028
|
const holders = db.listRuns().filter((r) => holdsFiles(r));
|
|
19671
20029
|
activeFiles.prune(holders.map((r) => r.id));
|
|
20030
|
+
const fileExists = (p) => {
|
|
20031
|
+
try {
|
|
20032
|
+
return statSync13(join34(repo, p), { throwIfNoEntry: false })?.isFile() ?? false;
|
|
20033
|
+
} catch {
|
|
20034
|
+
return false;
|
|
20035
|
+
}
|
|
20036
|
+
};
|
|
19672
20037
|
const active = [];
|
|
19673
20038
|
for (const r of holders) {
|
|
19674
20039
|
if (r.id === run.id) continue;
|
|
@@ -19677,10 +20042,10 @@ async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback)
|
|
|
19677
20042
|
const files = snapshot.length ? snapshot : await activeFiles.filesOf(r);
|
|
19678
20043
|
const prUrl = r.status === "review" ? r.pr_url : null;
|
|
19679
20044
|
active.push(
|
|
19680
|
-
files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: predictForRun(model, r)
|
|
20045
|
+
files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: heldByPrediction(predictForRun(model, r, fileExists)), actual: false, prUrl }
|
|
19681
20046
|
);
|
|
19682
20047
|
}
|
|
19683
|
-
return decideConflict({ model, candidate: run, active });
|
|
20048
|
+
return decideConflict({ model, candidate: run, active, fileExists });
|
|
19684
20049
|
}
|
|
19685
20050
|
function activeDronesOnRepo(deps, repo, exceptRunId) {
|
|
19686
20051
|
return deps.db.listRuns().filter(
|
|
@@ -20868,6 +21233,7 @@ async function tickOnce(deps) {
|
|
|
20868
21233
|
continue;
|
|
20869
21234
|
}
|
|
20870
21235
|
if (prLiveness(st) === "closed") {
|
|
21236
|
+
if (hasMergeIntent(run)) await dropMergeIntent(deps, run, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430");
|
|
20871
21237
|
await finalizeClosedPrReview(deps, run, "PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442");
|
|
20872
21238
|
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 \u2014 ${run.task_slug} \u0441\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0441 \u0440\u0435\u0432\u044C\u044E, \u0441\u043B\u043E\u0442 \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D.`);
|
|
20873
21239
|
continue;
|
|
@@ -20897,18 +21263,62 @@ async function tickOnce(deps) {
|
|
|
20897
21263
|
continue;
|
|
20898
21264
|
}
|
|
20899
21265
|
await retireOutdatedReviewers(deps, run);
|
|
20900
|
-
|
|
21266
|
+
let intentBaseStale = false;
|
|
21267
|
+
if (hasMergeIntent(run)) {
|
|
21268
|
+
const drop = mergeIntentDropReason(deps, run, st.conflicting === true);
|
|
21269
|
+
if (drop) {
|
|
21270
|
+
if (await dropMergeIntent(deps, run, drop)) run = deps.db.getRun(run.id) ?? run;
|
|
21271
|
+
} else if (deps.mergeIntent && !stormActive) {
|
|
21272
|
+
const decision = await deps.mergeIntent(run).catch(() => null);
|
|
21273
|
+
if (decision?.action === "clear") {
|
|
21274
|
+
if (await dropMergeIntent(deps, run, decision.reason)) run = deps.db.getRun(run.id) ?? run;
|
|
21275
|
+
} else if (decision?.action === "refresh_base") {
|
|
21276
|
+
if (resolveEscalated(deps.db, run.id)) {
|
|
21277
|
+
if (await dropMergeIntent(deps, run, "\u0440\u043E\u0439 \u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u0432\u0435\u0442\u043A\u0443 \u043A \u0441\u0432\u0435\u0436\u0435\u0439 \u0431\u0430\u0437\u0435 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A")) run = deps.db.getRun(run.id) ?? run;
|
|
21278
|
+
} else {
|
|
21279
|
+
intentBaseStale = true;
|
|
21280
|
+
}
|
|
21281
|
+
} else if (decision?.action === "merge" && deps.mergePr && !st.conflicting) {
|
|
21282
|
+
const res = await deps.mergePr(run, decision.headSha).catch((e) => ({ ok: false, error: describeError(e) }));
|
|
21283
|
+
if (res.ok) {
|
|
21284
|
+
deps.db.addEvent(run.id, "step", `\u0440\u043E\u0439 \u0432\u043B\u0438\u043B PR \u043F\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430: ${decision.reason} (TASWM-577)`);
|
|
21285
|
+
narrate(deps, `\u0412\u043B\u0438\u043B PR ${droneLabel(run)} \u043F\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u0435\u0440\u0436\u0430: ${decision.reason}.`);
|
|
21286
|
+
continue;
|
|
21287
|
+
}
|
|
21288
|
+
if ("headMoved" in res && res.headMoved) {
|
|
21289
|
+
deps.db.addEvent(run.id, "step", `\u043C\u0435\u0440\u0436 \u043F\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u0438\u044E \u043E\u0442\u043B\u043E\u0436\u0435\u043D: HEAD PR \u0441\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 ${(decision.headSha ?? "").slice(0, 7)} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044E \u043D\u043E\u0432\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C (TASWM-580)`);
|
|
21290
|
+
} else {
|
|
21291
|
+
noteMergeIntentFailure(deps, run, res.error, now);
|
|
21292
|
+
}
|
|
21293
|
+
}
|
|
21294
|
+
}
|
|
21295
|
+
}
|
|
21296
|
+
const baseStale = !st.conflicting && (intentBaseStale || await needsBaseRefreshNow(deps, run, stormActive, now));
|
|
20901
21297
|
if ((st.conflicting || baseStale) && deps.resolveConflict && !stormActive) {
|
|
20902
|
-
if (deps.resourcesOk && !deps.resourcesOk()) continue;
|
|
20903
|
-
if (countActiveDrones(deps.db) >= droneCap) continue;
|
|
20904
|
-
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
20905
21298
|
if (deps.db.activeResolverFor(run.id)) continue;
|
|
20906
21299
|
if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) {
|
|
20907
|
-
if (!deps.db
|
|
20908
|
-
deps.db.addEvent(
|
|
21300
|
+
if (!resolveEscalated(deps.db, run.id)) {
|
|
21301
|
+
deps.db.addEvent(
|
|
21302
|
+
run.id,
|
|
21303
|
+
"conflict",
|
|
21304
|
+
st.conflicting ? `${RESOLVE_ESCALATION_MARK}: \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0441\u044F \u0437\u0430 ${RESOLVE_LIMIT} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A` : `${RESOLVE_ESCALATION_MARK}: \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434, \u0430 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440\u043E\u0432 (${RESOLVE_LIMIT}) \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0432\u043B\u0438\u0442\u044C \u0431\u0430\u0437\u0443 \u0434\u043E\u043B\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
|
|
21305
|
+
);
|
|
20909
21306
|
}
|
|
20910
21307
|
continue;
|
|
20911
21308
|
}
|
|
21309
|
+
if (baseStale && lastBaseMergeWasNoop(deps.db, run.id)) {
|
|
21310
|
+
if (!resolveEscalated(deps.db, run.id)) {
|
|
21311
|
+
deps.db.addEvent(
|
|
21312
|
+
run.id,
|
|
21313
|
+
"conflict",
|
|
21314
|
+
`${RESOLVE_ESCALATION_MARK}: \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043C\u0435\u0440\u0436\u0430 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u0431\u0430\u0437\u0443 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0435\u0439, \u0430 \u0432\u043B\u0438\u0432\u0430\u0442\u044C \u0432 \u0432\u0435\u0442\u043A\u0443 \u043D\u0435\u0447\u0435\u0433\u043E \u2014 \u0441\u0430\u043C\u0430 \u0441\u043E\u0431\u043E\u0439 \u0442\u0430\u043A\u0430\u044F \u0431\u0430\u0437\u0430 \u043D\u0435 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
|
|
21315
|
+
);
|
|
21316
|
+
}
|
|
21317
|
+
continue;
|
|
21318
|
+
}
|
|
21319
|
+
if (deps.resourcesOk && !deps.resourcesOk()) continue;
|
|
21320
|
+
if (countActiveDrones(deps.db) >= droneCap) continue;
|
|
21321
|
+
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
20912
21322
|
const last = lastConflictAttemptMs(deps.db, run.id);
|
|
20913
21323
|
if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) continue;
|
|
20914
21324
|
deps.db.addEvent(
|
|
@@ -21514,13 +21924,14 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
21514
21924
|
if (m.changed) await deps.git.pushHeadTo(wt, branch);
|
|
21515
21925
|
await deps.git.removeWorktree(repo, wt).catch(() => {
|
|
21516
21926
|
});
|
|
21517
|
-
|
|
21927
|
+
const cleanWhy = m.changed ? `\u0447\u0438\u0441\u0442\u044B\u0439 merge ${mainRef} \u2014 \u0434\u0440\u043E\u043D \u043D\u0435 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F` : `${mainRef} \u0443\u0436\u0435 \u0432\u043B\u0438\u0442 \u2014 \u043D\u0435\u0447\u0435\u0433\u043E \u0434\u0435\u043B\u0430\u0442\u044C`;
|
|
21928
|
+
deps.db.updateRun(resolver.id, { status: "done", stop_reason: `${RESOLVE_CLEAN_MARK}: ${cleanWhy}` });
|
|
21929
|
+
deps.db.addEvent(resolver.id, "done", cleanWhy);
|
|
21518
21930
|
deps.db.addEvent(
|
|
21519
|
-
|
|
21520
|
-
"
|
|
21521
|
-
m.changed ?
|
|
21931
|
+
reviewRun.id,
|
|
21932
|
+
"conflict",
|
|
21933
|
+
m.changed ? `${BASE_MERGED_MARK} (${mainRef}, \u0431\u0435\u0437 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432) \u2014 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D` : `${mainRef} \u0443\u0436\u0435 \u0432 \u0432\u0435\u0442\u043A\u0435 \u2014 ${BASE_NOOP_MARK}, \u0431\u0430\u0437\u0430 \u043E\u0442 \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0441\u044F`
|
|
21522
21934
|
);
|
|
21523
|
-
deps.db.addEvent(reviewRun.id, "conflict", `\u0431\u0430\u0437\u0430 \u0432\u043B\u0438\u0442\u0430 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 (${mainRef}, \u0431\u0435\u0437 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432) \u2014 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D`);
|
|
21524
21935
|
} else {
|
|
21525
21936
|
const prompt = buildResolverPrompt(reviewRun, resolver, m.conflicts, branch, mainRef);
|
|
21526
21937
|
const pid = deps.launch({ worktree: wt, run: resolver, prompt });
|
|
@@ -22400,7 +22811,10 @@ async function runDaemon(opts) {
|
|
|
22400
22811
|
const ci = await prCiStatus(url);
|
|
22401
22812
|
if (ci.bucket === "denied" || ci.bucket === "unavailable") return null;
|
|
22402
22813
|
return { bucket: ci.bucket };
|
|
22403
|
-
}
|
|
22814
|
+
},
|
|
22815
|
+
// TASWM-577: прогоны GitHub Actions на HEAD — только вливанию по намерению: рой
|
|
22816
|
+
// вливает сам и обязан дождаться, пока CI на этом коммите добежит целиком.
|
|
22817
|
+
actionsRuns: (url, sha) => prActionsRuns(url, sha)
|
|
22404
22818
|
} : {}
|
|
22405
22819
|
});
|
|
22406
22820
|
const deps = {
|
|
@@ -22410,6 +22824,24 @@ async function runDaemon(opts) {
|
|
|
22410
22824
|
worktreesDir,
|
|
22411
22825
|
// TASWM-472: реконсайлу от очереди нужен ровно исход `base_stale` (см. needsBaseRefreshNow).
|
|
22412
22826
|
...SWARM_MERGE_QUEUE ? { mergeQueue: mergeParts.queue } : {},
|
|
22827
|
+
// TASWM-577: вливание ПО НАМЕРЕНИЮ — та же ось, тот же вход (mergeParts), но очередь из
|
|
22828
|
+
// одних отмеченных PR. Выключается тем же ключом, что и очередь: SWARM_MERGE_QUEUE=0 —
|
|
22829
|
+
// рой не вливает ничего, мержит человек кнопкой, как до этой задачи.
|
|
22830
|
+
...SWARM_MERGE_QUEUE ? { mergeIntent: mergeParts.intent } : {},
|
|
22831
|
+
// Сам мерж и снятие намерения идут в ПРОД-ручки (`POST /swarm/merge` — GitHub App
|
|
22832
|
+
// воркспейса, squash; `DELETE /swarm/merge-request/:runId`), а адресуются они cuid'ом
|
|
22833
|
+
// прогона в проде. Он есть только у клиентского бэкенда: в sqlite-режиме прогонов в
|
|
22834
|
+
// проде нет вовсе, и рой там по-прежнему не мержит — мержит человек.
|
|
22835
|
+
...db instanceof SwarmClient ? {
|
|
22836
|
+
mergePr: async (run, sha) => {
|
|
22837
|
+
const prodId = db.prodRunId(run.id);
|
|
22838
|
+
return prodId ? prodMergePr(prodId, sha) : { ok: false, error: "\u043F\u0440\u043E\u0433\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B \u0434\u043E \u043F\u0440\u043E\u0434\u0430 \u2014 \u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C" };
|
|
22839
|
+
},
|
|
22840
|
+
clearMergeIntent: async (run) => {
|
|
22841
|
+
const prodId = db.prodRunId(run.id);
|
|
22842
|
+
return prodId ? prodClearMergeIntent(prodId) : false;
|
|
22843
|
+
}
|
|
22844
|
+
} : {},
|
|
22413
22845
|
narrate: narrator.narrate,
|
|
22414
22846
|
control,
|
|
22415
22847
|
spawn: async (run) => {
|