@taskless-app/swarm 0.6.2 → 0.6.4
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/README.md +18 -0
- package/dist/cli.js +165 -34
- package/dist/overmind.js +460 -127
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -69,7 +69,13 @@ var init_types = __esm({
|
|
|
69
69
|
// делать нечего». Находки вроде «в дизайн-системе нет иконки микрофона» раньше либо
|
|
70
70
|
// превращались в такой дорогой вопрос, либо тонули в отчёте о сдаче (дрон LAMPYRIS сдал
|
|
71
71
|
// заглушку «Мик» текстом на кнопке — уехало в прод). note уезжает оператору, дрон работает дальше.
|
|
72
|
-
"note"
|
|
72
|
+
"note",
|
|
73
|
+
// Зависимость прогона снята НЕуспешным финалом блокера (TASWM-361): тот остановлен/упал/
|
|
74
|
+
// переигран, успешного финала не будет — ждать больше нечего. Пишется в ленту ЖДАВШЕГО
|
|
75
|
+
// прогона перед самым спавном: «поехал молча» означало бы, что причина запуска исчезла из
|
|
76
|
+
// истории, и следующий разбор «почему он стартовал поверх незакрытого блокера» упёрся бы в
|
|
77
|
+
// пустоту. Успешное снятие (блокер смержен) события не порождает — это штатный ход.
|
|
78
|
+
"dep_cleared"
|
|
73
79
|
];
|
|
74
80
|
MILESTONE_KINDS = [
|
|
75
81
|
"spawned",
|
|
@@ -90,8 +96,10 @@ var init_types = __esm({
|
|
|
90
96
|
"storm_dead",
|
|
91
97
|
"anomaly",
|
|
92
98
|
// надзор overmind (TASWM-194) — та же нотификация, что улетела в инбокс, вехой в Истории
|
|
93
|
-
"note"
|
|
99
|
+
"note",
|
|
94
100
|
// заметка дрона «замечено по пути» (TASWM-279) — веха, иначе находка тонет в ленте step'ов
|
|
101
|
+
"dep_cleared"
|
|
102
|
+
// зависимость снята неуспешным финалом блокера (TASWM-361) — решение роя, веха как conflict
|
|
95
103
|
];
|
|
96
104
|
MILESTONE_KIND_SET = new Set(MILESTONE_KINDS);
|
|
97
105
|
}
|
|
@@ -356,6 +364,43 @@ var init_status = __esm({
|
|
|
356
364
|
}
|
|
357
365
|
});
|
|
358
366
|
|
|
367
|
+
// ../swarm-core/src/deps.ts
|
|
368
|
+
function judgeDep(runs) {
|
|
369
|
+
const real = runs.filter((r) => !r.service);
|
|
370
|
+
if (real.length === 0) return { ...PENDING };
|
|
371
|
+
if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
|
|
372
|
+
if (real.some((r) => r.status === "done")) {
|
|
373
|
+
return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
374
|
+
}
|
|
375
|
+
const last = real[real.length - 1];
|
|
376
|
+
return {
|
|
377
|
+
outcome: "exhausted",
|
|
378
|
+
met: true,
|
|
379
|
+
status: last.status,
|
|
380
|
+
name: last.name ?? null,
|
|
381
|
+
stopReason: last.stopReason ?? null
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function depClearedText(slug, v) {
|
|
385
|
+
const who = v.name ? `${slug} (${v.name})` : slug;
|
|
386
|
+
const how = DEP_END_LABEL[v.status ?? ""] ?? `\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D (${v.status ?? "?"})`;
|
|
387
|
+
const why = v.stopReason ? ` \u2014 ${v.stopReason}` : "";
|
|
388
|
+
return `\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u0441\u043D\u044F\u0442\u0430: \u0436\u0434\u0430\u043B ${who}, \u0442\u043E\u0442 ${how}${why}. \u0423\u0441\u043F\u0435\u0448\u043D\u043E\u0433\u043E \u0444\u0438\u043D\u0430\u043B\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u2014 \u0435\u0434\u0443.`;
|
|
389
|
+
}
|
|
390
|
+
var PENDING, DEP_END_LABEL;
|
|
391
|
+
var init_deps = __esm({
|
|
392
|
+
"../swarm-core/src/deps.ts"() {
|
|
393
|
+
"use strict";
|
|
394
|
+
init_status();
|
|
395
|
+
PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
396
|
+
DEP_END_LABEL = {
|
|
397
|
+
stopped: "\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
398
|
+
failed: "\u0443\u043F\u0430\u043B",
|
|
399
|
+
superseded: "\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D"
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
359
404
|
// ../swarm-core/src/time.ts
|
|
360
405
|
function stamp(d = /* @__PURE__ */ new Date()) {
|
|
361
406
|
return d.toISOString().slice(0, 19).replace("T", " ");
|
|
@@ -1230,6 +1275,7 @@ var init_src = __esm({
|
|
|
1230
1275
|
init_timeline();
|
|
1231
1276
|
init_reviewPhase();
|
|
1232
1277
|
init_status();
|
|
1278
|
+
init_deps();
|
|
1233
1279
|
init_time();
|
|
1234
1280
|
init_windows();
|
|
1235
1281
|
init_slug();
|
|
@@ -1325,6 +1371,54 @@ var init_claudeBin = __esm({
|
|
|
1325
1371
|
}
|
|
1326
1372
|
});
|
|
1327
1373
|
|
|
1374
|
+
// src/swarmFiles.ts
|
|
1375
|
+
function sq(s) {
|
|
1376
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1377
|
+
}
|
|
1378
|
+
function guardHookScript(hook, delegate) {
|
|
1379
|
+
const paths = SWARM_WORKTREE_ARTIFACTS.map(sq).join(" ");
|
|
1380
|
+
const lines = [
|
|
1381
|
+
"#!/usr/bin/env sh",
|
|
1382
|
+
`# \u0421\u0442\u0440\u0430\u0445\u043E\u0432\u043A\u0430 \u0440\u043E\u044F (TASWM-372): \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u0440\u043E\u044F \u043D\u0435 \u0443\u0435\u0437\u0436\u0430\u044E\u0442 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.`,
|
|
1383
|
+
"# \u0425\u0443\u043A \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u041F\u0415\u0420-WORKTREE (git config --worktree core.hooksPath) \u043F\u0440\u0438 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0435",
|
|
1384
|
+
"# worktree \u0434\u0440\u043E\u043D\u0430; \u0445\u0443\u043A\u0438 \u0441\u0430\u043C\u043E\u0433\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442\u044B \u2014 \u043F\u0440\u0435\u0436\u043D\u0438\u0439 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0438\u0436\u0435.",
|
|
1385
|
+
`swarm_found=$(git ls-files -- ${paths} 2>/dev/null)`,
|
|
1386
|
+
`if [ -n "$swarm_found" ]; then`,
|
|
1387
|
+
` echo "\u2716 \u0421\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u0420\u041E\u042F \u0432 \u0438\u043D\u0434\u0435\u043A\u0441\u0435 git \u2014 ${ACTION[hook]} (TASWM-372):" >&2`,
|
|
1388
|
+
` printf '%s\\n' "$swarm_found" | sed 's/^/ /' >&2`,
|
|
1389
|
+
` echo " \u042D\u0442\u043E \u0444\u0430\u0439\u043B\u044B \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0440\u043E\u044F, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0432 ${SWARM_MCP_CONFIG_FILE} \u043B\u0435\u0436\u0438\u0442 \u0442\u043E\u043A\u0435\u043D," >&2`,
|
|
1390
|
+
` echo " \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u043E\u043A\u0430\u0437\u0430\u0442\u044C\u0441\u044F \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 (\u0438\u043D\u0446\u0438\u0434\u0435\u043D\u0442 11.08)." >&2`,
|
|
1391
|
+
` echo " \u0423\u0431\u0435\u0440\u0438 \u0438\u0445 \u0438\u0437 \u0438\u043D\u0434\u0435\u043A\u0441\u0430 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438:" >&2`,
|
|
1392
|
+
` echo " git rm --cached -r --ignore-unmatch ${SWARM_WORKTREE_ARTIFACTS.join(" ")}" >&2`,
|
|
1393
|
+
` echo " \u0424\u0430\u0439\u043B\u044B \u043D\u0430 \u0434\u0438\u0441\u043A\u0435 \u043E\u0441\u0442\u0430\u043D\u0443\u0442\u0441\u044F \u2014 \u043E\u043D\u0438 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u043E\u0433\u043E\u043D\u0443." >&2`,
|
|
1394
|
+
" exit 1",
|
|
1395
|
+
"fi"
|
|
1396
|
+
];
|
|
1397
|
+
if (delegate) {
|
|
1398
|
+
lines.push(
|
|
1399
|
+
"# \u041F\u0440\u0435\u0436\u043D\u0438\u0439 \u0445\u0443\u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (husky \u0438 \u0442.\u043F.) \u2014 \u043E\u043D \u043D\u0435 \u0434\u043E\u043B\u0436\u0435\u043D \u043F\u0440\u043E\u043F\u0430\u0441\u0442\u044C \u0438\u0437-\u0437\u0430 \u043D\u0430\u0448\u0435\u0439 \u0441\u0442\u0440\u0430\u0445\u043E\u0432\u043A\u0438.",
|
|
1400
|
+
`if [ -x ${sq(delegate)} ]; then exec ${sq(delegate)} "$@"; fi`
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
lines.push("exit 0", "");
|
|
1404
|
+
return lines.join("\n");
|
|
1405
|
+
}
|
|
1406
|
+
var SWARM_MCP_CONFIG_FILE, SWARM_TASK_DIR, SWARM_WORKTREE_ARTIFACTS, SWARM_EXCLUDE_PATTERNS, GUARD_HOOKS, ACTION;
|
|
1407
|
+
var init_swarmFiles = __esm({
|
|
1408
|
+
"src/swarmFiles.ts"() {
|
|
1409
|
+
"use strict";
|
|
1410
|
+
SWARM_MCP_CONFIG_FILE = ".swarm-mcp.json";
|
|
1411
|
+
SWARM_TASK_DIR = ".swarm-task";
|
|
1412
|
+
SWARM_WORKTREE_ARTIFACTS = [SWARM_MCP_CONFIG_FILE, SWARM_TASK_DIR];
|
|
1413
|
+
SWARM_EXCLUDE_PATTERNS = [`/${SWARM_MCP_CONFIG_FILE}`, `/${SWARM_TASK_DIR}/`];
|
|
1414
|
+
GUARD_HOOKS = ["pre-commit", "pre-push"];
|
|
1415
|
+
ACTION = {
|
|
1416
|
+
"pre-commit": "\u043A\u043E\u043C\u043C\u0438\u0442 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D",
|
|
1417
|
+
"pre-push": "\u043F\u0443\u0448 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D"
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1328
1422
|
// src/pricing.ts
|
|
1329
1423
|
function costFromUsage(usage, model, table = MODEL_PRICING) {
|
|
1330
1424
|
const pricing = model ? table[model] : void 0;
|
|
@@ -1463,9 +1557,10 @@ var init_claudeCode = __esm({
|
|
|
1463
1557
|
"src/executors/claudeCode.ts"() {
|
|
1464
1558
|
"use strict";
|
|
1465
1559
|
init_claudeBin();
|
|
1560
|
+
init_swarmFiles();
|
|
1466
1561
|
init_claudeResult();
|
|
1467
1562
|
ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
|
|
1468
|
-
MCP_CONFIG_FILE =
|
|
1563
|
+
MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
|
|
1469
1564
|
claudeCodeExecutor = {
|
|
1470
1565
|
id: "claude-code",
|
|
1471
1566
|
title: "Claude Code (claude)",
|
|
@@ -2647,8 +2742,8 @@ var init_procCleanup = __esm({
|
|
|
2647
2742
|
|
|
2648
2743
|
// src/git.ts
|
|
2649
2744
|
import { existsSync as existsSync9, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
|
|
2650
|
-
import { appendFile, mkdir, readFile, rm } from "fs/promises";
|
|
2651
|
-
import { isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
2745
|
+
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
2746
|
+
import { isAbsolute as isAbsolute2, join as join15, sep as sep2 } from "path";
|
|
2652
2747
|
import { $ as $2 } from "execa";
|
|
2653
2748
|
function isUnrecoverableRefError(e) {
|
|
2654
2749
|
if (e instanceof MissingRefError) return true;
|
|
@@ -2720,6 +2815,11 @@ async function worktreeForBranch(repo, branch) {
|
|
|
2720
2815
|
return null;
|
|
2721
2816
|
}
|
|
2722
2817
|
async function addWorktree(repo, path, branch, opts = {}) {
|
|
2818
|
+
const res = await checkoutWorktree(repo, path, branch, opts);
|
|
2819
|
+
await ensureWorktreeHygiene(path);
|
|
2820
|
+
return res;
|
|
2821
|
+
}
|
|
2822
|
+
async function checkoutWorktree(repo, path, branch, opts) {
|
|
2723
2823
|
await ensureLongPaths(repo);
|
|
2724
2824
|
const localSha = await revParse(repo, `refs/heads/${branch}`);
|
|
2725
2825
|
const originSha = await revParse(repo, `refs/remotes/origin/${branch}`);
|
|
@@ -2763,10 +2863,8 @@ async function setWorktreeIdentity(worktree, name) {
|
|
|
2763
2863
|
}
|
|
2764
2864
|
async function ensureGitExcluded(worktree, pattern) {
|
|
2765
2865
|
try {
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
if (out.exitCode !== 0 || !raw) return false;
|
|
2769
|
-
const commonDir = isAbsolute2(raw) ? raw : join15(worktree, raw);
|
|
2866
|
+
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
2867
|
+
if (!commonDir) return false;
|
|
2770
2868
|
const infoDir = join15(commonDir, "info");
|
|
2771
2869
|
const excludeFile = join15(infoDir, "exclude");
|
|
2772
2870
|
let current = "";
|
|
@@ -2784,6 +2882,46 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
2784
2882
|
return false;
|
|
2785
2883
|
}
|
|
2786
2884
|
}
|
|
2885
|
+
async function gitPath(worktree, flag) {
|
|
2886
|
+
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
2887
|
+
const raw = out.stdout.trim();
|
|
2888
|
+
if (out.exitCode !== 0 || !raw) return null;
|
|
2889
|
+
return isAbsolute2(raw) ? raw : join15(worktree, raw);
|
|
2890
|
+
}
|
|
2891
|
+
async function ensureWorktreeHygiene(worktree) {
|
|
2892
|
+
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
2893
|
+
await installGuardHooks(worktree);
|
|
2894
|
+
}
|
|
2895
|
+
async function installGuardHooks(worktree) {
|
|
2896
|
+
try {
|
|
2897
|
+
const gitDir = await gitPath(worktree, "--git-dir");
|
|
2898
|
+
if (!gitDir) return false;
|
|
2899
|
+
const hooksDir = join15(gitDir, "swarm-hooks");
|
|
2900
|
+
const prev = await previousHooksPath(worktree);
|
|
2901
|
+
await mkdir(hooksDir, { recursive: true });
|
|
2902
|
+
for (const hook of GUARD_HOOKS) {
|
|
2903
|
+
await writeFile(join15(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
2904
|
+
await chmod(join15(hooksDir, hook), 493);
|
|
2905
|
+
}
|
|
2906
|
+
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
2907
|
+
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
2908
|
+
return true;
|
|
2909
|
+
} catch {
|
|
2910
|
+
return false;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
function posix(p) {
|
|
2914
|
+
return (sep2 === "\\" ? p.replace(/\\/g, "/") : p).replace(/\/+$/, "");
|
|
2915
|
+
}
|
|
2916
|
+
async function previousHooksPath(worktree) {
|
|
2917
|
+
for (const scope of ["--local", "--global", "--system"]) {
|
|
2918
|
+
const out = await $2({ reject: false })`git -C ${worktree} config ${scope} --get core.hooksPath`;
|
|
2919
|
+
const value = out.stdout.trim();
|
|
2920
|
+
if (out.exitCode === 0 && value) return value;
|
|
2921
|
+
}
|
|
2922
|
+
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
2923
|
+
return commonDir ? join15(commonDir, "hooks") : null;
|
|
2924
|
+
}
|
|
2787
2925
|
async function removeWorktree(repo, path) {
|
|
2788
2926
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
2789
2927
|
await cleanupWorktreeProcesses(path);
|
|
@@ -2848,6 +2986,7 @@ async function prepareDetachedWorktree(repo, branch, worktree) {
|
|
|
2848
2986
|
const ref = `origin/${branch}`;
|
|
2849
2987
|
if (!await revParse(repo, `${ref}^{commit}`)) throw new MissingRefError(ref, repo);
|
|
2850
2988
|
await $2`git -C ${repo} worktree add --detach --force ${worktree} ${ref}`;
|
|
2989
|
+
await ensureWorktreeHygiene(worktree);
|
|
2851
2990
|
}
|
|
2852
2991
|
async function mergeRef(worktree, ref) {
|
|
2853
2992
|
const before = (await $2`git -C ${worktree} rev-parse HEAD`).stdout.trim();
|
|
@@ -2885,6 +3024,7 @@ var init_git = __esm({
|
|
|
2885
3024
|
"src/git.ts"() {
|
|
2886
3025
|
"use strict";
|
|
2887
3026
|
init_procCleanup();
|
|
3027
|
+
init_swarmFiles();
|
|
2888
3028
|
WorktreeBusyError = class extends Error {
|
|
2889
3029
|
constructor(branch, worktreePath) {
|
|
2890
3030
|
super(`worktree \u0437\u0430\u043D\u044F\u0442: ${worktreePath} (\u0432\u0435\u0442\u043A\u0430 ${branch}) \u2014 \u043F\u0440\u0438\u0431\u0435\u0440\u0438 worktree \u0438\u043B\u0438 \u043E\u0442\u043C\u0435\u043D\u0438 \u043F\u0440\u043E\u0433\u043E\u043D (swarm cancel ${branch})`);
|
|
@@ -3266,7 +3406,7 @@ var init_droneChannel = __esm({
|
|
|
3266
3406
|
});
|
|
3267
3407
|
|
|
3268
3408
|
// src/attachments.ts
|
|
3269
|
-
import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
|
|
3409
|
+
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3270
3410
|
import { join as join18 } from "path";
|
|
3271
3411
|
function attachmentsFromDoc(doc, origin) {
|
|
3272
3412
|
const out = [];
|
|
@@ -3354,7 +3494,7 @@ async function materializeAttachments(opts) {
|
|
|
3354
3494
|
const io = opts.io ?? {
|
|
3355
3495
|
removeDir: async (dir2) => rm2(dir2, { recursive: true, force: true }),
|
|
3356
3496
|
mkdir: async (dir2) => void await mkdir2(dir2, { recursive: true }),
|
|
3357
|
-
writeFile: async (path, bytes) =>
|
|
3497
|
+
writeFile: async (path, bytes) => writeFile2(path, bytes)
|
|
3358
3498
|
};
|
|
3359
3499
|
const result = { files: [], skipped: [] };
|
|
3360
3500
|
if (opts.refs.length === 0) return result;
|
|
@@ -3486,13 +3626,13 @@ function mb(bytes) {
|
|
|
3486
3626
|
function errText(e) {
|
|
3487
3627
|
return e instanceof Error ? e.message : String(e);
|
|
3488
3628
|
}
|
|
3489
|
-
var ATTACHMENTS_DIR,
|
|
3629
|
+
var ATTACHMENTS_DIR, ATTACH_MAX_FILES, ATTACH_MAX_TOTAL_BYTES, ATTACH_MAX_FILE_BYTES, IMAGE_EXT, DOC_EXT, TEXT_EXT, KNOWN_EXT, TRANSLIT, EXT_BY_MIME;
|
|
3490
3630
|
var init_attachments = __esm({
|
|
3491
3631
|
"src/attachments.ts"() {
|
|
3492
3632
|
"use strict";
|
|
3493
3633
|
init_git();
|
|
3494
|
-
|
|
3495
|
-
|
|
3634
|
+
init_swarmFiles();
|
|
3635
|
+
ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
|
|
3496
3636
|
ATTACH_MAX_FILES = 12;
|
|
3497
3637
|
ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
|
|
3498
3638
|
ATTACH_MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
@@ -4774,18 +4914,40 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4774
4914
|
return rows.map((r) => r.area);
|
|
4775
4915
|
}
|
|
4776
4916
|
/**
|
|
4777
|
-
* Из depends_on прогона — слаги зависимостей, ещё НЕ
|
|
4778
|
-
*
|
|
4779
|
-
*
|
|
4780
|
-
*
|
|
4781
|
-
*
|
|
4782
|
-
*
|
|
4917
|
+
* Из depends_on прогона — слаги зависимостей, ещё НЕ исчерпанных. Пусто → все выполнены,
|
|
4918
|
+
* дрона можно спавнить. Явная зависимость (spawn --after) отдельна от area-lock: area
|
|
4919
|
+
* освобождается на review (до мержа), а тут ждём финала блокера, чтобы следующий дрон
|
|
4920
|
+
* стартовал от его кода.
|
|
4921
|
+
*
|
|
4922
|
+
* TASWM-361: критерий — «живых прогонов по слагу не осталось», а не «есть done». Раньше
|
|
4923
|
+
* зависимость снимал ТОЛЬКО успех, и остановленный оператором блокер держал ждущих вечно
|
|
4924
|
+
* (VESPA и TYPHON — сутки очереди при пустом рое). Само правило и его обоснование —
|
|
4925
|
+
* `judgeDep` в swarm-core/deps.ts (его же читают зеркало прода и прод-снапшот).
|
|
4926
|
+
*
|
|
4927
|
+
* Несуществующий/зациклённый слаг прогонов не имеет вовсе → `pending` → прогон ждёт, как и
|
|
4928
|
+
* раньше, но очередь не виснет (гейт — пропуск в цикле, не блокировка).
|
|
4783
4929
|
*/
|
|
4784
4930
|
unmetDeps(depends_on) {
|
|
4931
|
+
return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
|
|
4932
|
+
}
|
|
4933
|
+
/**
|
|
4934
|
+
* TASWM-361: вердикт по каждой зависимости — не только «держит ли», но и КАК закрылась
|
|
4935
|
+
* (успехом блокера или его неуспешным финалом). Разница нужна overmind: снятие по
|
|
4936
|
+
* неуспеху оставляет след в ленге ждавшего прогона, штатное — нет.
|
|
4937
|
+
*/
|
|
4938
|
+
depVerdicts(depends_on) {
|
|
4785
4939
|
if (!depends_on || depends_on.length === 0) return [];
|
|
4786
|
-
const
|
|
4787
|
-
const
|
|
4788
|
-
|
|
4940
|
+
const slugs = [...new Set(depends_on)];
|
|
4941
|
+
const rows = this.db.prepare(
|
|
4942
|
+
`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`
|
|
4943
|
+
).all(...slugs);
|
|
4944
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
4945
|
+
for (const r of rows) {
|
|
4946
|
+
const list = bySlug.get(r.task_slug) ?? [];
|
|
4947
|
+
list.push({ status: r.status, name: r.name, stopReason: r.stop_reason });
|
|
4948
|
+
bySlug.set(r.task_slug, list);
|
|
4949
|
+
}
|
|
4950
|
+
return depends_on.map((slug) => ({ slug, ...judgeDep(bySlug.get(slug) ?? []) }));
|
|
4789
4951
|
}
|
|
4790
4952
|
/** Все репо, засветившиеся в прогонах — whitelist для валидации spawn из веба. */
|
|
4791
4953
|
distinctRepos() {
|
|
@@ -5230,11 +5392,11 @@ function savePauseState(home, state, io = {}) {
|
|
|
5230
5392
|
remove(path);
|
|
5231
5393
|
return;
|
|
5232
5394
|
}
|
|
5233
|
-
const
|
|
5395
|
+
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
5234
5396
|
writeFileSync(`${p}.tmp`, data);
|
|
5235
5397
|
renameSync(`${p}.tmp`, p);
|
|
5236
5398
|
});
|
|
5237
|
-
|
|
5399
|
+
writeFile3(path, JSON.stringify(state, null, 2));
|
|
5238
5400
|
}
|
|
5239
5401
|
function makePauseState(source, reason, now = Date.now()) {
|
|
5240
5402
|
return {
|
|
@@ -5474,10 +5636,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
5474
5636
|
// редко — без этого лог был бы простынёй из одной и той же строки, и в ней потонуло бы
|
|
5475
5637
|
// всё остальное. Меняется набор (эпик закрыли / блокер прошли) → печатаем снова.
|
|
5476
5638
|
lastClaimSkipLog = null;
|
|
5477
|
-
// Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг →
|
|
5478
|
-
//
|
|
5479
|
-
//
|
|
5480
|
-
|
|
5639
|
+
// Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг → вердикт по ВСЕМ его прогонам.
|
|
5640
|
+
// `done` монотонен (не откатывается) → положительный ответ не протухает и больше не
|
|
5641
|
+
// спрашивается; прочие вердикты переспрашиваются по TTL — «исчерпана неуспешно»
|
|
5642
|
+
// (TASWM-361) монотонностью НЕ обладает: оператор вправе переигрть остановленный блокер
|
|
5643
|
+
// новым прогоном того же слага, и тогда зависимость снова становится ожиданием.
|
|
5644
|
+
// Живёт в памяти: рестарт демона просто переспросит прод.
|
|
5645
|
+
depsProd = /* @__PURE__ */ new Map();
|
|
5481
5646
|
depsAskedAt = /* @__PURE__ */ new Map();
|
|
5482
5647
|
static DEPS_TTL_MS = 6e4;
|
|
5483
5648
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
@@ -5904,12 +6069,36 @@ var SwarmClient = class _SwarmClient {
|
|
|
5904
6069
|
return [...seen];
|
|
5905
6070
|
}
|
|
5906
6071
|
unmetDeps(depends_on) {
|
|
6072
|
+
return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
|
|
6073
|
+
}
|
|
6074
|
+
/**
|
|
6075
|
+
* TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
|
|
6076
|
+
*
|
|
6077
|
+
* АСИММЕТРИЯ ЗЕРКАЛА И ПРОДА, и почему она здесь намеренная. Зеркало — ЧАСТИЧНАЯ выборка
|
|
6078
|
+
* (только прогоны этой машины, только окно гидрации), и по частичной выборке два вывода
|
|
6079
|
+
* стоят разного:
|
|
6080
|
+
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он не
|
|
6081
|
+
* портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
6082
|
+
* • «живых прогонов слага не осталось» — вывод от ОТСУТСТВИЯ: невидимый зеркалу прогон
|
|
6083
|
+
* на соседней машине как раз и может быть живым. Поэтому «исчерпана неуспешно»
|
|
6084
|
+
* принимаем ТОЛЬКО из ответа прода (refreshDeps) — он видит всех.
|
|
6085
|
+
* Прод недоступен → вердикта нет → прогон честно ждёт следующего окна: лишняя минута
|
|
6086
|
+
* очереди дешевле дрона, поднятого поверх работающего блокера.
|
|
6087
|
+
*/
|
|
6088
|
+
depVerdicts(depends_on) {
|
|
5907
6089
|
if (!depends_on || depends_on.length === 0) return [];
|
|
5908
|
-
const
|
|
6090
|
+
const doneInMirror = /* @__PURE__ */ new Set();
|
|
5909
6091
|
for (const r of this.runs.values()) {
|
|
5910
|
-
if (r.status === "done" && r.resolves_run_id == null)
|
|
6092
|
+
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null) doneInMirror.add(r.task_slug);
|
|
5911
6093
|
}
|
|
5912
|
-
return depends_on.
|
|
6094
|
+
return depends_on.map((slug) => {
|
|
6095
|
+
if (doneInMirror.has(slug)) {
|
|
6096
|
+
return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
6097
|
+
}
|
|
6098
|
+
const fromProd = this.depsProd.get(slug);
|
|
6099
|
+
if (fromProd) return { slug, ...fromProd };
|
|
6100
|
+
return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
6101
|
+
});
|
|
5913
6102
|
}
|
|
5914
6103
|
/**
|
|
5915
6104
|
* TASWM-300: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
|
|
@@ -5920,14 +6109,15 @@ var SwarmClient = class _SwarmClient {
|
|
|
5920
6109
|
* просто позавчера, в зеркало не попадала — и `--after` держал прогон в очереди вечно
|
|
5921
6110
|
* (04.08: снимали руками).
|
|
5922
6111
|
*
|
|
5923
|
-
* Кеш.
|
|
5924
|
-
* положительный ответ помним навсегда и больше не спрашиваем.
|
|
6112
|
+
* Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
|
|
6113
|
+
* положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
|
|
6114
|
+
* «исчерпана неуспешно», TASWM-361 — блокер могут переиграть новым прогоном) переспрашиваем
|
|
5925
6115
|
* не чаще DEPS_TTL_MS: тик идёт раз в ~4с, а ждущих зависимость прогонов может быть
|
|
5926
6116
|
* много — без TTL это был бы поход в прод на каждый слаг каждый тик.
|
|
5927
6117
|
*/
|
|
5928
6118
|
async refreshDeps(slugs, nowMs = Date.now()) {
|
|
5929
6119
|
for (const slug of new Set(slugs)) {
|
|
5930
|
-
if (!slug || this.
|
|
6120
|
+
if (!slug || this.depsProd.get(slug)?.outcome === "done") continue;
|
|
5931
6121
|
const asked = this.depsAskedAt.get(slug);
|
|
5932
6122
|
if (asked !== void 0 && nowMs - asked < _SwarmClient.DEPS_TTL_MS) continue;
|
|
5933
6123
|
this.depsAskedAt.set(slug, nowMs);
|
|
@@ -5940,7 +6130,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
5940
6130
|
}
|
|
5941
6131
|
if (res?.isError) continue;
|
|
5942
6132
|
const runs = res.structuredContent?.runs ?? [];
|
|
5943
|
-
|
|
6133
|
+
const facts = runs.slice().sort((a, b) => String(a.createdAt ?? "").localeCompare(String(b.createdAt ?? ""))).map((r) => ({
|
|
6134
|
+
status: String(r.status ?? ""),
|
|
6135
|
+
// Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
|
|
6136
|
+
// не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
|
|
6137
|
+
// с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
|
|
6138
|
+
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
|
|
6139
|
+
// В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
|
|
6140
|
+
// запасной вариант ради фикстур и старых ответов.
|
|
6141
|
+
name: r.droneName ?? r.name ?? null,
|
|
6142
|
+
stopReason: r.stopReason ?? null
|
|
6143
|
+
}));
|
|
6144
|
+
this.depsProd.set(slug, judgeDep(facts));
|
|
5944
6145
|
}
|
|
5945
6146
|
}
|
|
5946
6147
|
/**
|
|
@@ -6595,6 +6796,26 @@ var SwarmClient = class _SwarmClient {
|
|
|
6595
6796
|
const v = sc.config?.exclusive;
|
|
6596
6797
|
return typeof v === "boolean" ? v : null;
|
|
6597
6798
|
}
|
|
6799
|
+
/**
|
|
6800
|
+
* Live КВОТА «дронов на проект» из Taskless (TASWM-365): тот же swarm_repo_config по
|
|
6801
|
+
* repoFullName — Project.swarmConfig.maxDrones. Сколько прогонов этого проекта могут идти
|
|
6802
|
+
* одновременно; 0 — без ограничения (дефолт проекта). Отдаём ЧИСЛО КАК ЕСТЬ, включая ноль:
|
|
6803
|
+
* превращать его в «без ограничения» — дело ридера (makeDroneCapReader), клиент здесь только
|
|
6804
|
+
* достаёт настройку. null (прод недоступен / repo не смэплен / поля нет / мусор) → ридер
|
|
6805
|
+
* возьмёт «без ограничения», то есть прежнее поведение.
|
|
6806
|
+
*/
|
|
6807
|
+
async repoDroneCap(repoFullName) {
|
|
6808
|
+
let res;
|
|
6809
|
+
try {
|
|
6810
|
+
res = await this.call("swarm_repo_config", { repoFullName });
|
|
6811
|
+
} catch {
|
|
6812
|
+
return null;
|
|
6813
|
+
}
|
|
6814
|
+
if (res?.isError) return null;
|
|
6815
|
+
const sc = res.structuredContent ?? {};
|
|
6816
|
+
const v = sc.config?.maxDrones;
|
|
6817
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
6818
|
+
}
|
|
6598
6819
|
/**
|
|
6599
6820
|
* TASWM-291: МАШИНЫ роя владельца (swarm_list_hosts) — чтобы `swarm spawn --host
|
|
6600
6821
|
* <имя>` резолвил человеческое имя в hostId, а `swarm hosts` показывал, какие
|
|
@@ -7661,7 +7882,7 @@ function hostIdentityPath(home) {
|
|
|
7661
7882
|
function loadHostIdentity(deps) {
|
|
7662
7883
|
const exists = deps.exists ?? existsSync7;
|
|
7663
7884
|
const readFile2 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
|
|
7664
|
-
const
|
|
7885
|
+
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
7665
7886
|
writeFileSync8(`${p}.tmp`, data);
|
|
7666
7887
|
renameSync6(`${p}.tmp`, p);
|
|
7667
7888
|
});
|
|
@@ -7681,7 +7902,7 @@ function loadHostIdentity(deps) {
|
|
|
7681
7902
|
}
|
|
7682
7903
|
const hostId = newId();
|
|
7683
7904
|
try {
|
|
7684
|
-
|
|
7905
|
+
writeFile3(path, JSON.stringify({ hostId, hostName, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
7685
7906
|
} catch {
|
|
7686
7907
|
console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C ${path} \u2014 hostId \u0436\u0438\u0432\u0451\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043E \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430`);
|
|
7687
7908
|
}
|
|
@@ -9366,13 +9587,31 @@ ${notFoundText}` : notFoundText }],
|
|
|
9366
9587
|
isError: true
|
|
9367
9588
|
};
|
|
9368
9589
|
}
|
|
9590
|
+
const reviewerBySpawn = opts.reviewsRunId != null || opts.runKind === "reviewer";
|
|
9591
|
+
const iAmReviewer = run?.reviews_run_id != null || run?.kind === "reviewer" || reviewerBySpawn;
|
|
9592
|
+
if (iAmReviewer && run?.reviews_run_id == null) {
|
|
9593
|
+
return {
|
|
9594
|
+
content: [
|
|
9595
|
+
{
|
|
9596
|
+
type: "text",
|
|
9597
|
+
text: '\u0422\u044B \u0434\u0440\u043E\u043D-\u0440\u0435\u0432\u044C\u044E\u0435\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-375). \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 "**APPROVE**|**REQUEST CHANGES** \u2026"`) \u2014 \u0440\u043E\u0439 \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442 \u0435\u0433\u043E \u043E\u0442\u0442\u0443\u0434\u0430 \u2014 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 done() \u0447\u0435\u0440\u0435\u0437 \u043C\u0438\u043D\u0443\u0442\u0443.'
|
|
9598
|
+
}
|
|
9599
|
+
],
|
|
9600
|
+
isError: true
|
|
9601
|
+
};
|
|
9602
|
+
}
|
|
9369
9603
|
if (run?.reviews_run_id != null) {
|
|
9370
9604
|
if (isTerminalStatus(run.status)) {
|
|
9371
9605
|
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" }] };
|
|
9372
9606
|
}
|
|
9373
9607
|
if (verdict !== "approve" && verdict !== "changes") {
|
|
9374
9608
|
return {
|
|
9375
|
-
content: [
|
|
9609
|
+
content: [
|
|
9610
|
+
{
|
|
9611
|
+
type: "text",
|
|
9612
|
+
text: '\u0422\u044B \u0434\u0440\u043E\u043D-\u0440\u0435\u0432\u044C\u044E\u0435\u0440 (\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E, TASWM-148) \u2014 done() \u0442\u0440\u0435\u0431\u0443\u0435\u0442 verdict: "approve" | "changes" (\u0438\u0442\u043E\u0433 \u043F\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E\u043C\u0443 PR). \u0422\u0440\u0435\u0442\u044C\u0435\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 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F (TASWM-375): \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u043D\u0430 \u043D\u0435\u0433\u043E \u0431\u0443\u0434\u0435\u0442 \u043D\u0435\u0447\u0435\u043C, \u0430 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \xAB\u043D\u0435 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u0434\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430\xBB \u0441\u043E\u0431\u043B\u044E\u0434\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C. \u0420\u0435\u0448\u0438\u0442\u044C \u043D\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u2014 done() \u043D\u0435 \u0437\u043E\u0432\u0438 \u0432\u043E\u0432\u0441\u0435: \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u043A\u0430\u043A failed \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0438 \u0443\u0439\u0434\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443, \u044D\u0442\u043E \u0447\u0435\u0441\u0442\u043D\u0435\u0435 \u0442\u0438\u0445\u043E\u0433\u043E \u0443\u0441\u043F\u0435\u0445\u0430.'
|
|
9613
|
+
}
|
|
9614
|
+
],
|
|
9376
9615
|
isError: true
|
|
9377
9616
|
};
|
|
9378
9617
|
}
|
|
@@ -9382,28 +9621,39 @@ ${notFoundText}` : notFoundText }],
|
|
|
9382
9621
|
await store.addEvent(run.reviews_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E \u043E\u0442 ${run.name}: ${verdictText}${summary ? ` \u2014 ${summary}` : ""}`);
|
|
9383
9622
|
const reviewedSlug = reportSlug(reviewed?.task_slug);
|
|
9384
9623
|
let shots = { attachments: [], warning: "" };
|
|
9624
|
+
let commentWarning = "";
|
|
9385
9625
|
if (opts.taskless && reviewedSlug) {
|
|
9386
9626
|
shots = await uploadShots();
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9627
|
+
try {
|
|
9628
|
+
await opts.taskless.addComment(
|
|
9629
|
+
reviewedSlug,
|
|
9630
|
+
`${(summary ?? "").trim()}${shots.warning}`,
|
|
9631
|
+
{
|
|
9632
|
+
swarmKind: "review",
|
|
9633
|
+
swarmDroneName: run.name,
|
|
9634
|
+
swarmVerdict: verdict,
|
|
9635
|
+
// TASWM-304: круг — ПРОВЕРЯЕМОГО прогона (у ревьюер-прогона свой
|
|
9636
|
+
// review_cycle всегда 0), иначе вердикты всех кругов схлопнулись
|
|
9637
|
+
// бы в одну плашку.
|
|
9638
|
+
swarmReviewCycle: reviewed?.review_cycle ?? 0
|
|
9639
|
+
},
|
|
9640
|
+
shots.attachments
|
|
9641
|
+
);
|
|
9642
|
+
} catch (e) {
|
|
9643
|
+
commentWarning = e instanceof Error ? e.message : String(e);
|
|
9644
|
+
await store.addEvent(run.reviews_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 ${reviewedSlug} \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
|
|
9645
|
+
}
|
|
9646
|
+
} else {
|
|
9647
|
+
commentWarning = opts.taskless ? "\u0437\u0430\u0434\u0430\u0447\u0430 \u0440\u0435\u0432\u044C\u044E \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";
|
|
9648
|
+
await store.addEvent(run.reviews_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
|
|
9402
9649
|
}
|
|
9403
9650
|
const trustedPrUrl = reviewed?.pr_url ?? pr_url;
|
|
9404
9651
|
await store.updateRun(null, { status: "review", pr_url: trustedPrUrl });
|
|
9405
9652
|
await store.addEvent(null, "pr_opened", trustedPrUrl);
|
|
9406
|
-
|
|
9653
|
+
const commentNote = commentWarning ? `
|
|
9654
|
+
|
|
9655
|
+
\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"\`.` : "";
|
|
9656
|
+
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}` }] };
|
|
9407
9657
|
}
|
|
9408
9658
|
if (isGithubPrUrl(pr_url)) {
|
|
9409
9659
|
const report = (summary ?? "").trim();
|
|
@@ -9625,7 +9875,14 @@ async function runDroneServer(runId) {
|
|
|
9625
9875
|
);
|
|
9626
9876
|
const expectedTaskSlug = process.env.SWARM_TASK_SLUG || void 0;
|
|
9627
9877
|
const expectedKind = toRunKind(process.env.SWARM_RUN_KIND);
|
|
9628
|
-
const
|
|
9878
|
+
const spawnReviewsRunId = Number(process.env.SWARM_REVIEWS_RUN_ID);
|
|
9879
|
+
const common = {
|
|
9880
|
+
taskless,
|
|
9881
|
+
runKind: expectedKind,
|
|
9882
|
+
prHeadBranch,
|
|
9883
|
+
...expectedTaskSlug ? { expectedTaskSlug } : {},
|
|
9884
|
+
...Number.isInteger(spawnReviewsRunId) && spawnReviewsRunId > 0 ? { reviewsRunId: spawnReviewsRunId } : {}
|
|
9885
|
+
};
|
|
9629
9886
|
const channelUrl = process.env.SWARM_DRONE_CHANNEL_URL;
|
|
9630
9887
|
const channelToken = process.env.SWARM_DRONE_CHANNEL_TOKEN;
|
|
9631
9888
|
if (channelUrl && channelToken) {
|
|
@@ -10327,6 +10584,20 @@ function makeExclusiveReader(opts) {
|
|
|
10327
10584
|
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
10328
10585
|
});
|
|
10329
10586
|
}
|
|
10587
|
+
function makeDroneCapReader(opts) {
|
|
10588
|
+
const read = makeRepoValueReader({
|
|
10589
|
+
fetch: opts.fetch,
|
|
10590
|
+
fallback: 0,
|
|
10591
|
+
// «настройки нет» — ниже станет Infinity, как и записанный оператором 0
|
|
10592
|
+
valid: (v) => typeof v === "number" && Number.isFinite(v) && v >= 0,
|
|
10593
|
+
...opts.ttlMs !== void 0 ? { ttlMs: opts.ttlMs } : {},
|
|
10594
|
+
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
10595
|
+
});
|
|
10596
|
+
return async (repo) => {
|
|
10597
|
+
const raw = await read(repo);
|
|
10598
|
+
return raw > 0 ? raw : Number.POSITIVE_INFINITY;
|
|
10599
|
+
};
|
|
10600
|
+
}
|
|
10330
10601
|
|
|
10331
10602
|
// src/envGate.ts
|
|
10332
10603
|
import { existsSync as existsSync13 } from "fs";
|
|
@@ -10391,9 +10662,9 @@ function parseRequirements(lines) {
|
|
|
10391
10662
|
out.push({ kind: "docker", raw });
|
|
10392
10663
|
continue;
|
|
10393
10664
|
}
|
|
10394
|
-
const
|
|
10395
|
-
const prefix =
|
|
10396
|
-
const rest =
|
|
10665
|
+
const sep3 = raw.indexOf(":");
|
|
10666
|
+
const prefix = sep3 === -1 ? "" : lower.slice(0, sep3);
|
|
10667
|
+
const rest = sep3 === -1 ? "" : raw.slice(sep3 + 1).trim();
|
|
10397
10668
|
if (!rest) {
|
|
10398
10669
|
out.push({ kind: "unknown", raw });
|
|
10399
10670
|
continue;
|
|
@@ -10766,7 +11037,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
10766
11037
|
// package.json
|
|
10767
11038
|
var package_default = {
|
|
10768
11039
|
name: "@taskless-app/swarm",
|
|
10769
|
-
version: "0.6.
|
|
11040
|
+
version: "0.6.4",
|
|
10770
11041
|
type: "module",
|
|
10771
11042
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
10772
11043
|
keywords: [
|
|
@@ -10816,7 +11087,7 @@ var package_default = {
|
|
|
10816
11087
|
execa: "^10.0.0",
|
|
10817
11088
|
zod: "3.25.76"
|
|
10818
11089
|
},
|
|
10819
|
-
gitHead: "
|
|
11090
|
+
gitHead: "12ecebcf1e60baab07e68cae3e78a29d78cb7fde"
|
|
10820
11091
|
};
|
|
10821
11092
|
|
|
10822
11093
|
// src/version.ts
|
|
@@ -11543,8 +11814,8 @@ var GK = ['claw','spore','eye','hex'];
|
|
|
11543
11814
|
var ST = ${jsonForScript(statusTuples())};
|
|
11544
11815
|
var ORDER = { running:0, blocked:1, paused:2, storm_dead:3, review:4, stalled:5, queued:6, stopped:7, done:8, failed:9, superseded:10 };
|
|
11545
11816
|
var TERM = ${jsonForScript(Object.fromEntries(TERMINAL_STATUSES.map((s) => [s, 1])))};
|
|
11546
|
-
var EVL = { spawned:'\u0434\u0440\u043E\u043D \u0437\u0430\u043F\u0443\u0449\u0435\u043D', step:'', blocked:'\u0432\u043E\u043F\u0440\u043E\u0441 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443', answered:'\u043F\u043E\u043B\u0443\u0447\u0435\u043D \u043E\u0442\u0432\u0435\u0442', pr_opened:'PR \u043E\u0442\u043A\u0440\u044B\u0442', merged:'PR \u0441\u043C\u0435\u0440\u0436\u0451\u043D, \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u0430', done:'\u0433\u043E\u0442\u043E\u0432\u043E', failed:'\u0434\u0440\u043E\u043D \u0443\u043F\u0430\u043B', stalled:'\u0434\u0440\u043E\u043D \u0437\u0430\u0432\u0438\u0441/\u043F\u043E\u0433\u0438\u0431', respawned:'\u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0435', paused:'\u0437\u0430\u043C\u0435\u0440 \u2014 overmind \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D', resumed:'overmind \u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u2014 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B', rescued:'worktree \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u2014 \u0437\u0430\u0431\u0435\u0440\u0438 \u0440\u0443\u043A\u0430\u043C\u0438', conflict:'\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 main (merge-main-in)', died:'\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440', storm_start:'\u26A1 \u043F\u0441\u0438\u043E\u043D\u043D\u0430\u044F \u0441\u0432\u044F\u0437\u044C \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430 \u2014 Anthropic \u0448\u0442\u043E\u0440\u043C\u0438\u0442', storm_end:'\u0441\u0432\u044F\u0437\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E\u0431\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044F', storm_dead:'\u0434\u0440\u043E\u043D \u0432 \u043A\u043E\u043A\u043E\u043D\u0435 (\u0448\u0442\u043E\u0440\u043C)', stopped:'\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D', superseded:'\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D', anomaly:'\u26A0\uFE0F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u044F \u0440\u043E\u044F' };
|
|
11547
|
-
var MDOT = { merged:'--success', done:'--success', pr_opened:'--info', spawned:'--muted-foreground', stalled:'--destructive', failed:'--destructive', respawned:'--warning', paused:'--warning', resumed:'--success', rescued:'--destructive', conflict:'--warning', died:'--destructive', storm_start:'--destructive', storm_end:'--success', storm_dead:'--warning', stopped:'--muted-foreground', superseded:'--muted-foreground', anomaly:'--warning' };
|
|
11817
|
+
var EVL = { spawned:'\u0434\u0440\u043E\u043D \u0437\u0430\u043F\u0443\u0449\u0435\u043D', step:'', blocked:'\u0432\u043E\u043F\u0440\u043E\u0441 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443', answered:'\u043F\u043E\u043B\u0443\u0447\u0435\u043D \u043E\u0442\u0432\u0435\u0442', pr_opened:'PR \u043E\u0442\u043A\u0440\u044B\u0442', merged:'PR \u0441\u043C\u0435\u0440\u0436\u0451\u043D, \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u0430', done:'\u0433\u043E\u0442\u043E\u0432\u043E', failed:'\u0434\u0440\u043E\u043D \u0443\u043F\u0430\u043B', stalled:'\u0434\u0440\u043E\u043D \u0437\u0430\u0432\u0438\u0441/\u043F\u043E\u0433\u0438\u0431', respawned:'\u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0435', paused:'\u0437\u0430\u043C\u0435\u0440 \u2014 overmind \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D', resumed:'overmind \u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u2014 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B', rescued:'worktree \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u2014 \u0437\u0430\u0431\u0435\u0440\u0438 \u0440\u0443\u043A\u0430\u043C\u0438', conflict:'\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 main (merge-main-in)', died:'\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440', storm_start:'\u26A1 \u043F\u0441\u0438\u043E\u043D\u043D\u0430\u044F \u0441\u0432\u044F\u0437\u044C \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430 \u2014 Anthropic \u0448\u0442\u043E\u0440\u043C\u0438\u0442', storm_end:'\u0441\u0432\u044F\u0437\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E\u0431\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044F', storm_dead:'\u0434\u0440\u043E\u043D \u0432 \u043A\u043E\u043A\u043E\u043D\u0435 (\u0448\u0442\u043E\u0440\u043C)', stopped:'\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D', superseded:'\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D', anomaly:'\u26A0\uFE0F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u044F \u0440\u043E\u044F', dep_cleared:'\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u0441\u043D\u044F\u0442\u0430 \u2014 \u0431\u043B\u043E\u043A\u0435\u0440 \u043D\u0435 \u0434\u043E\u0435\u0434\u0435\u0442' };
|
|
11818
|
+
var MDOT = { merged:'--success', done:'--success', pr_opened:'--info', spawned:'--muted-foreground', stalled:'--destructive', failed:'--destructive', respawned:'--warning', paused:'--warning', resumed:'--success', rescued:'--destructive', conflict:'--warning', died:'--destructive', storm_start:'--destructive', storm_end:'--success', storm_dead:'--warning', stopped:'--muted-foreground', superseded:'--muted-foreground', anomaly:'--warning', dep_cleared:'--warning' };
|
|
11548
11819
|
|
|
11549
11820
|
function E(s){ return String(s==null?"":s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]}); }
|
|
11550
11821
|
function color(st){ return 'var('+(ST[st]||ST.queued)[2]+')'; }
|
|
@@ -12828,11 +13099,11 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
12828
13099
|
}
|
|
12829
13100
|
function saveAnomalyState(home, state, io = {}) {
|
|
12830
13101
|
const path = anomalyStatePath(home);
|
|
12831
|
-
const
|
|
13102
|
+
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
12832
13103
|
writeFileSync14(`${p}.tmp`, data);
|
|
12833
13104
|
renameSync9(`${p}.tmp`, p);
|
|
12834
13105
|
});
|
|
12835
|
-
|
|
13106
|
+
writeFile3(path, JSON.stringify(state, null, 2));
|
|
12836
13107
|
}
|
|
12837
13108
|
var AnomalyStateStore = class {
|
|
12838
13109
|
constructor(home, now = Date.now(), io = {}) {
|
|
@@ -13306,6 +13577,7 @@ var NEEDS_HUMAN_MARKS = [
|
|
|
13306
13577
|
CHANGES_WAKE_ESCALATION_MARK
|
|
13307
13578
|
];
|
|
13308
13579
|
var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
|
|
13580
|
+
var REVIEW_NO_VERDICT_REASON = "\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430: \u043D\u0438 approve, \u043D\u0438 changes \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434 (TASWM-375)";
|
|
13309
13581
|
function verdictCommentMark(createdAt) {
|
|
13310
13582
|
return `\u043A\u043E\u043C\u043C\u0435\u043D\u0442-\u0432\u0435\u0440\u0434\u0438\u043A\u0442@${createdAt}`;
|
|
13311
13583
|
}
|
|
@@ -13636,7 +13908,7 @@ function buildCiReadyNudge(prUrl, over) {
|
|
|
13636
13908
|
head,
|
|
13637
13909
|
"",
|
|
13638
13910
|
"\u0422\u044B \u2014 \u0442\u043E\u0442 \u0436\u0435 \u0434\u0440\u043E\u043D-\u0420\u0415\u0412\u042C\u042E\u0415\u0420, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0440\u0430\u0431\u043E\u0442\u0443: \u0444\u0430\u0439\u043B\u044B \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u0448\u044C, \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043D\u0435 \u043F\u0443\u0448\u0438\u0448\u044C. \u0427\u0435\u043A-\u043B\u0438\u0441\u0442 \u0438 \u0434\u0438\u0444\u0444 \u0443\u0436\u0435 \u0432 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0435 \u043F\u0440\u043E\u0448\u043B\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u2014 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0439 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043D\u043E\u0432\u043E, \u0434\u043E\u0432\u0435\u0434\u0438 \u043D\u0430\u0447\u0430\u0442\u043E\u0435.",
|
|
13639
|
-
`\u0417\u0430\u043A\u043E\u043D\u0447\u0438\u043B \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C (\`gh pr comment ${prUrl} --body "**APPROVE**|**REQUEST CHANGES** \u2026"\`) \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "approve"|"changes", summary: "\u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B"). \u0411\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \
|
|
13911
|
+
`\u0417\u0430\u043A\u043E\u043D\u0447\u0438\u043B \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C (\`gh pr comment ${prUrl} --body "**APPROVE**|**REQUEST CHANGES** \u2026"\`) \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "approve"|"changes", summary: "\u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B"). \u0411\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0443\u0441\u043F\u0435\u0445\u043E\u043C \u043D\u0435 \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F: \u0440\u043E\u0439 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 \u0435\u0433\u043E \xAB\u0443\u043F\u0430\u043B\xBB \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043D\u043E\u0432\u043E (TASWM-375) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442, \u0437\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0443\u0436\u0435 \u0437\u0430\u043F\u043B\u0430\u0447\u0435\u043D\u043E, \u043F\u0440\u043E\u043F\u0430\u0434\u0451\u0442.`,
|
|
13640
13912
|
"\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."
|
|
13641
13913
|
].join("\n");
|
|
13642
13914
|
}
|
|
@@ -13861,7 +14133,8 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
13861
14133
|
" \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).",
|
|
13862
14134
|
`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.`
|
|
13863
14135
|
],
|
|
13864
|
-
`3. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "approve"|"changes", summary: "\u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B") \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \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-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A, \u0430 verdict \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D (\u0431\u0435\u0439\u0434\u0436 \u0432 \u0432\u0435\u0431\u0435). \u0422\u044B \u0441\u0430\u043C \u0440\u0435\u0432\u044C\u044E \u041D\u0415 \u043F\u043E\u0434\u043B\u0435\u0436\u0438\u0448\u044C \u2014 \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0438 \u043D\u0435\u0442
|
|
14136
|
+
`3. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "approve"|"changes", summary: "\u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B") \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \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-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A, \u0430 verdict \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D (\u0431\u0435\u0439\u0434\u0436 \u0432 \u0432\u0435\u0431\u0435). \u0422\u044B \u0441\u0430\u043C \u0440\u0435\u0432\u044C\u044E \u041D\u0415 \u043F\u043E\u0434\u043B\u0435\u0436\u0438\u0448\u044C \u2014 \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0438 \u043D\u0435\u0442.`,
|
|
14137
|
+
'\u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D: \u0442\u0440\u0435\u0442\u044C\u0435\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442, \u0438 \u0443\u0441\u043F\u0435\u0445\u043E\u043C \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F (TASWM-375) \u2014 done() \u0431\u0435\u0437 verdict \u043E\u0442\u043A\u0430\u0436\u0435\u0442, \u0430 \u043F\u0440\u043E\u0433\u043E\u043D, \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u0440\u043E\u0439 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 "\u0443\u043F\u0430\u043B" \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043D\u043E\u0432\u043E. \u041E\u0442\u043A\u0430\u0437\u0430\u043B done() \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR (\u0448\u0430\u0433 2): \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442 \u0435\u0433\u043E \u043E\u0442\u0442\u0443\u0434\u0430.'
|
|
13865
14138
|
].join("\n");
|
|
13866
14139
|
}
|
|
13867
14140
|
function droneLabel(run) {
|
|
@@ -13958,7 +14231,7 @@ async function resurrectStormDead(deps, worktreeExists, hasTranscript, opts) {
|
|
|
13958
14231
|
let resurrected = 0;
|
|
13959
14232
|
for (const run of deps.db.listRuns("storm_dead")) {
|
|
13960
14233
|
if (countActiveDrones(deps.db) >= cap) break;
|
|
13961
|
-
if (!await
|
|
14234
|
+
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
13962
14235
|
const wt = run.worktree_path;
|
|
13963
14236
|
const mode = !!wt && worktreeExists(wt) && hasTranscript(wt) ? "continue" : "fresh";
|
|
13964
14237
|
deps.db.addEvent(run.id, "resumed", mode === "continue" ? STORM_RESUME_CONTINUE_TEXT : STORM_RESUME_FRESH_TEXT);
|
|
@@ -14111,7 +14384,7 @@ async function wakeAuthorOnRedCi(deps, run, now, ci, worktreeExists, hasTranscri
|
|
|
14111
14384
|
if (run.pid != null && deps.isAlive(run.pid)) return false;
|
|
14112
14385
|
if (deps.resourcesOk && !deps.resourcesOk()) return false;
|
|
14113
14386
|
if (countActiveDrones(deps.db) >= droneCap) return false;
|
|
14114
|
-
if (!await
|
|
14387
|
+
if (!await projectQuotaGateAllows(deps, run)) return false;
|
|
14115
14388
|
const stats = ciWakeStats(deps.db, run.id);
|
|
14116
14389
|
if (stats.count >= CI_WAKE_LIMIT) {
|
|
14117
14390
|
if (!stats.escalated) {
|
|
@@ -14242,7 +14515,7 @@ async function wakeAuthorOnChanges(deps, run, now, droneCap) {
|
|
|
14242
14515
|
if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled")) return false;
|
|
14243
14516
|
if (deps.resourcesOk && !deps.resourcesOk()) return false;
|
|
14244
14517
|
if (countActiveDrones(deps.db) >= droneCap) return false;
|
|
14245
|
-
if (!await
|
|
14518
|
+
if (!await projectQuotaGateAllows(deps, run)) return false;
|
|
14246
14519
|
if (run.review_cycle >= REVIEW_CYCLE_LIMIT) return false;
|
|
14247
14520
|
const wakes = deps.db.events(run.id).filter((e) => e.kind === "resumed" && (e.text ?? "").includes(CHANGES_WAKE_MARK));
|
|
14248
14521
|
if (wakes.length >= CHANGES_WAKE_LIMIT) {
|
|
@@ -14336,37 +14609,55 @@ function activeDronesOnRepo(deps, repo, exceptRunId) {
|
|
|
14336
14609
|
(r) => r.id !== exceptRunId && (r.kind ?? "drone") === "drone" && ACTIVE_DRONE_STATUSES.has(r.status) && effectiveRepo(deps, r) === repo
|
|
14337
14610
|
);
|
|
14338
14611
|
}
|
|
14339
|
-
async function
|
|
14340
|
-
|
|
14612
|
+
async function projectCapFor(deps, repo) {
|
|
14613
|
+
let exclusive = false;
|
|
14614
|
+
if (deps.exclusiveProject) {
|
|
14615
|
+
try {
|
|
14616
|
+
exclusive = await deps.exclusiveProject(repo);
|
|
14617
|
+
} catch (e) {
|
|
14618
|
+
console.error("[overmind] \u0447\u0442\u0435\u043D\u0438\u0435 \u044D\u043A\u0441\u043A\u043B\u044E\u0437\u0438\u0432\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u2014 \u0441\u043F\u0430\u0432\u043D\u044E \u043A\u0430\u043A \u0440\u0430\u043D\u044C\u0448\u0435:", e);
|
|
14619
|
+
}
|
|
14620
|
+
}
|
|
14621
|
+
if (exclusive) return { cap: 1, exclusive: true };
|
|
14622
|
+
let quota = Number.POSITIVE_INFINITY;
|
|
14623
|
+
if (deps.projectDroneCap) {
|
|
14624
|
+
try {
|
|
14625
|
+
const v = await deps.projectDroneCap(repo);
|
|
14626
|
+
if (typeof v === "number" && v > 0) quota = v;
|
|
14627
|
+
} catch (e) {
|
|
14628
|
+
console.error("[overmind] \u0447\u0442\u0435\u043D\u0438\u0435 \u043A\u0432\u043E\u0442\u044B \xAB\u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\xBB \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u2014 \u0441\u043F\u0430\u0432\u043D\u044E \u043A\u0430\u043A \u0440\u0430\u043D\u044C\u0448\u0435:", e);
|
|
14629
|
+
}
|
|
14630
|
+
}
|
|
14631
|
+
return { cap: quota, exclusive: false };
|
|
14632
|
+
}
|
|
14633
|
+
async function projectQuotaGateAllows(deps, run) {
|
|
14634
|
+
if (!deps.exclusiveProject && !deps.projectDroneCap) return true;
|
|
14341
14635
|
const repo = effectiveRepo(deps, run);
|
|
14342
14636
|
if (!repo) return true;
|
|
14343
|
-
|
|
14344
|
-
try {
|
|
14345
|
-
exclusive = await deps.exclusiveProject(repo);
|
|
14346
|
-
} catch (e) {
|
|
14347
|
-
console.error("[overmind] \u0447\u0442\u0435\u043D\u0438\u0435 \u044D\u043A\u0441\u043A\u043B\u044E\u0437\u0438\u0432\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u2014 \u0441\u043F\u0430\u0432\u043D\u044E \u043A\u0430\u043A \u0440\u0430\u043D\u044C\u0448\u0435:", e);
|
|
14348
|
-
return true;
|
|
14349
|
-
}
|
|
14637
|
+
const { cap, exclusive } = await projectCapFor(deps, repo);
|
|
14350
14638
|
const logged = deps.exclusiveLogged ??= /* @__PURE__ */ new Map();
|
|
14351
|
-
if (
|
|
14639
|
+
if (Number.isFinite(cap)) {
|
|
14352
14640
|
const busy = activeDronesOnRepo(deps, repo, run.id);
|
|
14353
|
-
if (busy.length
|
|
14641
|
+
if (busy.length >= cap) {
|
|
14354
14642
|
const who = busy.map((r) => `${droneLabel(r)} (${r.task_slug})`).join(", ");
|
|
14355
|
-
const reason = `\u043F\u0440\u043E\u0435\u043A\u0442 \u0433\u043E\u043D\u044F\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 (\u044D\u043A\u0441\u043A\u043B\u044E\u0437\u0438\u0432\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B) \u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u0437\u0430\u043D\u044F\u0442: ${who}`;
|
|
14643
|
+
const reason = exclusive ? `\u043F\u0440\u043E\u0435\u043A\u0442 \u0433\u043E\u043D\u044F\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 (\u044D\u043A\u0441\u043A\u043B\u044E\u0437\u0438\u0432\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B) \u0438 \u0441\u0435\u0439\u0447\u0430\u0441 \u0437\u0430\u043D\u044F\u0442: ${who}` : `\u043F\u0440\u043E\u0435\u043A\u0442\u0443 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u043E ${cap} \u0434\u0440\u043E\u043D(\u043E\u0432) \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E (\xAB\u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\xBB), \u0438 \u0432\u0441\u0435 \u0437\u0430\u043D\u044F\u0442\u044B: ${who}`;
|
|
14356
14644
|
if (logged.get(run.id) !== reason) {
|
|
14357
14645
|
logged.set(run.id, reason);
|
|
14358
14646
|
deps.db.addEvent(
|
|
14359
14647
|
run.id,
|
|
14360
14648
|
"step",
|
|
14361
|
-
`${reason} \u2014 \u0436\u0434\u0443 \u043D\u0435 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u0443\u044E \u043C\u0430\u0448\u0438\u043D\u0443, \u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430; \u043F\u043E\u0434\u043D\u0438\u043C\u0443 \u0441\u0430\u043C, \u043A\u0430\u043A \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u043D \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F (TASWM-314)`
|
|
14649
|
+
`${reason} \u2014 \u0436\u0434\u0443 \u043D\u0435 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u0443\u044E \u043C\u0430\u0448\u0438\u043D\u0443, \u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430; \u043F\u043E\u0434\u043D\u0438\u043C\u0443 \u0441\u0430\u043C, \u043A\u0430\u043A \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u043D \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F (${exclusive ? "TASWM-314" : "TASWM-365"})`
|
|
14650
|
+
);
|
|
14651
|
+
narrate(
|
|
14652
|
+
deps,
|
|
14653
|
+
exclusive ? `${droneLabel(run)} \u2014 ${reason}. \u041D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E: \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u044B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043C\u0435\u0448\u0430\u044E\u0442 \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0443.` : `${droneLabel(run)} \u2014 ${reason}. \u041D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E: \u043F\u0440\u043E\u0435\u043A\u0442 \u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0432\u043E\u044E \u043A\u0432\u043E\u0442\u0443 \u0434\u0440\u043E\u043D\u043E\u0432, \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043F\u043E\u0434\u043E\u0436\u0434\u0451\u0442.`
|
|
14362
14654
|
);
|
|
14363
|
-
narrate(deps, `${droneLabel(run)} \u2014 ${reason}. \u041D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E: \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u044B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043C\u0435\u0448\u0430\u044E\u0442 \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0443.`);
|
|
14364
14655
|
}
|
|
14365
14656
|
return false;
|
|
14366
14657
|
}
|
|
14367
14658
|
}
|
|
14368
14659
|
if (logged.delete(run.id)) {
|
|
14369
|
-
deps.db.addEvent(run.id, "step", "\u043F\u0440\u043E\u0435\u043A\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u043B\u0441\u044F \u2014 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0430 \
|
|
14660
|
+
deps.db.addEvent(run.id, "step", "\u043F\u0440\u043E\u0435\u043A\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u043B\u0441\u044F \u2014 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0430 \u043F\u043E \u043A\u0432\u043E\u0442\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441\u043D\u044F\u0442\u0430 (TASWM-314/TASWM-365)");
|
|
14370
14661
|
}
|
|
14371
14662
|
return true;
|
|
14372
14663
|
}
|
|
@@ -14379,19 +14670,14 @@ function pruneGateLogs(deps) {
|
|
|
14379
14670
|
for (const id of [...map.keys()]) if (!open.has(id)) map.delete(id);
|
|
14380
14671
|
}
|
|
14381
14672
|
}
|
|
14382
|
-
async function
|
|
14673
|
+
async function projectQuotaBlockedRunIds(deps) {
|
|
14383
14674
|
const blocked = /* @__PURE__ */ new Set();
|
|
14384
|
-
if (!deps.exclusiveProject) return blocked;
|
|
14675
|
+
if (!deps.exclusiveProject && !deps.projectDroneCap) return blocked;
|
|
14385
14676
|
for (const run of deps.db.listRuns("queued")) {
|
|
14386
14677
|
const repo = effectiveRepo(deps, run);
|
|
14387
14678
|
if (!repo) continue;
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
exclusive = await deps.exclusiveProject(repo);
|
|
14391
|
-
} catch {
|
|
14392
|
-
continue;
|
|
14393
|
-
}
|
|
14394
|
-
if (exclusive && activeDronesOnRepo(deps, repo, run.id).length > 0) blocked.add(run.id);
|
|
14679
|
+
const { cap } = await projectCapFor(deps, repo);
|
|
14680
|
+
if (Number.isFinite(cap) && activeDronesOnRepo(deps, repo, run.id).length >= cap) blocked.add(run.id);
|
|
14395
14681
|
}
|
|
14396
14682
|
return blocked;
|
|
14397
14683
|
}
|
|
@@ -14407,6 +14693,19 @@ async function fillSuggestedArea(deps, run) {
|
|
|
14407
14693
|
`\u0437\u043E\u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u0437 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0440\u0435\u043F\u043E: ${guess.zone} (${Math.round(guess.confidence * 100)}% \u2014 ${guess.why}) \u2014 TASWM-348`
|
|
14408
14694
|
);
|
|
14409
14695
|
}
|
|
14696
|
+
function noteExhaustedDeps(deps, run, verdicts) {
|
|
14697
|
+
const exhausted = verdicts.filter((d) => d.outcome === "exhausted");
|
|
14698
|
+
if (exhausted.length === 0) return;
|
|
14699
|
+
const already = new Set(
|
|
14700
|
+
deps.db.events(run.id).filter((e) => e.kind === "dep_cleared").map((e) => e.text ?? "")
|
|
14701
|
+
);
|
|
14702
|
+
for (const d of exhausted) {
|
|
14703
|
+
const text = depClearedText(d.slug, d);
|
|
14704
|
+
if (already.has(text)) continue;
|
|
14705
|
+
deps.db.addEvent(run.id, "dep_cleared", text);
|
|
14706
|
+
narrate(deps, `${droneLabel(run)} \u2014 ${text}`);
|
|
14707
|
+
}
|
|
14708
|
+
}
|
|
14410
14709
|
async function trySpawnQueuedRun(deps, run) {
|
|
14411
14710
|
if (deps.control?.paused) return "skipped";
|
|
14412
14711
|
const mergedUrl = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
|
|
@@ -14417,11 +14716,13 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
14417
14716
|
narrate(deps, `${droneLabel(run)} \u2014 \u0437\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} \u0443\u0436\u0435 \u0441\u043C\u0435\u0440\u0436\u0435\u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u043C \u0440\u0430\u043D\u043E\u043C, \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E.`);
|
|
14418
14717
|
return "closed";
|
|
14419
14718
|
}
|
|
14420
|
-
|
|
14719
|
+
const depVerdicts = deps.db.depVerdicts(run.depends_on);
|
|
14720
|
+
if (depVerdicts.some((d) => !d.met)) return "skipped";
|
|
14421
14721
|
if (!await envGateAllows(deps, run)) return "skipped";
|
|
14422
|
-
if (!await
|
|
14722
|
+
if (!await projectQuotaGateAllows(deps, run)) return "skipped";
|
|
14423
14723
|
if (!await prodStillAllows(deps, run, ["queued"])) return "skipped";
|
|
14424
14724
|
if (deps.db.takeRun && !await deps.db.takeRun(run.id)) return "skipped";
|
|
14725
|
+
noteExhaustedDeps(deps, run, depVerdicts);
|
|
14425
14726
|
await fillSuggestedArea(deps, run);
|
|
14426
14727
|
try {
|
|
14427
14728
|
await deps.spawn(run);
|
|
@@ -14456,7 +14757,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
14456
14757
|
if (!parked.length) return;
|
|
14457
14758
|
let candidate;
|
|
14458
14759
|
for (const run of orderQueue(parked)) {
|
|
14459
|
-
if (await
|
|
14760
|
+
if (await projectQuotaGateAllows(deps, run)) {
|
|
14460
14761
|
candidate = run;
|
|
14461
14762
|
break;
|
|
14462
14763
|
}
|
|
@@ -14786,6 +15087,28 @@ async function postScrapedVerdictReport(deps, runId, taskSlug, verdict, droneNam
|
|
|
14786
15087
|
deps.db.addEvent(runId, "step", `\u0434\u0435\u043C\u043E\u043D-\u043E\u0442\u0447\u0451\u0442 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 ${taskSlug} \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${e instanceof Error ? e.message : String(e)}`);
|
|
14787
15088
|
});
|
|
14788
15089
|
}
|
|
15090
|
+
async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
|
|
15091
|
+
if (!deps.verdictComment || !rv.pr_url) return false;
|
|
15092
|
+
const found = await deps.verdictComment(rv.pr_url, since).catch(() => null);
|
|
15093
|
+
if (!found) return false;
|
|
15094
|
+
const verdictText = verdictLabel(found.verdict);
|
|
15095
|
+
deps.db.updateRun(reviewsRunId, { review_verdict: found.verdict });
|
|
15096
|
+
deps.db.addEvent(reviewsRunId, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D \u0438\u0437 PR-\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430 (${ctx.how}): ${verdictText}`);
|
|
15097
|
+
await closeReviewerRun(
|
|
15098
|
+
deps,
|
|
15099
|
+
rv,
|
|
15100
|
+
`\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C (${verdictText}), ${ctx.how} \u2014 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u0440\u044B\u0442\u043E \u0431\u0435\u0437 \u0441\u043F\u0430\u0432\u043D\u0430 \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 (TASWM-229)`
|
|
15101
|
+
);
|
|
15102
|
+
narrate(deps, `${droneLabel(rv)} \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442 (${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 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E.`);
|
|
15103
|
+
const reviewed = deps.db.getRun(reviewsRunId);
|
|
15104
|
+
if (reviewed) {
|
|
15105
|
+
await postScrapedVerdictReport(deps, rv.id, reviewed.task_slug, found.verdict, rv.name || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)", reviewed.workspace_id, reviewed.review_cycle ?? 0);
|
|
15106
|
+
if (found.verdict === "changes" && !ctx.stormActive && !deps.db.activeResolverFor(reviewed.id)) {
|
|
15107
|
+
await wakeAuthorOnChanges(deps, reviewed, ctx.now, ctx.droneCap);
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
15110
|
+
return true;
|
|
15111
|
+
}
|
|
14789
15112
|
function buildEscalationCandidates(db) {
|
|
14790
15113
|
const candidates = [];
|
|
14791
15114
|
for (const run of db.listRuns("review")) {
|
|
@@ -14945,6 +15268,24 @@ async function tickOnce(deps) {
|
|
|
14945
15268
|
continue;
|
|
14946
15269
|
}
|
|
14947
15270
|
if (run.reviews_run_id != null) {
|
|
15271
|
+
const reviewed = deps.db.getRun(run.reviews_run_id);
|
|
15272
|
+
const sameCycle = reviewed != null && (reviewed.review_cycle ?? 0) === (run.review_cycle ?? 0);
|
|
15273
|
+
if (reviewed && sameCycle && reviewed.review_verdict == null) {
|
|
15274
|
+
const salvaged = await salvageVerdictFromPrComment(deps, run, run.reviews_run_id, run.created_at, {
|
|
15275
|
+
how: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u0437\u0432\u0430\u043B done(), \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u0441\u044F",
|
|
15276
|
+
now,
|
|
15277
|
+
droneCap,
|
|
15278
|
+
stormActive
|
|
15279
|
+
});
|
|
15280
|
+
if (salvaged) continue;
|
|
15281
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15282
|
+
});
|
|
15283
|
+
deps.db.updateRun(run.id, { status: "failed", stop_reason: REVIEW_NO_VERDICT_REASON });
|
|
15284
|
+
deps.db.addEvent(run.id, "failed", REVIEW_NO_VERDICT_REASON);
|
|
15285
|
+
deps.db.addEvent(run.reviews_run_id, "step", `\u0440\u0435\u0432\u044C\u044E\u0435\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 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u043E\u0441\u044C, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434`);
|
|
15286
|
+
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, \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \u0443\u043F\u0430\u0432\u0448\u0438\u0439: \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
|
|
15287
|
+
continue;
|
|
15288
|
+
}
|
|
14948
15289
|
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
14949
15290
|
});
|
|
14950
15291
|
deps.db.updateRun(run.id, { status: "done" });
|
|
@@ -14990,7 +15331,7 @@ async function tickOnce(deps) {
|
|
|
14990
15331
|
if (st.conflicting && deps.resolveConflict && !stormActive) {
|
|
14991
15332
|
if (deps.resourcesOk && !deps.resourcesOk()) continue;
|
|
14992
15333
|
if (countActiveDrones(deps.db) >= droneCap) continue;
|
|
14993
|
-
if (!await
|
|
15334
|
+
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
14994
15335
|
if (deps.db.activeResolverFor(run.id)) continue;
|
|
14995
15336
|
if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) {
|
|
14996
15337
|
if (!deps.db.events(run.id).some((e) => e.kind === "conflict" && (e.text ?? "").includes(RESOLVE_ESCALATION_MARK))) {
|
|
@@ -15218,33 +15559,13 @@ async function tickOnce(deps) {
|
|
|
15218
15559
|
}
|
|
15219
15560
|
}
|
|
15220
15561
|
if (!alive && run.status === "running" && run.reviews_run_id != null && run.pr_url && deps.verdictComment) {
|
|
15221
|
-
const
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
`\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D \u0438\u0437 PR-\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430 (\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432\u044B\u0448\u0435\u043B, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done()): ${verdictText}`
|
|
15229
|
-
);
|
|
15230
|
-
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15231
|
-
});
|
|
15232
|
-
deps.db.updateRun(run.id, { status: "done" });
|
|
15233
|
-
deps.db.addEvent(
|
|
15234
|
-
run.id,
|
|
15235
|
-
"done",
|
|
15236
|
-
`\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u044B\u043D\u0435\u0441\u0435\u043D \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C (${verdictText}), \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u2014 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u0440\u044B\u0442\u043E \u0431\u0435\u0437 \u0441\u043F\u0430\u0432\u043D\u0430 \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 (TASWM-229)`
|
|
15237
|
-
);
|
|
15238
|
-
narrate(deps, `${droneLabel(run)} \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442 (${verdictText}) \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0438 \u0432\u044B\u0448\u0435\u043B \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u0430\u043B, \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E.`);
|
|
15239
|
-
const reviewed = deps.db.getRun(run.reviews_run_id);
|
|
15240
|
-
if (reviewed) {
|
|
15241
|
-
await postScrapedVerdictReport(deps, run.id, reviewed.task_slug, verdict.verdict, run.name || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)", reviewed.workspace_id, reviewed.review_cycle ?? 0);
|
|
15242
|
-
if (verdict.verdict === "changes" && !stormActive && !deps.db.activeResolverFor(reviewed.id)) {
|
|
15243
|
-
await wakeAuthorOnChanges(deps, reviewed, now, droneCap);
|
|
15244
|
-
}
|
|
15245
|
-
}
|
|
15246
|
-
continue;
|
|
15247
|
-
}
|
|
15562
|
+
const salvaged = await salvageVerdictFromPrComment(deps, run, run.reviews_run_id, run.created_at, {
|
|
15563
|
+
how: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432\u044B\u0448\u0435\u043B, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done()",
|
|
15564
|
+
now,
|
|
15565
|
+
droneCap,
|
|
15566
|
+
stormActive
|
|
15567
|
+
});
|
|
15568
|
+
if (salvaged) continue;
|
|
15248
15569
|
}
|
|
15249
15570
|
if (!alive && run.status === "running" && run.reviews_run_id != null && run.pr_url && deps.ci) {
|
|
15250
15571
|
const parent = deps.db.getRun(run.reviews_run_id);
|
|
@@ -15312,7 +15633,7 @@ async function tickOnce(deps) {
|
|
|
15312
15633
|
if (run.stop_reason === RELIEF_PAUSE_REASON) continue;
|
|
15313
15634
|
if (run.stop_reason != null && run.stop_reason.startsWith(EXITED_NO_DONE_REASON)) continue;
|
|
15314
15635
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
15315
|
-
if (!await
|
|
15636
|
+
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
15316
15637
|
const wt = run.worktree_path;
|
|
15317
15638
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
15318
15639
|
const mode = canContinue ? "continue" : "fresh";
|
|
@@ -15392,7 +15713,7 @@ async function tickOnce(deps) {
|
|
|
15392
15713
|
}
|
|
15393
15714
|
if (deps.watchdog) {
|
|
15394
15715
|
try {
|
|
15395
|
-
const action = decideWatchdogAction(deps.db, now, deps.watchdog.state, deps.watchdog.thresholds, droneCap, { exists: worktreeExists, hasTranscript }, await
|
|
15716
|
+
const action = decideWatchdogAction(deps.db, now, deps.watchdog.state, deps.watchdog.thresholds, droneCap, { exists: worktreeExists, hasTranscript }, await projectQuotaBlockedRunIds(deps));
|
|
15396
15717
|
if (action) await applyWatchdogAction(deps, action);
|
|
15397
15718
|
} catch (e) {
|
|
15398
15719
|
console.error("[overmind] \u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430: \u0442\u0438\u043A \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
|
|
@@ -15957,6 +16278,7 @@ async function runDaemon(opts) {
|
|
|
15957
16278
|
let baseBranchReader;
|
|
15958
16279
|
let envGate;
|
|
15959
16280
|
let exclusiveProject;
|
|
16281
|
+
let projectDroneCap;
|
|
15960
16282
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
|
|
15961
16283
|
const client = db;
|
|
15962
16284
|
verifyModeReader = makeVerifyModeReader({
|
|
@@ -15996,6 +16318,13 @@ async function runDaemon(opts) {
|
|
|
15996
16318
|
},
|
|
15997
16319
|
ttlMs: 6e4
|
|
15998
16320
|
});
|
|
16321
|
+
projectDroneCap = makeDroneCapReader({
|
|
16322
|
+
fetch: async (repo) => {
|
|
16323
|
+
const repoFullName = await remoteRepoFullName(repo).catch(() => null);
|
|
16324
|
+
return repoFullName ? client.repoDroneCap(repoFullName) : null;
|
|
16325
|
+
},
|
|
16326
|
+
ttlMs: 6e4
|
|
16327
|
+
});
|
|
15999
16328
|
}
|
|
16000
16329
|
let spawnReviewerFn;
|
|
16001
16330
|
let verdictCommentFn;
|
|
@@ -16216,6 +16545,9 @@ async function runDaemon(opts) {
|
|
|
16216
16545
|
// client-режим (настройка живёт в проекте); repoFallback — чтобы прогон с пустым repo не
|
|
16217
16546
|
// выглядел отдельным проектом. Дедуп событий — своя мапа на цикл демона, как у envGate.
|
|
16218
16547
|
...exclusiveProject ? { exclusiveProject } : {},
|
|
16548
|
+
// TASWM-365: и квота «дронов на проект» рядом — один и тот же гейт очереди читает обе
|
|
16549
|
+
// настройки и берёт из них минимум (см. projectCapFor).
|
|
16550
|
+
...projectDroneCap ? { projectDroneCap } : {},
|
|
16219
16551
|
repoFallback: opts.repo,
|
|
16220
16552
|
exclusiveLogged: /* @__PURE__ */ new Map(),
|
|
16221
16553
|
// Зона задачи из истории репо (TASWM-348) — предлагаем сами, если постановщик
|
|
@@ -16672,6 +17004,7 @@ export {
|
|
|
16672
17004
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
16673
17005
|
REVIEW_CYCLE_LIMIT,
|
|
16674
17006
|
REVIEW_ESCALATION_MARK,
|
|
17007
|
+
REVIEW_NO_VERDICT_REASON,
|
|
16675
17008
|
REVIEW_PR_UNKNOWN_MARK,
|
|
16676
17009
|
REVIEW_SPAWN_LIMIT,
|
|
16677
17010
|
REVIEW_UNRECOVERABLE_MARK,
|