@taskless-app/swarm 0.6.3 → 0.6.5
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 +154 -28
- package/dist/overmind.js +352 -83
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -104,7 +104,13 @@ var init_types = __esm({
|
|
|
104
104
|
// делать нечего». Находки вроде «в дизайн-системе нет иконки микрофона» раньше либо
|
|
105
105
|
// превращались в такой дорогой вопрос, либо тонули в отчёте о сдаче (дрон LAMPYRIS сдал
|
|
106
106
|
// заглушку «Мик» текстом на кнопке — уехало в прод). note уезжает оператору, дрон работает дальше.
|
|
107
|
-
"note"
|
|
107
|
+
"note",
|
|
108
|
+
// Зависимость прогона снята НЕуспешным финалом блокера (TASWM-361): тот остановлен/упал/
|
|
109
|
+
// переигран, успешного финала не будет — ждать больше нечего. Пишется в ленту ЖДАВШЕГО
|
|
110
|
+
// прогона перед самым спавном: «поехал молча» означало бы, что причина запуска исчезла из
|
|
111
|
+
// истории, и следующий разбор «почему он стартовал поверх незакрытого блокера» упёрся бы в
|
|
112
|
+
// пустоту. Успешное снятие (блокер смержен) события не порождает — это штатный ход.
|
|
113
|
+
"dep_cleared"
|
|
108
114
|
];
|
|
109
115
|
MILESTONE_KINDS = [
|
|
110
116
|
"spawned",
|
|
@@ -125,8 +131,10 @@ var init_types = __esm({
|
|
|
125
131
|
"storm_dead",
|
|
126
132
|
"anomaly",
|
|
127
133
|
// надзор overmind (TASWM-194) — та же нотификация, что улетела в инбокс, вехой в Истории
|
|
128
|
-
"note"
|
|
134
|
+
"note",
|
|
129
135
|
// заметка дрона «замечено по пути» (TASWM-279) — веха, иначе находка тонет в ленте step'ов
|
|
136
|
+
"dep_cleared"
|
|
137
|
+
// зависимость снята неуспешным финалом блокера (TASWM-361) — решение роя, веха как conflict
|
|
130
138
|
];
|
|
131
139
|
MILESTONE_KIND_SET = new Set(MILESTONE_KINDS);
|
|
132
140
|
}
|
|
@@ -355,6 +363,32 @@ var init_status = __esm({
|
|
|
355
363
|
}
|
|
356
364
|
});
|
|
357
365
|
|
|
366
|
+
// ../swarm-core/src/deps.ts
|
|
367
|
+
function judgeDep(runs) {
|
|
368
|
+
const real = runs.filter((r) => !r.service);
|
|
369
|
+
if (real.length === 0) return { ...PENDING };
|
|
370
|
+
if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
|
|
371
|
+
if (real.some((r) => r.status === "done")) {
|
|
372
|
+
return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
373
|
+
}
|
|
374
|
+
const last = real[real.length - 1];
|
|
375
|
+
return {
|
|
376
|
+
outcome: "exhausted",
|
|
377
|
+
met: true,
|
|
378
|
+
status: last.status,
|
|
379
|
+
name: last.name ?? null,
|
|
380
|
+
stopReason: last.stopReason ?? null
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
var PENDING;
|
|
384
|
+
var init_deps = __esm({
|
|
385
|
+
"../swarm-core/src/deps.ts"() {
|
|
386
|
+
"use strict";
|
|
387
|
+
init_status();
|
|
388
|
+
PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
358
392
|
// ../swarm-core/src/time.ts
|
|
359
393
|
function stamp(d = /* @__PURE__ */ new Date()) {
|
|
360
394
|
return d.toISOString().slice(0, 19).replace("T", " ");
|
|
@@ -1061,6 +1095,7 @@ var init_src = __esm({
|
|
|
1061
1095
|
init_timeline();
|
|
1062
1096
|
init_reviewPhase();
|
|
1063
1097
|
init_status();
|
|
1098
|
+
init_deps();
|
|
1064
1099
|
init_time();
|
|
1065
1100
|
init_windows();
|
|
1066
1101
|
init_slug();
|
|
@@ -1849,7 +1884,7 @@ var init_package = __esm({
|
|
|
1849
1884
|
"package.json"() {
|
|
1850
1885
|
package_default = {
|
|
1851
1886
|
name: "@taskless-app/swarm",
|
|
1852
|
-
version: "0.6.
|
|
1887
|
+
version: "0.6.5",
|
|
1853
1888
|
type: "module",
|
|
1854
1889
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
1855
1890
|
keywords: [
|
|
@@ -1899,7 +1934,7 @@ var init_package = __esm({
|
|
|
1899
1934
|
execa: "^10.0.0",
|
|
1900
1935
|
zod: "3.25.76"
|
|
1901
1936
|
},
|
|
1902
|
-
gitHead: "
|
|
1937
|
+
gitHead: "76ff5edd816a2b9dd7f10db62c8fde89eec7771f"
|
|
1903
1938
|
};
|
|
1904
1939
|
}
|
|
1905
1940
|
});
|
|
@@ -2688,18 +2723,40 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2688
2723
|
return rows.map((r) => r.area);
|
|
2689
2724
|
}
|
|
2690
2725
|
/**
|
|
2691
|
-
* Из depends_on прогона — слаги зависимостей, ещё НЕ
|
|
2692
|
-
*
|
|
2693
|
-
*
|
|
2694
|
-
*
|
|
2695
|
-
*
|
|
2696
|
-
*
|
|
2726
|
+
* Из depends_on прогона — слаги зависимостей, ещё НЕ исчерпанных. Пусто → все выполнены,
|
|
2727
|
+
* дрона можно спавнить. Явная зависимость (spawn --after) отдельна от area-lock: area
|
|
2728
|
+
* освобождается на review (до мержа), а тут ждём финала блокера, чтобы следующий дрон
|
|
2729
|
+
* стартовал от его кода.
|
|
2730
|
+
*
|
|
2731
|
+
* TASWM-361: критерий — «живых прогонов по слагу не осталось», а не «есть done». Раньше
|
|
2732
|
+
* зависимость снимал ТОЛЬКО успех, и остановленный оператором блокер держал ждущих вечно
|
|
2733
|
+
* (VESPA и TYPHON — сутки очереди при пустом рое). Само правило и его обоснование —
|
|
2734
|
+
* `judgeDep` в swarm-core/deps.ts (его же читают зеркало прода и прод-снапшот).
|
|
2735
|
+
*
|
|
2736
|
+
* Несуществующий/зациклённый слаг прогонов не имеет вовсе → `pending` → прогон ждёт, как и
|
|
2737
|
+
* раньше, но очередь не виснет (гейт — пропуск в цикле, не блокировка).
|
|
2697
2738
|
*/
|
|
2698
2739
|
unmetDeps(depends_on) {
|
|
2740
|
+
return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* TASWM-361: вердикт по каждой зависимости — не только «держит ли», но и КАК закрылась
|
|
2744
|
+
* (успехом блокера или его неуспешным финалом). Разница нужна overmind: снятие по
|
|
2745
|
+
* неуспеху оставляет след в ленге ждавшего прогона, штатное — нет.
|
|
2746
|
+
*/
|
|
2747
|
+
depVerdicts(depends_on) {
|
|
2699
2748
|
if (!depends_on || depends_on.length === 0) return [];
|
|
2700
|
-
const
|
|
2701
|
-
const
|
|
2702
|
-
|
|
2749
|
+
const slugs = [...new Set(depends_on)];
|
|
2750
|
+
const rows = this.db.prepare(
|
|
2751
|
+
`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`
|
|
2752
|
+
).all(...slugs);
|
|
2753
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
2754
|
+
for (const r of rows) {
|
|
2755
|
+
const list = bySlug.get(r.task_slug) ?? [];
|
|
2756
|
+
list.push({ status: r.status, name: r.name, stopReason: r.stop_reason });
|
|
2757
|
+
bySlug.set(r.task_slug, list);
|
|
2758
|
+
}
|
|
2759
|
+
return depends_on.map((slug) => ({ slug, ...judgeDep(bySlug.get(slug) ?? []) }));
|
|
2703
2760
|
}
|
|
2704
2761
|
/** Все репо, засветившиеся в прогонах — whitelist для валидации spawn из веба. */
|
|
2705
2762
|
distinctRepos() {
|
|
@@ -4184,10 +4241,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
4184
4241
|
// редко — без этого лог был бы простынёй из одной и той же строки, и в ней потонуло бы
|
|
4185
4242
|
// всё остальное. Меняется набор (эпик закрыли / блокер прошли) → печатаем снова.
|
|
4186
4243
|
lastClaimSkipLog = null;
|
|
4187
|
-
// Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг →
|
|
4188
|
-
//
|
|
4189
|
-
//
|
|
4190
|
-
|
|
4244
|
+
// Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг → вердикт по ВСЕМ его прогонам.
|
|
4245
|
+
// `done` монотонен (не откатывается) → положительный ответ не протухает и больше не
|
|
4246
|
+
// спрашивается; прочие вердикты переспрашиваются по TTL — «исчерпана неуспешно»
|
|
4247
|
+
// (TASWM-361) монотонностью НЕ обладает: оператор вправе переигрть остановленный блокер
|
|
4248
|
+
// новым прогоном того же слага, и тогда зависимость снова становится ожиданием.
|
|
4249
|
+
// Живёт в памяти: рестарт демона просто переспросит прод.
|
|
4250
|
+
depsProd = /* @__PURE__ */ new Map();
|
|
4191
4251
|
depsAskedAt = /* @__PURE__ */ new Map();
|
|
4192
4252
|
static DEPS_TTL_MS = 6e4;
|
|
4193
4253
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
@@ -4614,12 +4674,36 @@ var SwarmClient = class _SwarmClient {
|
|
|
4614
4674
|
return [...seen];
|
|
4615
4675
|
}
|
|
4616
4676
|
unmetDeps(depends_on) {
|
|
4677
|
+
return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
|
|
4678
|
+
}
|
|
4679
|
+
/**
|
|
4680
|
+
* TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
|
|
4681
|
+
*
|
|
4682
|
+
* АСИММЕТРИЯ ЗЕРКАЛА И ПРОДА, и почему она здесь намеренная. Зеркало — ЧАСТИЧНАЯ выборка
|
|
4683
|
+
* (только прогоны этой машины, только окно гидрации), и по частичной выборке два вывода
|
|
4684
|
+
* стоят разного:
|
|
4685
|
+
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он не
|
|
4686
|
+
* портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
4687
|
+
* • «живых прогонов слага не осталось» — вывод от ОТСУТСТВИЯ: невидимый зеркалу прогон
|
|
4688
|
+
* на соседней машине как раз и может быть живым. Поэтому «исчерпана неуспешно»
|
|
4689
|
+
* принимаем ТОЛЬКО из ответа прода (refreshDeps) — он видит всех.
|
|
4690
|
+
* Прод недоступен → вердикта нет → прогон честно ждёт следующего окна: лишняя минута
|
|
4691
|
+
* очереди дешевле дрона, поднятого поверх работающего блокера.
|
|
4692
|
+
*/
|
|
4693
|
+
depVerdicts(depends_on) {
|
|
4617
4694
|
if (!depends_on || depends_on.length === 0) return [];
|
|
4618
|
-
const
|
|
4695
|
+
const doneInMirror = /* @__PURE__ */ new Set();
|
|
4619
4696
|
for (const r of this.runs.values()) {
|
|
4620
|
-
if (r.status === "done" && r.resolves_run_id == null)
|
|
4697
|
+
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null) doneInMirror.add(r.task_slug);
|
|
4621
4698
|
}
|
|
4622
|
-
return depends_on.
|
|
4699
|
+
return depends_on.map((slug) => {
|
|
4700
|
+
if (doneInMirror.has(slug)) {
|
|
4701
|
+
return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
4702
|
+
}
|
|
4703
|
+
const fromProd = this.depsProd.get(slug);
|
|
4704
|
+
if (fromProd) return { slug, ...fromProd };
|
|
4705
|
+
return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
4706
|
+
});
|
|
4623
4707
|
}
|
|
4624
4708
|
/**
|
|
4625
4709
|
* TASWM-300: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
|
|
@@ -4630,14 +4714,15 @@ var SwarmClient = class _SwarmClient {
|
|
|
4630
4714
|
* просто позавчера, в зеркало не попадала — и `--after` держал прогон в очереди вечно
|
|
4631
4715
|
* (04.08: снимали руками).
|
|
4632
4716
|
*
|
|
4633
|
-
* Кеш.
|
|
4634
|
-
* положительный ответ помним навсегда и больше не спрашиваем.
|
|
4717
|
+
* Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
|
|
4718
|
+
* положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
|
|
4719
|
+
* «исчерпана неуспешно», TASWM-361 — блокер могут переиграть новым прогоном) переспрашиваем
|
|
4635
4720
|
* не чаще DEPS_TTL_MS: тик идёт раз в ~4с, а ждущих зависимость прогонов может быть
|
|
4636
4721
|
* много — без TTL это был бы поход в прод на каждый слаг каждый тик.
|
|
4637
4722
|
*/
|
|
4638
4723
|
async refreshDeps(slugs, nowMs = Date.now()) {
|
|
4639
4724
|
for (const slug of new Set(slugs)) {
|
|
4640
|
-
if (!slug || this.
|
|
4725
|
+
if (!slug || this.depsProd.get(slug)?.outcome === "done") continue;
|
|
4641
4726
|
const asked = this.depsAskedAt.get(slug);
|
|
4642
4727
|
if (asked !== void 0 && nowMs - asked < _SwarmClient.DEPS_TTL_MS) continue;
|
|
4643
4728
|
this.depsAskedAt.set(slug, nowMs);
|
|
@@ -4650,7 +4735,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
4650
4735
|
}
|
|
4651
4736
|
if (res?.isError) continue;
|
|
4652
4737
|
const runs = res.structuredContent?.runs ?? [];
|
|
4653
|
-
|
|
4738
|
+
const facts = runs.slice().sort((a, b) => String(a.createdAt ?? "").localeCompare(String(b.createdAt ?? ""))).map((r) => ({
|
|
4739
|
+
status: String(r.status ?? ""),
|
|
4740
|
+
// Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
|
|
4741
|
+
// не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
|
|
4742
|
+
// с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
|
|
4743
|
+
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
|
|
4744
|
+
// В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
|
|
4745
|
+
// запасной вариант ради фикстур и старых ответов.
|
|
4746
|
+
name: r.droneName ?? r.name ?? null,
|
|
4747
|
+
stopReason: r.stopReason ?? null
|
|
4748
|
+
}));
|
|
4749
|
+
this.depsProd.set(slug, judgeDep(facts));
|
|
4654
4750
|
}
|
|
4655
4751
|
}
|
|
4656
4752
|
/**
|
|
@@ -5195,6 +5291,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
5195
5291
|
* → null: ридер (makeMaxDronesReader) берёт env-фолбэк.
|
|
5196
5292
|
*/
|
|
5197
5293
|
async ownerMaxDrones() {
|
|
5294
|
+
return (await this.ownerSwarmSettings())?.maxDrones ?? null;
|
|
5295
|
+
}
|
|
5296
|
+
/**
|
|
5297
|
+
* TASWM-364: настройки роя ЦЕЛИКОМ из того же who_am_i — живой кап дронов И модели, на
|
|
5298
|
+
* которых спавнятся дроны и ревьюеры (раздел «Рой» в настройках). Одним вызовом, а не
|
|
5299
|
+
* двумя: читатель у них один (ридер maxDrones раз в TTL), и второй сетевой поход за
|
|
5300
|
+
* соседним полем того же ответа был бы платой ни за что.
|
|
5301
|
+
*
|
|
5302
|
+
* Прод недоступен / ответ без валидного капа → null: вызывающий берёт env-фолбэки
|
|
5303
|
+
* (SWARM_MAX_IN_FLIGHT для капа, SWARM_DRONE_MODEL для модели). Модель без капа отдельно
|
|
5304
|
+
* не отдаём СОЗНАТЕЛЬНО — «ответ прода не разобрался» это одно событие, а не два.
|
|
5305
|
+
*/
|
|
5306
|
+
async ownerSwarmSettings() {
|
|
5198
5307
|
let res;
|
|
5199
5308
|
try {
|
|
5200
5309
|
res = await this.call("who_am_i", {});
|
|
@@ -5204,7 +5313,14 @@ var SwarmClient = class _SwarmClient {
|
|
|
5204
5313
|
if (res?.isError) return null;
|
|
5205
5314
|
const obj = jsonFromTextContent(res);
|
|
5206
5315
|
const n = obj?.maxDrones;
|
|
5207
|
-
|
|
5316
|
+
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
5317
|
+
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
5318
|
+
return {
|
|
5319
|
+
maxDrones: Math.trunc(n),
|
|
5320
|
+
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
5321
|
+
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
5322
|
+
models: { drone: str(obj?.swarmSettings?.droneModel), reviewer: str(obj?.swarmSettings?.reviewerModel) }
|
|
5323
|
+
};
|
|
5208
5324
|
}
|
|
5209
5325
|
/**
|
|
5210
5326
|
* Live verifyMode проекта из Taskless (TASWM-171): читает swarm_repo_config по
|
|
@@ -7642,8 +7758,8 @@ var GK = ['claw','spore','eye','hex'];
|
|
|
7642
7758
|
var ST = ${jsonForScript(statusTuples())};
|
|
7643
7759
|
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 };
|
|
7644
7760
|
var TERM = ${jsonForScript(Object.fromEntries(TERMINAL_STATUSES.map((s) => [s, 1])))};
|
|
7645
|
-
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' };
|
|
7646
|
-
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' };
|
|
7761
|
+
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' };
|
|
7762
|
+
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' };
|
|
7647
7763
|
|
|
7648
7764
|
function E(s){ return String(s==null?"":s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]}); }
|
|
7649
7765
|
function color(st){ return 'var('+(ST[st]||ST.queued)[2]+')'; }
|
|
@@ -8613,6 +8729,16 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8613
8729
|
};
|
|
8614
8730
|
}
|
|
8615
8731
|
|
|
8732
|
+
// src/swarmModels.ts
|
|
8733
|
+
function resolveRunModel(run, swarm, env = process.env) {
|
|
8734
|
+
const pick = (v) => {
|
|
8735
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
8736
|
+
return s.length > 0 ? s : null;
|
|
8737
|
+
};
|
|
8738
|
+
const fromSwarm = run.kind === "reviewer" ? pick(swarm?.reviewer) ?? pick(swarm?.drone) : pick(swarm?.drone);
|
|
8739
|
+
return pick(run.model) ?? fromSwarm ?? pick(env.SWARM_DRONE_MODEL) ?? null;
|
|
8740
|
+
}
|
|
8741
|
+
|
|
8616
8742
|
// src/launcher.ts
|
|
8617
8743
|
init_executors();
|
|
8618
8744
|
var requireFromHere = createRequire(import.meta.url);
|
|
@@ -8750,7 +8876,7 @@ function makeLauncher(cfg) {
|
|
|
8750
8876
|
...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
|
|
8751
8877
|
}
|
|
8752
8878
|
};
|
|
8753
|
-
const model = run
|
|
8879
|
+
const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
|
|
8754
8880
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8755
8881
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8756
8882
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
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();
|
|
@@ -4059,6 +4105,16 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4059
4105
|
const text = res?.content?.find((c) => c.type === "text")?.text;
|
|
4060
4106
|
return typeof text === "string" && text.trim() ? text : null;
|
|
4061
4107
|
}
|
|
4108
|
+
async function reportAcceptanceCheck(input) {
|
|
4109
|
+
const res = await safeCall("report_acceptance_check", {
|
|
4110
|
+
id: input.id,
|
|
4111
|
+
state: input.state,
|
|
4112
|
+
...input.evidence ? { evidence: input.evidence } : {}
|
|
4113
|
+
});
|
|
4114
|
+
if (!res) return null;
|
|
4115
|
+
const text = res.content?.find((c) => c.type === "text")?.text;
|
|
4116
|
+
return typeof text === "string" && text.trim() ? text : null;
|
|
4117
|
+
}
|
|
4062
4118
|
async function resolveId(idOrSlug) {
|
|
4063
4119
|
const cached = idCache.get(idOrSlug);
|
|
4064
4120
|
if (cached) return cached;
|
|
@@ -4127,6 +4183,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4127
4183
|
backlogStatusId,
|
|
4128
4184
|
getDocument,
|
|
4129
4185
|
getTaskContext,
|
|
4186
|
+
reportAcceptanceCheck,
|
|
4130
4187
|
createTask: async (input) => {
|
|
4131
4188
|
if (!input.projectId) {
|
|
4132
4189
|
throw new Error("createTask: projectId \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u2014 \u0430\u0432\u0442\u043E-\u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u043E\u0435\u043C \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E (TASWM-204)");
|
|
@@ -4375,7 +4432,7 @@ var init_taskless = __esm({
|
|
|
4375
4432
|
init_agentToken();
|
|
4376
4433
|
init_workspace();
|
|
4377
4434
|
SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
|
|
4378
|
-
WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload"]);
|
|
4435
|
+
WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
|
|
4379
4436
|
TaskBodyUnavailableError = class extends Error {
|
|
4380
4437
|
constructor(slug, workspaceId) {
|
|
4381
4438
|
super(
|
|
@@ -4868,18 +4925,40 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4868
4925
|
return rows.map((r) => r.area);
|
|
4869
4926
|
}
|
|
4870
4927
|
/**
|
|
4871
|
-
* Из depends_on прогона — слаги зависимостей, ещё НЕ
|
|
4872
|
-
*
|
|
4873
|
-
*
|
|
4874
|
-
*
|
|
4875
|
-
*
|
|
4876
|
-
*
|
|
4928
|
+
* Из depends_on прогона — слаги зависимостей, ещё НЕ исчерпанных. Пусто → все выполнены,
|
|
4929
|
+
* дрона можно спавнить. Явная зависимость (spawn --after) отдельна от area-lock: area
|
|
4930
|
+
* освобождается на review (до мержа), а тут ждём финала блокера, чтобы следующий дрон
|
|
4931
|
+
* стартовал от его кода.
|
|
4932
|
+
*
|
|
4933
|
+
* TASWM-361: критерий — «живых прогонов по слагу не осталось», а не «есть done». Раньше
|
|
4934
|
+
* зависимость снимал ТОЛЬКО успех, и остановленный оператором блокер держал ждущих вечно
|
|
4935
|
+
* (VESPA и TYPHON — сутки очереди при пустом рое). Само правило и его обоснование —
|
|
4936
|
+
* `judgeDep` в swarm-core/deps.ts (его же читают зеркало прода и прод-снапшот).
|
|
4937
|
+
*
|
|
4938
|
+
* Несуществующий/зациклённый слаг прогонов не имеет вовсе → `pending` → прогон ждёт, как и
|
|
4939
|
+
* раньше, но очередь не виснет (гейт — пропуск в цикле, не блокировка).
|
|
4877
4940
|
*/
|
|
4878
4941
|
unmetDeps(depends_on) {
|
|
4942
|
+
return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
|
|
4943
|
+
}
|
|
4944
|
+
/**
|
|
4945
|
+
* TASWM-361: вердикт по каждой зависимости — не только «держит ли», но и КАК закрылась
|
|
4946
|
+
* (успехом блокера или его неуспешным финалом). Разница нужна overmind: снятие по
|
|
4947
|
+
* неуспеху оставляет след в ленге ждавшего прогона, штатное — нет.
|
|
4948
|
+
*/
|
|
4949
|
+
depVerdicts(depends_on) {
|
|
4879
4950
|
if (!depends_on || depends_on.length === 0) return [];
|
|
4880
|
-
const
|
|
4881
|
-
const
|
|
4882
|
-
|
|
4951
|
+
const slugs = [...new Set(depends_on)];
|
|
4952
|
+
const rows = this.db.prepare(
|
|
4953
|
+
`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`
|
|
4954
|
+
).all(...slugs);
|
|
4955
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
4956
|
+
for (const r of rows) {
|
|
4957
|
+
const list = bySlug.get(r.task_slug) ?? [];
|
|
4958
|
+
list.push({ status: r.status, name: r.name, stopReason: r.stop_reason });
|
|
4959
|
+
bySlug.set(r.task_slug, list);
|
|
4960
|
+
}
|
|
4961
|
+
return depends_on.map((slug) => ({ slug, ...judgeDep(bySlug.get(slug) ?? []) }));
|
|
4883
4962
|
}
|
|
4884
4963
|
/** Все репо, засветившиеся в прогонах — whitelist для валидации spawn из веба. */
|
|
4885
4964
|
distinctRepos() {
|
|
@@ -5568,10 +5647,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
5568
5647
|
// редко — без этого лог был бы простынёй из одной и той же строки, и в ней потонуло бы
|
|
5569
5648
|
// всё остальное. Меняется набор (эпик закрыли / блокер прошли) → печатаем снова.
|
|
5570
5649
|
lastClaimSkipLog = null;
|
|
5571
|
-
// Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг →
|
|
5572
|
-
//
|
|
5573
|
-
//
|
|
5574
|
-
|
|
5650
|
+
// Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг → вердикт по ВСЕМ его прогонам.
|
|
5651
|
+
// `done` монотонен (не откатывается) → положительный ответ не протухает и больше не
|
|
5652
|
+
// спрашивается; прочие вердикты переспрашиваются по TTL — «исчерпана неуспешно»
|
|
5653
|
+
// (TASWM-361) монотонностью НЕ обладает: оператор вправе переигрть остановленный блокер
|
|
5654
|
+
// новым прогоном того же слага, и тогда зависимость снова становится ожиданием.
|
|
5655
|
+
// Живёт в памяти: рестарт демона просто переспросит прод.
|
|
5656
|
+
depsProd = /* @__PURE__ */ new Map();
|
|
5575
5657
|
depsAskedAt = /* @__PURE__ */ new Map();
|
|
5576
5658
|
static DEPS_TTL_MS = 6e4;
|
|
5577
5659
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
@@ -5998,12 +6080,36 @@ var SwarmClient = class _SwarmClient {
|
|
|
5998
6080
|
return [...seen];
|
|
5999
6081
|
}
|
|
6000
6082
|
unmetDeps(depends_on) {
|
|
6083
|
+
return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
|
|
6084
|
+
}
|
|
6085
|
+
/**
|
|
6086
|
+
* TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
|
|
6087
|
+
*
|
|
6088
|
+
* АСИММЕТРИЯ ЗЕРКАЛА И ПРОДА, и почему она здесь намеренная. Зеркало — ЧАСТИЧНАЯ выборка
|
|
6089
|
+
* (только прогоны этой машины, только окно гидрации), и по частичной выборке два вывода
|
|
6090
|
+
* стоят разного:
|
|
6091
|
+
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он не
|
|
6092
|
+
* портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
6093
|
+
* • «живых прогонов слага не осталось» — вывод от ОТСУТСТВИЯ: невидимый зеркалу прогон
|
|
6094
|
+
* на соседней машине как раз и может быть живым. Поэтому «исчерпана неуспешно»
|
|
6095
|
+
* принимаем ТОЛЬКО из ответа прода (refreshDeps) — он видит всех.
|
|
6096
|
+
* Прод недоступен → вердикта нет → прогон честно ждёт следующего окна: лишняя минута
|
|
6097
|
+
* очереди дешевле дрона, поднятого поверх работающего блокера.
|
|
6098
|
+
*/
|
|
6099
|
+
depVerdicts(depends_on) {
|
|
6001
6100
|
if (!depends_on || depends_on.length === 0) return [];
|
|
6002
|
-
const
|
|
6101
|
+
const doneInMirror = /* @__PURE__ */ new Set();
|
|
6003
6102
|
for (const r of this.runs.values()) {
|
|
6004
|
-
if (r.status === "done" && r.resolves_run_id == null)
|
|
6103
|
+
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null) doneInMirror.add(r.task_slug);
|
|
6005
6104
|
}
|
|
6006
|
-
return depends_on.
|
|
6105
|
+
return depends_on.map((slug) => {
|
|
6106
|
+
if (doneInMirror.has(slug)) {
|
|
6107
|
+
return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
6108
|
+
}
|
|
6109
|
+
const fromProd = this.depsProd.get(slug);
|
|
6110
|
+
if (fromProd) return { slug, ...fromProd };
|
|
6111
|
+
return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
6112
|
+
});
|
|
6007
6113
|
}
|
|
6008
6114
|
/**
|
|
6009
6115
|
* TASWM-300: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
|
|
@@ -6014,14 +6120,15 @@ var SwarmClient = class _SwarmClient {
|
|
|
6014
6120
|
* просто позавчера, в зеркало не попадала — и `--after` держал прогон в очереди вечно
|
|
6015
6121
|
* (04.08: снимали руками).
|
|
6016
6122
|
*
|
|
6017
|
-
* Кеш.
|
|
6018
|
-
* положительный ответ помним навсегда и больше не спрашиваем.
|
|
6123
|
+
* Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
|
|
6124
|
+
* положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
|
|
6125
|
+
* «исчерпана неуспешно», TASWM-361 — блокер могут переиграть новым прогоном) переспрашиваем
|
|
6019
6126
|
* не чаще DEPS_TTL_MS: тик идёт раз в ~4с, а ждущих зависимость прогонов может быть
|
|
6020
6127
|
* много — без TTL это был бы поход в прод на каждый слаг каждый тик.
|
|
6021
6128
|
*/
|
|
6022
6129
|
async refreshDeps(slugs, nowMs = Date.now()) {
|
|
6023
6130
|
for (const slug of new Set(slugs)) {
|
|
6024
|
-
if (!slug || this.
|
|
6131
|
+
if (!slug || this.depsProd.get(slug)?.outcome === "done") continue;
|
|
6025
6132
|
const asked = this.depsAskedAt.get(slug);
|
|
6026
6133
|
if (asked !== void 0 && nowMs - asked < _SwarmClient.DEPS_TTL_MS) continue;
|
|
6027
6134
|
this.depsAskedAt.set(slug, nowMs);
|
|
@@ -6034,7 +6141,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
6034
6141
|
}
|
|
6035
6142
|
if (res?.isError) continue;
|
|
6036
6143
|
const runs = res.structuredContent?.runs ?? [];
|
|
6037
|
-
|
|
6144
|
+
const facts = runs.slice().sort((a, b) => String(a.createdAt ?? "").localeCompare(String(b.createdAt ?? ""))).map((r) => ({
|
|
6145
|
+
status: String(r.status ?? ""),
|
|
6146
|
+
// Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
|
|
6147
|
+
// не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
|
|
6148
|
+
// с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
|
|
6149
|
+
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
|
|
6150
|
+
// В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
|
|
6151
|
+
// запасной вариант ради фикстур и старых ответов.
|
|
6152
|
+
name: r.droneName ?? r.name ?? null,
|
|
6153
|
+
stopReason: r.stopReason ?? null
|
|
6154
|
+
}));
|
|
6155
|
+
this.depsProd.set(slug, judgeDep(facts));
|
|
6038
6156
|
}
|
|
6039
6157
|
}
|
|
6040
6158
|
/**
|
|
@@ -6579,6 +6697,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
6579
6697
|
* → null: ридер (makeMaxDronesReader) берёт env-фолбэк.
|
|
6580
6698
|
*/
|
|
6581
6699
|
async ownerMaxDrones() {
|
|
6700
|
+
return (await this.ownerSwarmSettings())?.maxDrones ?? null;
|
|
6701
|
+
}
|
|
6702
|
+
/**
|
|
6703
|
+
* TASWM-364: настройки роя ЦЕЛИКОМ из того же who_am_i — живой кап дронов И модели, на
|
|
6704
|
+
* которых спавнятся дроны и ревьюеры (раздел «Рой» в настройках). Одним вызовом, а не
|
|
6705
|
+
* двумя: читатель у них один (ридер maxDrones раз в TTL), и второй сетевой поход за
|
|
6706
|
+
* соседним полем того же ответа был бы платой ни за что.
|
|
6707
|
+
*
|
|
6708
|
+
* Прод недоступен / ответ без валидного капа → null: вызывающий берёт env-фолбэки
|
|
6709
|
+
* (SWARM_MAX_IN_FLIGHT для капа, SWARM_DRONE_MODEL для модели). Модель без капа отдельно
|
|
6710
|
+
* не отдаём СОЗНАТЕЛЬНО — «ответ прода не разобрался» это одно событие, а не два.
|
|
6711
|
+
*/
|
|
6712
|
+
async ownerSwarmSettings() {
|
|
6582
6713
|
let res;
|
|
6583
6714
|
try {
|
|
6584
6715
|
res = await this.call("who_am_i", {});
|
|
@@ -6588,7 +6719,14 @@ var SwarmClient = class _SwarmClient {
|
|
|
6588
6719
|
if (res?.isError) return null;
|
|
6589
6720
|
const obj = jsonFromTextContent(res);
|
|
6590
6721
|
const n = obj?.maxDrones;
|
|
6591
|
-
|
|
6722
|
+
if (!(typeof n === "number" && Number.isFinite(n) && n > 0)) return null;
|
|
6723
|
+
const str2 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
6724
|
+
return {
|
|
6725
|
+
maxDrones: Math.trunc(n),
|
|
6726
|
+
// Старый прод (до TASWM-364) поля swarmSettings не отдаёт вовсе — обе модели null,
|
|
6727
|
+
// то есть прежнее поведение: env машины и дефолт харнесса.
|
|
6728
|
+
models: { drone: str2(obj?.swarmSettings?.droneModel), reviewer: str2(obj?.swarmSettings?.reviewerModel) }
|
|
6729
|
+
};
|
|
6592
6730
|
}
|
|
6593
6731
|
/**
|
|
6594
6732
|
* Live verifyMode проекта из Taskless (TASWM-171): читает swarm_repo_config по
|
|
@@ -8079,6 +8217,16 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
8079
8217
|
};
|
|
8080
8218
|
}
|
|
8081
8219
|
|
|
8220
|
+
// src/swarmModels.ts
|
|
8221
|
+
function resolveRunModel(run, swarm, env = process.env) {
|
|
8222
|
+
const pick2 = (v) => {
|
|
8223
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
8224
|
+
return s.length > 0 ? s : null;
|
|
8225
|
+
};
|
|
8226
|
+
const fromSwarm = run.kind === "reviewer" ? pick2(swarm?.reviewer) ?? pick2(swarm?.drone) : pick2(swarm?.drone);
|
|
8227
|
+
return pick2(run.model) ?? fromSwarm ?? pick2(env.SWARM_DRONE_MODEL) ?? null;
|
|
8228
|
+
}
|
|
8229
|
+
|
|
8082
8230
|
// src/launcher.ts
|
|
8083
8231
|
init_executors();
|
|
8084
8232
|
var requireFromHere = createRequire(import.meta.url);
|
|
@@ -8216,7 +8364,7 @@ function makeLauncher(cfg) {
|
|
|
8216
8364
|
...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
|
|
8217
8365
|
}
|
|
8218
8366
|
};
|
|
8219
|
-
const model = run
|
|
8367
|
+
const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
|
|
8220
8368
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
8221
8369
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
8222
8370
|
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
@@ -8284,7 +8432,7 @@ function accountRunCost(deps, run) {
|
|
|
8284
8432
|
const dir = join13(deps.runLogsDir, String(run.id));
|
|
8285
8433
|
const files = unprocessedLogFiles(dir);
|
|
8286
8434
|
if (files.length === 0) return;
|
|
8287
|
-
const model = run
|
|
8435
|
+
const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
|
|
8288
8436
|
const executor = deps.executor ?? resolveExecutor();
|
|
8289
8437
|
let added = 0;
|
|
8290
8438
|
let tokensIn = 0;
|
|
@@ -9388,6 +9536,41 @@ ${notFoundText}` : notFoundText }],
|
|
|
9388
9536
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
9389
9537
|
}
|
|
9390
9538
|
);
|
|
9539
|
+
server.tool(
|
|
9540
|
+
"report_acceptance_check",
|
|
9541
|
+
"\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043E\u0434\u0438\u043D \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0442\u0432\u043E\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438: passed \u2014 \u043F\u0440\u043E\u0448\u043B\u0430, failed \u2014 \u0443\u043F\u0430\u043B\u0430, pending \u2014 \u0441\u043D\u043E\u0432\u0430 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C. id \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0431\u0435\u0440\u0438 \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB (\u0441\u0442\u0440\u043E\u043A\u0430 \xAB(id: \u2026)\xBB) \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435 \u0438\u043B\u0438 \u0432 read_task. evidence \u2014 \u0414\u041E\u041A\u0410\u0417\u0410\u0422\u0415\u041B\u042C\u0421\u0422\u0412\u041E: \u0441\u0442\u0440\u043E\u043A\u0430 \u0432\u044B\u0432\u043E\u0434\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR/\u0441\u043A\u0440\u0438\u043D, \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B. \u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0441\u0442\u0430\u0432\u044C \u043F\u043E \u0424\u0410\u041A\u0422\u0423 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0430 \u043D\u0435 \u043F\u043E \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044E: \xAB\u0443\u043F\u0430\u043B\u0430\xBB \u0438 \xAB\u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C\xBB \u2014 \u0440\u0430\u0437\u043D\u043E\u0435. \u041F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u0432\u0441\u0435\u043C \u043F\u0443\u043D\u043A\u0442\u0430\u043C \u041F\u0415\u0420\u0415\u0414 done(): \u043D\u0435\u043F\u0440\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043F\u043E \u0442\u0432\u043E\u0435\u043C\u0443 \u043E\u0442\u0447\u0451\u0442\u0443 \u2014 \u0440\u043E\u0432\u043D\u043E \u0442\u0430 \u0440\u0430\u0431\u043E\u0442\u0430, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0441\u043D\u0438\u043C\u0430\u044E\u0442. \u0421\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0430\u043C\u0438 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043D\u0435\u0447\u0435\u043C: \u0438\u0445 \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0449\u0438\u043A.",
|
|
9542
|
+
{
|
|
9543
|
+
id: z.string().describe("id \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB"),
|
|
9544
|
+
state: z.enum(["passed", "failed", "pending"]).describe("passed | failed | pending \u2014 \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430"),
|
|
9545
|
+
evidence: z.string().max(4e3).optional().describe("\u0447\u0435\u043C \u0434\u043E\u043A\u0430\u0437\u0430\u043D\u043E: \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR/\u0441\u043A\u0440\u0438\u043D, \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435")
|
|
9546
|
+
},
|
|
9547
|
+
async ({ id, state, evidence }) => {
|
|
9548
|
+
if (!opts.taskless) {
|
|
9549
|
+
return {
|
|
9550
|
+
content: [
|
|
9551
|
+
{
|
|
9552
|
+
type: "text",
|
|
9553
|
+
text: "Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u0433\u0430\u043B\u043E\u0447\u043A\u0443 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C. \u041E\u0442\u0447\u0438\u0442\u0430\u0439\u0441\u044F \u043F\u043E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u044E \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0432 summary \u043F\u0440\u0438 done()."
|
|
9554
|
+
}
|
|
9555
|
+
],
|
|
9556
|
+
isError: true
|
|
9557
|
+
};
|
|
9558
|
+
}
|
|
9559
|
+
const text = await opts.taskless.reportAcceptanceCheck({ id, state, evidence });
|
|
9560
|
+
if (!text) {
|
|
9561
|
+
return {
|
|
9562
|
+
content: [
|
|
9563
|
+
{
|
|
9564
|
+
type: "text",
|
|
9565
|
+
text: `\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 ${id} \u043D\u0435 \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u0430 (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430): id \u043C\u043E\u0433 \u0431\u044B\u0442\u044C \u0447\u0443\u0436\u0438\u043C \u0438\u043B\u0438 \u0443\u0441\u0442\u0430\u0440\u0435\u0442\u044C \u2014 \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0439 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 read_task. \u041D\u0435 \u043C\u043E\u043B\u0447\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 summary \u043F\u0440\u0438 done().`
|
|
9566
|
+
}
|
|
9567
|
+
],
|
|
9568
|
+
isError: true
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9571
|
+
return { content: [{ type: "text", text }] };
|
|
9572
|
+
}
|
|
9573
|
+
);
|
|
9391
9574
|
server.tool(
|
|
9392
9575
|
"ask",
|
|
9393
9576
|
"\u0417\u0430\u0434\u0430\u0442\u044C \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u043E\u0442\u0432\u0435\u0442\u0430. \u0415\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F \u2014 \u0434\u0430\u0439 options (2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430, \u0441\u0442\u0430\u043D\u0443\u0442 \u043A\u043D\u043E\u043F\u043A\u0430\u043C\u0438); \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0432\u044B\u0431\u0435\u0440\u0435\u0442 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0438\u043B\u0438 \u043D\u0430\u043F\u0438\u0448\u0435\u0442 \u0441\u0432\u043E\u0451. \u041E\u043F\u0443\u0441\u0442\u0438 options \u0434\u043B\u044F \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u0430. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043E\u0442\u0432\u0435\u0442 \u0441\u0442\u0440\u043E\u043A\u043E\u0439. \u0417\u043E\u0432\u0438, \u043A\u043E\u0433\u0434\u0430 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u2014 \u043D\u0435 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439.",
|
|
@@ -9480,13 +9663,31 @@ ${notFoundText}` : notFoundText }],
|
|
|
9480
9663
|
isError: true
|
|
9481
9664
|
};
|
|
9482
9665
|
}
|
|
9666
|
+
const reviewerBySpawn = opts.reviewsRunId != null || opts.runKind === "reviewer";
|
|
9667
|
+
const iAmReviewer = run?.reviews_run_id != null || run?.kind === "reviewer" || reviewerBySpawn;
|
|
9668
|
+
if (iAmReviewer && run?.reviews_run_id == null) {
|
|
9669
|
+
return {
|
|
9670
|
+
content: [
|
|
9671
|
+
{
|
|
9672
|
+
type: "text",
|
|
9673
|
+
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.'
|
|
9674
|
+
}
|
|
9675
|
+
],
|
|
9676
|
+
isError: true
|
|
9677
|
+
};
|
|
9678
|
+
}
|
|
9483
9679
|
if (run?.reviews_run_id != null) {
|
|
9484
9680
|
if (isTerminalStatus(run.status)) {
|
|
9485
9681
|
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" }] };
|
|
9486
9682
|
}
|
|
9487
9683
|
if (verdict !== "approve" && verdict !== "changes") {
|
|
9488
9684
|
return {
|
|
9489
|
-
content: [
|
|
9685
|
+
content: [
|
|
9686
|
+
{
|
|
9687
|
+
type: "text",
|
|
9688
|
+
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.'
|
|
9689
|
+
}
|
|
9690
|
+
],
|
|
9490
9691
|
isError: true
|
|
9491
9692
|
};
|
|
9492
9693
|
}
|
|
@@ -9496,28 +9697,39 @@ ${notFoundText}` : notFoundText }],
|
|
|
9496
9697
|
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}` : ""}`);
|
|
9497
9698
|
const reviewedSlug = reportSlug(reviewed?.task_slug);
|
|
9498
9699
|
let shots = { attachments: [], warning: "" };
|
|
9700
|
+
let commentWarning = "";
|
|
9499
9701
|
if (opts.taskless && reviewedSlug) {
|
|
9500
9702
|
shots = await uploadShots();
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9703
|
+
try {
|
|
9704
|
+
await opts.taskless.addComment(
|
|
9705
|
+
reviewedSlug,
|
|
9706
|
+
`${(summary ?? "").trim()}${shots.warning}`,
|
|
9707
|
+
{
|
|
9708
|
+
swarmKind: "review",
|
|
9709
|
+
swarmDroneName: run.name,
|
|
9710
|
+
swarmVerdict: verdict,
|
|
9711
|
+
// TASWM-304: круг — ПРОВЕРЯЕМОГО прогона (у ревьюер-прогона свой
|
|
9712
|
+
// review_cycle всегда 0), иначе вердикты всех кругов схлопнулись
|
|
9713
|
+
// бы в одну плашку.
|
|
9714
|
+
swarmReviewCycle: reviewed?.review_cycle ?? 0
|
|
9715
|
+
},
|
|
9716
|
+
shots.attachments
|
|
9717
|
+
);
|
|
9718
|
+
} catch (e) {
|
|
9719
|
+
commentWarning = e instanceof Error ? e.message : String(e);
|
|
9720
|
+
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}`);
|
|
9721
|
+
}
|
|
9722
|
+
} else {
|
|
9723
|
+
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";
|
|
9724
|
+
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}`);
|
|
9516
9725
|
}
|
|
9517
9726
|
const trustedPrUrl = reviewed?.pr_url ?? pr_url;
|
|
9518
9727
|
await store.updateRun(null, { status: "review", pr_url: trustedPrUrl });
|
|
9519
9728
|
await store.addEvent(null, "pr_opened", trustedPrUrl);
|
|
9520
|
-
|
|
9729
|
+
const commentNote = commentWarning ? `
|
|
9730
|
+
|
|
9731
|
+
\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"\`.` : "";
|
|
9732
|
+
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}` }] };
|
|
9521
9733
|
}
|
|
9522
9734
|
if (isGithubPrUrl(pr_url)) {
|
|
9523
9735
|
const report = (summary ?? "").trim();
|
|
@@ -9739,7 +9951,14 @@ async function runDroneServer(runId) {
|
|
|
9739
9951
|
);
|
|
9740
9952
|
const expectedTaskSlug = process.env.SWARM_TASK_SLUG || void 0;
|
|
9741
9953
|
const expectedKind = toRunKind(process.env.SWARM_RUN_KIND);
|
|
9742
|
-
const
|
|
9954
|
+
const spawnReviewsRunId = Number(process.env.SWARM_REVIEWS_RUN_ID);
|
|
9955
|
+
const common = {
|
|
9956
|
+
taskless,
|
|
9957
|
+
runKind: expectedKind,
|
|
9958
|
+
prHeadBranch,
|
|
9959
|
+
...expectedTaskSlug ? { expectedTaskSlug } : {},
|
|
9960
|
+
...Number.isInteger(spawnReviewsRunId) && spawnReviewsRunId > 0 ? { reviewsRunId: spawnReviewsRunId } : {}
|
|
9961
|
+
};
|
|
9743
9962
|
const channelUrl = process.env.SWARM_DRONE_CHANNEL_URL;
|
|
9744
9963
|
const channelToken = process.env.SWARM_DRONE_CHANNEL_TOKEN;
|
|
9745
9964
|
if (channelUrl && channelToken) {
|
|
@@ -10894,7 +11113,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
10894
11113
|
// package.json
|
|
10895
11114
|
var package_default = {
|
|
10896
11115
|
name: "@taskless-app/swarm",
|
|
10897
|
-
version: "0.6.
|
|
11116
|
+
version: "0.6.5",
|
|
10898
11117
|
type: "module",
|
|
10899
11118
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
10900
11119
|
keywords: [
|
|
@@ -10944,7 +11163,7 @@ var package_default = {
|
|
|
10944
11163
|
execa: "^10.0.0",
|
|
10945
11164
|
zod: "3.25.76"
|
|
10946
11165
|
},
|
|
10947
|
-
gitHead: "
|
|
11166
|
+
gitHead: "76ff5edd816a2b9dd7f10db62c8fde89eec7771f"
|
|
10948
11167
|
};
|
|
10949
11168
|
|
|
10950
11169
|
// src/version.ts
|
|
@@ -11671,8 +11890,8 @@ var GK = ['claw','spore','eye','hex'];
|
|
|
11671
11890
|
var ST = ${jsonForScript(statusTuples())};
|
|
11672
11891
|
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 };
|
|
11673
11892
|
var TERM = ${jsonForScript(Object.fromEntries(TERMINAL_STATUSES.map((s) => [s, 1])))};
|
|
11674
|
-
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' };
|
|
11675
|
-
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' };
|
|
11893
|
+
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' };
|
|
11894
|
+
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' };
|
|
11676
11895
|
|
|
11677
11896
|
function E(s){ return String(s==null?"":s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]}); }
|
|
11678
11897
|
function color(st){ return 'var('+(ST[st]||ST.queued)[2]+')'; }
|
|
@@ -13434,6 +13653,7 @@ var NEEDS_HUMAN_MARKS = [
|
|
|
13434
13653
|
CHANGES_WAKE_ESCALATION_MARK
|
|
13435
13654
|
];
|
|
13436
13655
|
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";
|
|
13656
|
+
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)";
|
|
13437
13657
|
function verdictCommentMark(createdAt) {
|
|
13438
13658
|
return `\u043A\u043E\u043C\u043C\u0435\u043D\u0442-\u0432\u0435\u0440\u0434\u0438\u043A\u0442@${createdAt}`;
|
|
13439
13659
|
}
|
|
@@ -13764,7 +13984,7 @@ function buildCiReadyNudge(prUrl, over) {
|
|
|
13764
13984
|
head,
|
|
13765
13985
|
"",
|
|
13766
13986
|
"\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.",
|
|
13767
|
-
`\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 \
|
|
13987
|
+
`\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.`,
|
|
13768
13988
|
"\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."
|
|
13769
13989
|
].join("\n");
|
|
13770
13990
|
}
|
|
@@ -13810,7 +14030,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
13810
14030
|
// вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
|
|
13811
14031
|
// ветку заводит spawnDrone из слага) — прежняя формулировка.
|
|
13812
14032
|
`\u0422\u044B \u2014 \u0434\u0440\u043E\u043D \u0440\u043E\u044F. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u044D\u0442\u043E\u043C git-worktree, \u0432\u0435\u0442\u043A\u0430 \`${run.branch || run.task_slug}\`${run.branch && run.branch !== run.task_slug ? " (\u043E\u043D\u0430 \u041D\u0415 \u0440\u0430\u0432\u043D\u0430 \u0441\u043B\u0430\u0433\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E PR, \u043F\u0443\u0448 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043D\u0435\u0451)" : " \u0441\u043E\u0437\u0434\u0430\u043D\u0430 \u043F\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438"}.`,
|
|
13813
|
-
"\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
|
|
14033
|
+
"\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
|
|
13814
14034
|
"\u041F\u0440\u0430\u0432\u0438\u043B\u0430: \u0442\u0440\u043E\u0433\u0430\u0435\u0448\u044C UI \u2014 \u0421\u041D\u0410\u0427\u0410\u041B\u0410 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0441\u043A\u0438\u043B\u043B \u0441 UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u0433\u043E \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (\u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u0435 \u0445\u0430\u0440\u0434\u043A\u043E\u0434\u044C \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u043E\u0435); \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u0441\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438; \u043F\u043E\u0441\u043B\u0435 UI-\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043F\u0440\u0438\u043B\u043E\u0436\u0438 \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 screenshots \u0443 done); \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0447\u0435\u0440\u0435\u0437 gh \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).",
|
|
13815
14035
|
// TASWM-293: живой случай SCE-5 — ревьюер заблокировал PR за отсутствие CI (у проекта
|
|
13816
14036
|
// его нет и не предполагается), автора разбудили «закрывать замечания», замечаний по коду
|
|
@@ -13989,7 +14209,8 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
13989
14209
|
" \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).",
|
|
13990
14210
|
`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.`
|
|
13991
14211
|
],
|
|
13992
|
-
`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
|
|
14212
|
+
`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.`,
|
|
14213
|
+
'\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.'
|
|
13993
14214
|
].join("\n");
|
|
13994
14215
|
}
|
|
13995
14216
|
function droneLabel(run) {
|
|
@@ -14548,6 +14769,19 @@ async function fillSuggestedArea(deps, run) {
|
|
|
14548
14769
|
`\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`
|
|
14549
14770
|
);
|
|
14550
14771
|
}
|
|
14772
|
+
function noteExhaustedDeps(deps, run, verdicts) {
|
|
14773
|
+
const exhausted = verdicts.filter((d) => d.outcome === "exhausted");
|
|
14774
|
+
if (exhausted.length === 0) return;
|
|
14775
|
+
const already = new Set(
|
|
14776
|
+
deps.db.events(run.id).filter((e) => e.kind === "dep_cleared").map((e) => e.text ?? "")
|
|
14777
|
+
);
|
|
14778
|
+
for (const d of exhausted) {
|
|
14779
|
+
const text = depClearedText(d.slug, d);
|
|
14780
|
+
if (already.has(text)) continue;
|
|
14781
|
+
deps.db.addEvent(run.id, "dep_cleared", text);
|
|
14782
|
+
narrate(deps, `${droneLabel(run)} \u2014 ${text}`);
|
|
14783
|
+
}
|
|
14784
|
+
}
|
|
14551
14785
|
async function trySpawnQueuedRun(deps, run) {
|
|
14552
14786
|
if (deps.control?.paused) return "skipped";
|
|
14553
14787
|
const mergedUrl = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
|
|
@@ -14558,11 +14792,13 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
14558
14792
|
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.`);
|
|
14559
14793
|
return "closed";
|
|
14560
14794
|
}
|
|
14561
|
-
|
|
14795
|
+
const depVerdicts = deps.db.depVerdicts(run.depends_on);
|
|
14796
|
+
if (depVerdicts.some((d) => !d.met)) return "skipped";
|
|
14562
14797
|
if (!await envGateAllows(deps, run)) return "skipped";
|
|
14563
14798
|
if (!await projectQuotaGateAllows(deps, run)) return "skipped";
|
|
14564
14799
|
if (!await prodStillAllows(deps, run, ["queued"])) return "skipped";
|
|
14565
14800
|
if (deps.db.takeRun && !await deps.db.takeRun(run.id)) return "skipped";
|
|
14801
|
+
noteExhaustedDeps(deps, run, depVerdicts);
|
|
14566
14802
|
await fillSuggestedArea(deps, run);
|
|
14567
14803
|
try {
|
|
14568
14804
|
await deps.spawn(run);
|
|
@@ -14927,6 +15163,28 @@ async function postScrapedVerdictReport(deps, runId, taskSlug, verdict, droneNam
|
|
|
14927
15163
|
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)}`);
|
|
14928
15164
|
});
|
|
14929
15165
|
}
|
|
15166
|
+
async function salvageVerdictFromPrComment(deps, rv, reviewsRunId, since, ctx) {
|
|
15167
|
+
if (!deps.verdictComment || !rv.pr_url) return false;
|
|
15168
|
+
const found = await deps.verdictComment(rv.pr_url, since).catch(() => null);
|
|
15169
|
+
if (!found) return false;
|
|
15170
|
+
const verdictText = verdictLabel(found.verdict);
|
|
15171
|
+
deps.db.updateRun(reviewsRunId, { review_verdict: found.verdict });
|
|
15172
|
+
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}`);
|
|
15173
|
+
await closeReviewerRun(
|
|
15174
|
+
deps,
|
|
15175
|
+
rv,
|
|
15176
|
+
`\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)`
|
|
15177
|
+
);
|
|
15178
|
+
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.`);
|
|
15179
|
+
const reviewed = deps.db.getRun(reviewsRunId);
|
|
15180
|
+
if (reviewed) {
|
|
15181
|
+
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);
|
|
15182
|
+
if (found.verdict === "changes" && !ctx.stormActive && !deps.db.activeResolverFor(reviewed.id)) {
|
|
15183
|
+
await wakeAuthorOnChanges(deps, reviewed, ctx.now, ctx.droneCap);
|
|
15184
|
+
}
|
|
15185
|
+
}
|
|
15186
|
+
return true;
|
|
15187
|
+
}
|
|
14930
15188
|
function buildEscalationCandidates(db) {
|
|
14931
15189
|
const candidates = [];
|
|
14932
15190
|
for (const run of db.listRuns("review")) {
|
|
@@ -15086,6 +15344,24 @@ async function tickOnce(deps) {
|
|
|
15086
15344
|
continue;
|
|
15087
15345
|
}
|
|
15088
15346
|
if (run.reviews_run_id != null) {
|
|
15347
|
+
const reviewed = deps.db.getRun(run.reviews_run_id);
|
|
15348
|
+
const sameCycle = reviewed != null && (reviewed.review_cycle ?? 0) === (run.review_cycle ?? 0);
|
|
15349
|
+
if (reviewed && sameCycle && reviewed.review_verdict == null) {
|
|
15350
|
+
const salvaged = await salvageVerdictFromPrComment(deps, run, run.reviews_run_id, run.created_at, {
|
|
15351
|
+
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",
|
|
15352
|
+
now,
|
|
15353
|
+
droneCap,
|
|
15354
|
+
stormActive
|
|
15355
|
+
});
|
|
15356
|
+
if (salvaged) continue;
|
|
15357
|
+
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15358
|
+
});
|
|
15359
|
+
deps.db.updateRun(run.id, { status: "failed", stop_reason: REVIEW_NO_VERDICT_REASON });
|
|
15360
|
+
deps.db.addEvent(run.id, "failed", REVIEW_NO_VERDICT_REASON);
|
|
15361
|
+
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`);
|
|
15362
|
+
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.`);
|
|
15363
|
+
continue;
|
|
15364
|
+
}
|
|
15089
15365
|
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15090
15366
|
});
|
|
15091
15367
|
deps.db.updateRun(run.id, { status: "done" });
|
|
@@ -15359,33 +15635,13 @@ async function tickOnce(deps) {
|
|
|
15359
15635
|
}
|
|
15360
15636
|
}
|
|
15361
15637
|
if (!alive && run.status === "running" && run.reviews_run_id != null && run.pr_url && deps.verdictComment) {
|
|
15362
|
-
const
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
`\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}`
|
|
15370
|
-
);
|
|
15371
|
-
if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
|
|
15372
|
-
});
|
|
15373
|
-
deps.db.updateRun(run.id, { status: "done" });
|
|
15374
|
-
deps.db.addEvent(
|
|
15375
|
-
run.id,
|
|
15376
|
-
"done",
|
|
15377
|
-
`\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)`
|
|
15378
|
-
);
|
|
15379
|
-
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.`);
|
|
15380
|
-
const reviewed = deps.db.getRun(run.reviews_run_id);
|
|
15381
|
-
if (reviewed) {
|
|
15382
|
-
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);
|
|
15383
|
-
if (verdict.verdict === "changes" && !stormActive && !deps.db.activeResolverFor(reviewed.id)) {
|
|
15384
|
-
await wakeAuthorOnChanges(deps, reviewed, now, droneCap);
|
|
15385
|
-
}
|
|
15386
|
-
}
|
|
15387
|
-
continue;
|
|
15388
|
-
}
|
|
15638
|
+
const salvaged = await salvageVerdictFromPrComment(deps, run, run.reviews_run_id, run.created_at, {
|
|
15639
|
+
how: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432\u044B\u0448\u0435\u043B, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done()",
|
|
15640
|
+
now,
|
|
15641
|
+
droneCap,
|
|
15642
|
+
stormActive
|
|
15643
|
+
});
|
|
15644
|
+
if (salvaged) continue;
|
|
15389
15645
|
}
|
|
15390
15646
|
if (!alive && run.status === "running" && run.reviews_run_id != null && run.pr_url && deps.ci) {
|
|
15391
15647
|
const parent = deps.db.getRun(run.reviews_run_id);
|
|
@@ -15937,6 +16193,7 @@ async function runDaemon(opts) {
|
|
|
15937
16193
|
}
|
|
15938
16194
|
} : void 0;
|
|
15939
16195
|
let lastDroneCap = null;
|
|
16196
|
+
let lastSwarmModels = null;
|
|
15940
16197
|
let lastOwnCapacity = null;
|
|
15941
16198
|
const hostCommandState = hostCommandStatePath(SWARM_HOME);
|
|
15942
16199
|
let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
|
|
@@ -15949,7 +16206,8 @@ async function runDaemon(opts) {
|
|
|
15949
16206
|
executor: EXECUTOR,
|
|
15950
16207
|
droneIsolation: DRONE_ISOLATION,
|
|
15951
16208
|
channel: droneChannelLaunch,
|
|
15952
|
-
droneCap: () => lastDroneCap
|
|
16209
|
+
droneCap: () => lastDroneCap,
|
|
16210
|
+
swarmModels: () => lastSwarmModels
|
|
15953
16211
|
});
|
|
15954
16212
|
let prepareReader;
|
|
15955
16213
|
const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
|
|
@@ -16085,12 +16343,20 @@ async function runDaemon(opts) {
|
|
|
16085
16343
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
|
|
16086
16344
|
const client = db;
|
|
16087
16345
|
const readMaxDrones = makeMaxDronesReader({
|
|
16088
|
-
|
|
16346
|
+
// TASWM-364: одним чтением берём и кап, и модели роя — это соседние поля ОДНОГО
|
|
16347
|
+
// ответа who_am_i. Модели складываем в снимок для спавна тем же побочным ходом,
|
|
16348
|
+
// что и кап ниже: второго сетевого канала за ними заводить незачем.
|
|
16349
|
+
fetch: async () => {
|
|
16350
|
+
const settings = await client.ownerSwarmSettings();
|
|
16351
|
+
if (settings) lastSwarmModels = settings.models;
|
|
16352
|
+
return settings?.maxDrones ?? null;
|
|
16353
|
+
},
|
|
16089
16354
|
fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
|
|
16090
16355
|
ttlMs: 1e4
|
|
16091
16356
|
});
|
|
16092
16357
|
maxDrones = async () => {
|
|
16093
|
-
const
|
|
16358
|
+
const live = await readMaxDrones();
|
|
16359
|
+
const own = lastOwnCapacity = hostMaxDrones ?? live;
|
|
16094
16360
|
return lastDroneCap = operatorMaxDrones != null ? Math.min(own, operatorMaxDrones) : own;
|
|
16095
16361
|
};
|
|
16096
16362
|
}
|
|
@@ -16287,7 +16553,9 @@ async function runDaemon(opts) {
|
|
|
16287
16553
|
repoHasCi: (run) => repoHasCiWorkflows(run.worktree_path, run.repo || opts.repo),
|
|
16288
16554
|
isAlive: pidAlive,
|
|
16289
16555
|
// Учёт cost/usage (TASWM-188): реальный аккаунтант поверх RUN_LOGS_DIR + db.
|
|
16290
|
-
|
|
16556
|
+
// TASWM-364: swarmModels — ТОТ ЖЕ снимок, что уехал в launcher: цена токенов считается
|
|
16557
|
+
// по модели, на которой прогон реально работал.
|
|
16558
|
+
accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels }),
|
|
16291
16559
|
// Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
|
|
16292
16560
|
// (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
|
|
16293
16561
|
exitOutcome: makeExitClassifier(RUN_LOGS_DIR, EXECUTOR),
|
|
@@ -16824,6 +17092,7 @@ export {
|
|
|
16824
17092
|
REVIEW_CYCLE_ESCALATION_MARK,
|
|
16825
17093
|
REVIEW_CYCLE_LIMIT,
|
|
16826
17094
|
REVIEW_ESCALATION_MARK,
|
|
17095
|
+
REVIEW_NO_VERDICT_REASON,
|
|
16827
17096
|
REVIEW_PR_UNKNOWN_MARK,
|
|
16828
17097
|
REVIEW_SPAWN_LIMIT,
|
|
16829
17098
|
REVIEW_UNRECOVERABLE_MARK,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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": "76ff5edd816a2b9dd7f10db62c8fde89eec7771f",
|
|
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",
|