@taskless-app/swarm 0.6.7 → 0.6.8
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 +124 -2
- package/dist/overmind.js +116 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -322,6 +322,35 @@ var init_reviewPhase = __esm({
|
|
|
322
322
|
}
|
|
323
323
|
});
|
|
324
324
|
|
|
325
|
+
// ../swarm-core/src/mergeGate.ts
|
|
326
|
+
function computeMergeGate(input) {
|
|
327
|
+
if (!input.hasRun) {
|
|
328
|
+
return { allowed: true, code: "unknown_pr", reason: "PR \u043D\u0435 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0443 \u0440\u043E\u044F \u2014 \u0433\u0435\u0439\u0442 \u0440\u0435\u0432\u044C\u044E \u043A \u043D\u0435\u043C\u0443 \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F" };
|
|
329
|
+
}
|
|
330
|
+
if (!input.autoReview) {
|
|
331
|
+
return { allowed: true, code: "auto_review_off", reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F" };
|
|
332
|
+
}
|
|
333
|
+
if (input.reviewVerdict === "approve") {
|
|
334
|
+
return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
|
|
335
|
+
}
|
|
336
|
+
if (input.reviewVerdict === "changes") {
|
|
337
|
+
return { allowed: false, code: "changes_requested", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043F\u0440\u0430\u0432\u043A\u0438 (REQUEST CHANGES) \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430" };
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
allowed: false,
|
|
341
|
+
code: "no_verdict",
|
|
342
|
+
reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function mergeGateLine(d) {
|
|
346
|
+
return `${d.allowed ? "\u2705 \u043C\u0435\u0440\u0436 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D" : "\u26D4 \u043C\u0435\u0440\u0436 \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D"}: ${d.reason}`;
|
|
347
|
+
}
|
|
348
|
+
var init_mergeGate = __esm({
|
|
349
|
+
"../swarm-core/src/mergeGate.ts"() {
|
|
350
|
+
"use strict";
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
325
354
|
// ../swarm-core/src/status.ts
|
|
326
355
|
function statusMeta(status) {
|
|
327
356
|
return STATUS_META[status] ?? FALLBACK;
|
|
@@ -1176,6 +1205,7 @@ var init_src = __esm({
|
|
|
1176
1205
|
init_types();
|
|
1177
1206
|
init_timeline();
|
|
1178
1207
|
init_reviewPhase();
|
|
1208
|
+
init_mergeGate();
|
|
1179
1209
|
init_status();
|
|
1180
1210
|
init_deps();
|
|
1181
1211
|
init_time();
|
|
@@ -2005,7 +2035,7 @@ var init_package = __esm({
|
|
|
2005
2035
|
"package.json"() {
|
|
2006
2036
|
package_default = {
|
|
2007
2037
|
name: "@taskless-app/swarm",
|
|
2008
|
-
version: "0.6.
|
|
2038
|
+
version: "0.6.8",
|
|
2009
2039
|
type: "module",
|
|
2010
2040
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2011
2041
|
keywords: [
|
|
@@ -2055,7 +2085,7 @@ var init_package = __esm({
|
|
|
2055
2085
|
execa: "^10.0.0",
|
|
2056
2086
|
zod: "3.25.76"
|
|
2057
2087
|
},
|
|
2058
|
-
gitHead: "
|
|
2088
|
+
gitHead: "c06a3749a3ef7d0c8fcae83fd79b84ba2318859b"
|
|
2059
2089
|
};
|
|
2060
2090
|
}
|
|
2061
2091
|
});
|
|
@@ -2370,6 +2400,16 @@ async function daemonStatus2() {
|
|
|
2370
2400
|
return null;
|
|
2371
2401
|
}
|
|
2372
2402
|
}
|
|
2403
|
+
async function daemonMergeGate(ref) {
|
|
2404
|
+
try {
|
|
2405
|
+
const res = await fetch(`${BASE}/merge-gate?pr=${encodeURIComponent(ref)}`, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
2406
|
+
if (!res.ok) return null;
|
|
2407
|
+
const body = await res.json();
|
|
2408
|
+
return typeof body?.allowed === "boolean" && typeof body?.reason === "string" ? body : null;
|
|
2409
|
+
} catch {
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2373
2413
|
var TIMEOUT_MS, ACTION_TIMEOUT_MS, BASE, NO_DAEMON_NOTE;
|
|
2374
2414
|
var init_daemonLink = __esm({
|
|
2375
2415
|
"src/daemonLink.ts"() {
|
|
@@ -5636,6 +5676,27 @@ var SwarmClient = class _SwarmClient {
|
|
|
5636
5676
|
const v = sc.config?.maxDrones;
|
|
5637
5677
|
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
5638
5678
|
}
|
|
5679
|
+
/**
|
|
5680
|
+
* Live АВТОРЕВЬЮ проекта из Taskless (TASWM-355): тот же swarm_repo_config по
|
|
5681
|
+
* repoFullName — Project.swarmConfig.autoReview. true → у проекта перед мержем обязан быть
|
|
5682
|
+
* вердикт дрона-ревьюера, и гейт мержа (mergeGate.ts) не пускает `gh pr merge`, пока
|
|
5683
|
+
* вердикта нет. До этой ручки демон знал только ГЛОБАЛЬНЫЙ env SWARM_AUTO_REVIEW
|
|
5684
|
+
* («поднимать ли ревьюеров на этой машине»), а настройка проекта до него не доезжала —
|
|
5685
|
+
* гейту же нужен ответ про КОНКРЕТНЫЙ проект. null (прод недоступен / repo не смэплен /
|
|
5686
|
+
* поля нет) → ридер (makeAutoReviewReader) возьмёт env, то есть прежнее поведение.
|
|
5687
|
+
*/
|
|
5688
|
+
async repoAutoReview(repoFullName) {
|
|
5689
|
+
let res;
|
|
5690
|
+
try {
|
|
5691
|
+
res = await this.call("swarm_repo_config", { repoFullName });
|
|
5692
|
+
} catch {
|
|
5693
|
+
return null;
|
|
5694
|
+
}
|
|
5695
|
+
if (res?.isError) return null;
|
|
5696
|
+
const sc = res.structuredContent ?? {};
|
|
5697
|
+
const v = sc.config?.autoReview;
|
|
5698
|
+
return typeof v === "boolean" ? v : null;
|
|
5699
|
+
}
|
|
5639
5700
|
/**
|
|
5640
5701
|
* TASWM-291: МАШИНЫ роя владельца (swarm_list_hosts) — чтобы `swarm spawn --host
|
|
5641
5702
|
* <имя>` резолвил человеческое имя в hostId, а `swarm hosts` показывал, какие
|
|
@@ -7119,6 +7180,41 @@ async function ghChecks(pr, run = realChecksRunner) {
|
|
|
7119
7180
|
|
|
7120
7181
|
// src/web.ts
|
|
7121
7182
|
init_config();
|
|
7183
|
+
|
|
7184
|
+
// src/mergeGate.ts
|
|
7185
|
+
init_src();
|
|
7186
|
+
function prNumberOf(ref) {
|
|
7187
|
+
const s = (ref || "").trim();
|
|
7188
|
+
if (!s) return null;
|
|
7189
|
+
const url = s.match(/\/pull\/(\d+)/);
|
|
7190
|
+
if (url) return Number(url[1]);
|
|
7191
|
+
const bare = s.match(/^#?(\d+)$/);
|
|
7192
|
+
return bare ? Number(bare[1]) : null;
|
|
7193
|
+
}
|
|
7194
|
+
function findRunByPr(runs, ref) {
|
|
7195
|
+
const num = prNumberOf(ref);
|
|
7196
|
+
if (num == null) return null;
|
|
7197
|
+
let best = null;
|
|
7198
|
+
for (const r of runs) {
|
|
7199
|
+
if (!r.pr_url || prNumberOf(r.pr_url) !== num) continue;
|
|
7200
|
+
if (!best || r.id > best.id) best = r;
|
|
7201
|
+
}
|
|
7202
|
+
return best;
|
|
7203
|
+
}
|
|
7204
|
+
function makeMergeGateResolver(deps) {
|
|
7205
|
+
return async (ref) => {
|
|
7206
|
+
const run = findRunByPr(deps.listRuns(), ref);
|
|
7207
|
+
if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
|
|
7208
|
+
const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
|
|
7209
|
+
const decision = computeMergeGate({ hasRun: true, autoReview, reviewVerdict: run.review_verdict });
|
|
7210
|
+
return {
|
|
7211
|
+
...decision,
|
|
7212
|
+
run: { id: run.id, task_slug: run.task_slug, name: run.name, pr_url: run.pr_url, review_verdict: run.review_verdict }
|
|
7213
|
+
};
|
|
7214
|
+
};
|
|
7215
|
+
}
|
|
7216
|
+
|
|
7217
|
+
// src/web.ts
|
|
7122
7218
|
init_droneChannel();
|
|
7123
7219
|
|
|
7124
7220
|
// src/oauth.ts
|
|
@@ -8670,6 +8766,12 @@ function makeHandler(deps) {
|
|
|
8670
8766
|
hostId: deps.selfHost?.hostId ?? null
|
|
8671
8767
|
});
|
|
8672
8768
|
}
|
|
8769
|
+
if (p === "/merge-gate") {
|
|
8770
|
+
const ref = url.searchParams.get("pr") ?? "";
|
|
8771
|
+
if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
|
|
8772
|
+
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({ listRuns: () => deps.db.listRuns(), fallbackAutoReview: SWARM_AUTO_REVIEW });
|
|
8773
|
+
return Response.json(await resolve4(ref));
|
|
8774
|
+
}
|
|
8673
8775
|
if (p === "/resources") return Response.json(hostResourceSnapshot());
|
|
8674
8776
|
if (p === "/timeline") return Response.json(timelineState(deps.db, Date.now(), readNarratorLog(deps.narratorLog)));
|
|
8675
8777
|
const diffMatch = p.match(/^\/run\/(\d+)\/diff$/);
|
|
@@ -10182,6 +10284,9 @@ function usage() {
|
|
|
10182
10284
|
swarm wake <runId> \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (id \u0438\u0437 swarm ls); \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E \u2192 \u043D\u0430\u0447\u043D\u0451\u0442 \u0441 \u043D\u0443\u043B\u044F
|
|
10183
10285
|
swarm cancel <slug> <\u043F\u0440\u0438\u0447\u0438\u043D\u0430\u2026>
|
|
10184
10286
|
\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430: \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\xBB \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F
|
|
10287
|
+
swarm merge-gate <pr> \u043C\u043E\u0436\u043D\u043E \u043B\u0438 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u044D\u0442\u043E\u0442 PR (\u0441\u0441\u044B\u043B\u043A\u0430 \u0438\u043B\u0438 \u043D\u043E\u043C\u0435\u0440): \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441
|
|
10288
|
+
\u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430. \u0412\u044B\u0445\u043E\u0434 1 = \u043D\u0435\u043B\u044C\u0437\u044F.
|
|
10289
|
+
\u0422\u043E\u0442 \u0436\u0435 \u043E\u0442\u0432\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 \u0445\u0443\u043A, \u043F\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044B\u0432\u0430\u044E\u0449\u0438\u0439 gh pr merge
|
|
10185
10290
|
swarm questions \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0434\u0440\u043E\u043D\u043E\u0432
|
|
10186
10291
|
swarm answer <qid> <\u0442\u0435\u043A\u0441\u0442\u2026>
|
|
10187
10292
|
\u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430
|
|
@@ -10466,6 +10571,23 @@ if (cmd === "status") {
|
|
|
10466
10571
|
for (const line of repoMapLines(st.repos)) console.log(line);
|
|
10467
10572
|
process.exit(0);
|
|
10468
10573
|
}
|
|
10574
|
+
if (cmd === "merge-gate") {
|
|
10575
|
+
const ref = rawArgs[0];
|
|
10576
|
+
if (!ref) {
|
|
10577
|
+
console.error("\u043D\u0443\u0436\u043D\u043E: swarm merge-gate <\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR|\u043D\u043E\u043C\u0435\u0440>");
|
|
10578
|
+
process.exit(2);
|
|
10579
|
+
}
|
|
10580
|
+
const answer = await daemonMergeGate(ref);
|
|
10581
|
+
if (!answer) {
|
|
10582
|
+
console.error(
|
|
10583
|
+
`\u26D4 \u043C\u0435\u0440\u0436 \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D: \u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT}) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C, \u0447\u0442\u043E \u043F\u043E PR \u0435\u0441\u0442\u044C \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 (swarm start) \u0438\u043B\u0438, \u0435\u0441\u043B\u0438 \u043C\u0435\u0440\u0436\u0438\u0448\u044C \u043E\u0441\u043E\u0437\u043D\u0430\u043D\u043D\u043E \u0440\u0443\u043A\u0430\u043C\u0438, \u0441\u043D\u0438\u043C\u0438 \u0433\u0435\u0439\u0442: SWARM_MERGE_GATE=off`
|
|
10584
|
+
);
|
|
10585
|
+
process.exit(1);
|
|
10586
|
+
}
|
|
10587
|
+
const who = answer.run ? ` (\u043F\u0440\u043E\u0433\u043E\u043D #${answer.run.id} ${answer.run.task_slug}, \u0434\u0440\u043E\u043D ${answer.run.name})` : "";
|
|
10588
|
+
console.log(`${mergeGateLine(answer)}${who}`);
|
|
10589
|
+
process.exit(answer.allowed ? 0 : 1);
|
|
10590
|
+
}
|
|
10469
10591
|
if (cmd === "pause" || cmd === "resume") {
|
|
10470
10592
|
const wantPause = cmd === "pause";
|
|
10471
10593
|
const reason = wantPause ? rawArgs.join(" ").trim() || null : null;
|
package/dist/overmind.js
CHANGED
|
@@ -319,6 +319,32 @@ var init_reviewPhase = __esm({
|
|
|
319
319
|
}
|
|
320
320
|
});
|
|
321
321
|
|
|
322
|
+
// ../swarm-core/src/mergeGate.ts
|
|
323
|
+
function computeMergeGate(input) {
|
|
324
|
+
if (!input.hasRun) {
|
|
325
|
+
return { allowed: true, code: "unknown_pr", reason: "PR \u043D\u0435 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0443 \u0440\u043E\u044F \u2014 \u0433\u0435\u0439\u0442 \u0440\u0435\u0432\u044C\u044E \u043A \u043D\u0435\u043C\u0443 \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F" };
|
|
326
|
+
}
|
|
327
|
+
if (!input.autoReview) {
|
|
328
|
+
return { allowed: true, code: "auto_review_off", reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F" };
|
|
329
|
+
}
|
|
330
|
+
if (input.reviewVerdict === "approve") {
|
|
331
|
+
return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
|
|
332
|
+
}
|
|
333
|
+
if (input.reviewVerdict === "changes") {
|
|
334
|
+
return { allowed: false, code: "changes_requested", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043F\u0440\u0430\u0432\u043A\u0438 (REQUEST CHANGES) \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430" };
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
allowed: false,
|
|
338
|
+
code: "no_verdict",
|
|
339
|
+
reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
var init_mergeGate = __esm({
|
|
343
|
+
"../swarm-core/src/mergeGate.ts"() {
|
|
344
|
+
"use strict";
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
322
348
|
// ../swarm-core/src/status.ts
|
|
323
349
|
function isAwaitingCi(status, stopReason) {
|
|
324
350
|
return status === "paused" && stopReason === AWAITING_CI_REASON;
|
|
@@ -1336,6 +1362,7 @@ var init_src = __esm({
|
|
|
1336
1362
|
init_types();
|
|
1337
1363
|
init_timeline();
|
|
1338
1364
|
init_reviewPhase();
|
|
1365
|
+
init_mergeGate();
|
|
1339
1366
|
init_status();
|
|
1340
1367
|
init_deps();
|
|
1341
1368
|
init_time();
|
|
@@ -7042,6 +7069,27 @@ var SwarmClient = class _SwarmClient {
|
|
|
7042
7069
|
const v = sc.config?.maxDrones;
|
|
7043
7070
|
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
7044
7071
|
}
|
|
7072
|
+
/**
|
|
7073
|
+
* Live АВТОРЕВЬЮ проекта из Taskless (TASWM-355): тот же swarm_repo_config по
|
|
7074
|
+
* repoFullName — Project.swarmConfig.autoReview. true → у проекта перед мержем обязан быть
|
|
7075
|
+
* вердикт дрона-ревьюера, и гейт мержа (mergeGate.ts) не пускает `gh pr merge`, пока
|
|
7076
|
+
* вердикта нет. До этой ручки демон знал только ГЛОБАЛЬНЫЙ env SWARM_AUTO_REVIEW
|
|
7077
|
+
* («поднимать ли ревьюеров на этой машине»), а настройка проекта до него не доезжала —
|
|
7078
|
+
* гейту же нужен ответ про КОНКРЕТНЫЙ проект. null (прод недоступен / repo не смэплен /
|
|
7079
|
+
* поля нет) → ридер (makeAutoReviewReader) возьмёт env, то есть прежнее поведение.
|
|
7080
|
+
*/
|
|
7081
|
+
async repoAutoReview(repoFullName) {
|
|
7082
|
+
let res;
|
|
7083
|
+
try {
|
|
7084
|
+
res = await this.call("swarm_repo_config", { repoFullName });
|
|
7085
|
+
} catch {
|
|
7086
|
+
return null;
|
|
7087
|
+
}
|
|
7088
|
+
if (res?.isError) return null;
|
|
7089
|
+
const sc = res.structuredContent ?? {};
|
|
7090
|
+
const v = sc.config?.autoReview;
|
|
7091
|
+
return typeof v === "boolean" ? v : null;
|
|
7092
|
+
}
|
|
7045
7093
|
/**
|
|
7046
7094
|
* TASWM-291: МАШИНЫ роя владельца (swarm_list_hosts) — чтобы `swarm spawn --host
|
|
7047
7095
|
* <имя>` резолвил человеческое имя в hostId, а `swarm hosts` показывал, какие
|
|
@@ -10911,6 +10959,15 @@ function makeExclusiveReader(opts) {
|
|
|
10911
10959
|
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
10912
10960
|
});
|
|
10913
10961
|
}
|
|
10962
|
+
function makeAutoReviewReader(opts) {
|
|
10963
|
+
return makeRepoValueReader({
|
|
10964
|
+
fetch: opts.fetch,
|
|
10965
|
+
fallback: opts.fallback,
|
|
10966
|
+
valid: (v) => typeof v === "boolean",
|
|
10967
|
+
...opts.ttlMs !== void 0 ? { ttlMs: opts.ttlMs } : {},
|
|
10968
|
+
...opts.now !== void 0 ? { now: opts.now } : {}
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10914
10971
|
function makeDroneCapReader(opts) {
|
|
10915
10972
|
const read = makeRepoValueReader({
|
|
10916
10973
|
fetch: opts.fetch,
|
|
@@ -10926,6 +10983,39 @@ function makeDroneCapReader(opts) {
|
|
|
10926
10983
|
};
|
|
10927
10984
|
}
|
|
10928
10985
|
|
|
10986
|
+
// src/mergeGate.ts
|
|
10987
|
+
init_src();
|
|
10988
|
+
function prNumberOf(ref) {
|
|
10989
|
+
const s = (ref || "").trim();
|
|
10990
|
+
if (!s) return null;
|
|
10991
|
+
const url = s.match(/\/pull\/(\d+)/);
|
|
10992
|
+
if (url) return Number(url[1]);
|
|
10993
|
+
const bare = s.match(/^#?(\d+)$/);
|
|
10994
|
+
return bare ? Number(bare[1]) : null;
|
|
10995
|
+
}
|
|
10996
|
+
function findRunByPr(runs, ref) {
|
|
10997
|
+
const num = prNumberOf(ref);
|
|
10998
|
+
if (num == null) return null;
|
|
10999
|
+
let best = null;
|
|
11000
|
+
for (const r of runs) {
|
|
11001
|
+
if (!r.pr_url || prNumberOf(r.pr_url) !== num) continue;
|
|
11002
|
+
if (!best || r.id > best.id) best = r;
|
|
11003
|
+
}
|
|
11004
|
+
return best;
|
|
11005
|
+
}
|
|
11006
|
+
function makeMergeGateResolver(deps) {
|
|
11007
|
+
return async (ref) => {
|
|
11008
|
+
const run = findRunByPr(deps.listRuns(), ref);
|
|
11009
|
+
if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
|
|
11010
|
+
const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
|
|
11011
|
+
const decision = computeMergeGate({ hasRun: true, autoReview, reviewVerdict: run.review_verdict });
|
|
11012
|
+
return {
|
|
11013
|
+
...decision,
|
|
11014
|
+
run: { id: run.id, task_slug: run.task_slug, name: run.name, pr_url: run.pr_url, review_verdict: run.review_verdict }
|
|
11015
|
+
};
|
|
11016
|
+
};
|
|
11017
|
+
}
|
|
11018
|
+
|
|
10929
11019
|
// src/envGate.ts
|
|
10930
11020
|
import { existsSync as existsSync13 } from "fs";
|
|
10931
11021
|
import { join as join24 } from "path";
|
|
@@ -11364,7 +11454,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
11364
11454
|
// package.json
|
|
11365
11455
|
var package_default = {
|
|
11366
11456
|
name: "@taskless-app/swarm",
|
|
11367
|
-
version: "0.6.
|
|
11457
|
+
version: "0.6.8",
|
|
11368
11458
|
type: "module",
|
|
11369
11459
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11370
11460
|
keywords: [
|
|
@@ -11414,7 +11504,7 @@ var package_default = {
|
|
|
11414
11504
|
execa: "^10.0.0",
|
|
11415
11505
|
zod: "3.25.76"
|
|
11416
11506
|
},
|
|
11417
|
-
gitHead: "
|
|
11507
|
+
gitHead: "c06a3749a3ef7d0c8fcae83fd79b84ba2318859b"
|
|
11418
11508
|
};
|
|
11419
11509
|
|
|
11420
11510
|
// src/version.ts
|
|
@@ -12837,6 +12927,12 @@ function makeHandler(deps) {
|
|
|
12837
12927
|
hostId: deps.selfHost?.hostId ?? null
|
|
12838
12928
|
});
|
|
12839
12929
|
}
|
|
12930
|
+
if (p === "/merge-gate") {
|
|
12931
|
+
const ref = url.searchParams.get("pr") ?? "";
|
|
12932
|
+
if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
|
|
12933
|
+
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({ listRuns: () => deps.db.listRuns(), fallbackAutoReview: SWARM_AUTO_REVIEW });
|
|
12934
|
+
return Response.json(await resolve4(ref));
|
|
12935
|
+
}
|
|
12840
12936
|
if (p === "/resources") return Response.json(hostResourceSnapshot());
|
|
12841
12937
|
if (p === "/timeline") return Response.json(timelineState(deps.db, Date.now(), readNarratorLog(deps.narratorLog)));
|
|
12842
12938
|
const diffMatch = p.match(/^\/run\/(\d+)\/diff$/);
|
|
@@ -16720,6 +16816,7 @@ async function runDaemon(opts) {
|
|
|
16720
16816
|
let envGate;
|
|
16721
16817
|
let exclusiveProject;
|
|
16722
16818
|
let projectDroneCap;
|
|
16819
|
+
let autoReviewProject;
|
|
16723
16820
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
|
|
16724
16821
|
const client = db;
|
|
16725
16822
|
verifyModeReader = makeVerifyModeReader({
|
|
@@ -16766,6 +16863,14 @@ async function runDaemon(opts) {
|
|
|
16766
16863
|
},
|
|
16767
16864
|
ttlMs: 6e4
|
|
16768
16865
|
});
|
|
16866
|
+
autoReviewProject = makeAutoReviewReader({
|
|
16867
|
+
fetch: async (repo) => {
|
|
16868
|
+
const repoFullName = await remoteRepoFullName(repo).catch(() => null);
|
|
16869
|
+
return repoFullName ? client.repoAutoReview(repoFullName) : null;
|
|
16870
|
+
},
|
|
16871
|
+
fallback: SWARM_AUTO_REVIEW,
|
|
16872
|
+
ttlMs: 6e4
|
|
16873
|
+
});
|
|
16769
16874
|
}
|
|
16770
16875
|
let spawnReviewerFn;
|
|
16771
16876
|
let verdictCommentFn;
|
|
@@ -17334,6 +17439,15 @@ async function runDaemon(opts) {
|
|
|
17334
17439
|
// печатает `swarm status`. Без него человек снова гадал бы, какие репозитории машина
|
|
17335
17440
|
// считает своими и в каком дереве работает при двух копиях одного клона.
|
|
17336
17441
|
repoMap: () => repoMapRows(repoMapState),
|
|
17442
|
+
// Гейт мержа (TASWM-355): ручка /merge-gate отвечает по ЖИВОЙ настройке autoReview того
|
|
17443
|
+
// проекта, которому принадлежит PR (ридер выше), а не по глобальному env машины. Именно
|
|
17444
|
+
// этот ответ и превращается в запрет: PreToolUse-хук на `gh pr merge` спрашивает его
|
|
17445
|
+
// через `swarm merge-gate` и отказывает в команде, пока вердикта ревьюера нет.
|
|
17446
|
+
mergeGate: makeMergeGateResolver({
|
|
17447
|
+
listRuns: () => db.listRuns(),
|
|
17448
|
+
...autoReviewProject ? { autoReview: autoReviewProject } : {},
|
|
17449
|
+
fallbackAutoReview: SWARM_AUTO_REVIEW
|
|
17450
|
+
}),
|
|
17337
17451
|
// Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
|
|
17338
17452
|
// narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
|
|
17339
17453
|
control,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"execa": "^10.0.0",
|
|
39
39
|
"zod": "3.25.76"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "c06a3749a3ef7d0c8fcae83fd79b84ba2318859b",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
|
|
44
44
|
"swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",
|