@taskless-app/swarm 0.6.12 → 0.6.14
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 +137 -31
- package/dist/overmind.js +190 -40
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -452,6 +452,40 @@ var init_mergeGate = __esm({
|
|
|
452
452
|
}
|
|
453
453
|
});
|
|
454
454
|
|
|
455
|
+
// ../swarm-core/src/modelPolicy.ts
|
|
456
|
+
function findProvider(id) {
|
|
457
|
+
const key = typeof id === "string" ? id.trim().toLowerCase() : "";
|
|
458
|
+
return MODEL_PROVIDERS.find((p) => p.id === key) ?? null;
|
|
459
|
+
}
|
|
460
|
+
function parseModelRef(ref) {
|
|
461
|
+
const s = typeof ref === "string" ? ref.trim() : "";
|
|
462
|
+
if (!s) return null;
|
|
463
|
+
const slash = s.indexOf("/");
|
|
464
|
+
if (slash < 0) return { providerId: DEFAULT_PROVIDER_ID, model: s };
|
|
465
|
+
const providerId = s.slice(0, slash).trim().toLowerCase();
|
|
466
|
+
const model = s.slice(slash + 1).trim();
|
|
467
|
+
if (!model) return null;
|
|
468
|
+
return { providerId: providerId || DEFAULT_PROVIDER_ID, model };
|
|
469
|
+
}
|
|
470
|
+
function defaultModelFor(role, providerId = null) {
|
|
471
|
+
const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
|
|
472
|
+
return provider.roles[role];
|
|
473
|
+
}
|
|
474
|
+
var MODEL_PROVIDERS, DEFAULT_PROVIDER_ID;
|
|
475
|
+
var init_modelPolicy = __esm({
|
|
476
|
+
"../swarm-core/src/modelPolicy.ts"() {
|
|
477
|
+
"use strict";
|
|
478
|
+
MODEL_PROVIDERS = [
|
|
479
|
+
{
|
|
480
|
+
id: "anthropic",
|
|
481
|
+
label: "Anthropic",
|
|
482
|
+
roles: { executor: "claude-opus-5", review: "claude-sonnet-5" }
|
|
483
|
+
}
|
|
484
|
+
];
|
|
485
|
+
DEFAULT_PROVIDER_ID = MODEL_PROVIDERS[0].id;
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
455
489
|
// ../swarm-core/src/status.ts
|
|
456
490
|
function statusMeta(status) {
|
|
457
491
|
return STATUS_META[status] ?? FALLBACK;
|
|
@@ -687,6 +721,13 @@ var init_baseBranch = __esm({
|
|
|
687
721
|
});
|
|
688
722
|
|
|
689
723
|
// ../swarm-core/src/hosts.ts
|
|
724
|
+
function hostSignInHint(webUrl) {
|
|
725
|
+
const url = (webUrl ?? "").trim();
|
|
726
|
+
return url ? `\u0420\u043E\u0439 \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D. \u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 ${url} \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0440\u043E\u044F \u0438 \u0432\u043E\u0439\u0434\u0438\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 Taskless` : "\u0420\u043E\u0439 \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D. \u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0443 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0440\u043E\u044F \u0438 \u0432\u043E\u0439\u0434\u0438\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 Taskless";
|
|
727
|
+
}
|
|
728
|
+
function hostNotSignedIn(host) {
|
|
729
|
+
return host.signedIn === false;
|
|
730
|
+
}
|
|
690
731
|
function normalizeHostRef(value) {
|
|
691
732
|
if (value == null) return null;
|
|
692
733
|
const trimmed = value.trim();
|
|
@@ -720,6 +761,10 @@ function groupByHost(items, ref, opts = {}) {
|
|
|
720
761
|
offline: false,
|
|
721
762
|
paused: unknown ? false : info?.paused === true,
|
|
722
763
|
pauseReason: unknown ? null : info?.pauseReason ?? null,
|
|
764
|
+
// Поле ставим, ТОЛЬКО если машина о входе сказала: undefined здесь — «не знаем»,
|
|
765
|
+
// и превращать молчание в отказ значило бы объявить невойденным весь парк машин
|
|
766
|
+
// со старым демоном.
|
|
767
|
+
...unknown || info?.signedIn == null ? {} : { signedIn: info.signedIn },
|
|
723
768
|
runs: []
|
|
724
769
|
};
|
|
725
770
|
byKey.set(key, g);
|
|
@@ -752,6 +797,7 @@ function hostStateText(host, nowMs = Date.now(), staleMs2 = HOST_HEARTBEAT_STALE
|
|
|
752
797
|
const at = Date.parse(host.lastSeenAt);
|
|
753
798
|
if (Number.isNaN(at)) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
|
|
754
799
|
if (!isHostOffline(host.lastSeenAt, nowMs, staleMs2)) {
|
|
800
|
+
if (hostNotSignedIn(host)) return hostSignInHint(host.webUrl);
|
|
755
801
|
if (host.paused) return host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435";
|
|
756
802
|
if (host.storm) return host.stormReason ? `\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 ${host.stormReason}` : "\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 \u043D\u043E\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442";
|
|
757
803
|
return "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
|
|
@@ -873,6 +919,42 @@ var init_cost = __esm({
|
|
|
873
919
|
}
|
|
874
920
|
});
|
|
875
921
|
|
|
922
|
+
// ../swarm-core/src/modelPricing.ts
|
|
923
|
+
function findModelPricing(model, table = MODEL_PRICING) {
|
|
924
|
+
if (!model) return void 0;
|
|
925
|
+
const exact = table[model];
|
|
926
|
+
if (exact) return exact;
|
|
927
|
+
let bestKey = "";
|
|
928
|
+
for (const key of Object.keys(table)) {
|
|
929
|
+
if (model.startsWith(key) && key.length > bestKey.length) bestKey = key;
|
|
930
|
+
}
|
|
931
|
+
return bestKey ? table[bestKey] : void 0;
|
|
932
|
+
}
|
|
933
|
+
var MODEL_PRICING, CACHE_READ_FACTOR, CACHE_WRITE_5M_FACTOR;
|
|
934
|
+
var init_modelPricing = __esm({
|
|
935
|
+
"../swarm-core/src/modelPricing.ts"() {
|
|
936
|
+
"use strict";
|
|
937
|
+
MODEL_PRICING = {
|
|
938
|
+
// Sonnet 5 — 2/10. Цена ПОСТОЯННАЯ: 18.08.2026 владелец подтвердил, что вводной
|
|
939
|
+
// она быть перестала. Даты годности у тарифа тут больше нет и заводить её заново не
|
|
940
|
+
// нужно: тариф со сроком означает, что в календарный день расчёт молча меняет цифру,
|
|
941
|
+
// и «почему прогон вчера стоил иначе» приходится выяснять по коду, а не по таблице.
|
|
942
|
+
// Меняется цена — меняется строка, одним видимым коммитом.
|
|
943
|
+
"claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
|
|
944
|
+
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
945
|
+
"claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
946
|
+
// Прогоны и вызовы на 4.x остаются в истории — строки держим ради их стоимости.
|
|
947
|
+
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
948
|
+
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
949
|
+
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
950
|
+
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
951
|
+
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
952
|
+
};
|
|
953
|
+
CACHE_READ_FACTOR = 0.1;
|
|
954
|
+
CACHE_WRITE_5M_FACTOR = 1.25;
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
|
|
876
958
|
// ../swarm-core/src/runGuards.ts
|
|
877
959
|
function remainingRunBudgetUsd(maxCostUsd, spentUsd) {
|
|
878
960
|
if (!(maxCostUsd > 0)) return null;
|
|
@@ -1316,6 +1398,7 @@ var init_src = __esm({
|
|
|
1316
1398
|
init_reviewPhase();
|
|
1317
1399
|
init_mergeGate();
|
|
1318
1400
|
init_verifyVerdict();
|
|
1401
|
+
init_modelPolicy();
|
|
1319
1402
|
init_status();
|
|
1320
1403
|
init_deps();
|
|
1321
1404
|
init_time();
|
|
@@ -1331,6 +1414,7 @@ var init_src = __esm({
|
|
|
1331
1414
|
init_hostCommands();
|
|
1332
1415
|
init_relief();
|
|
1333
1416
|
init_cost();
|
|
1417
|
+
init_modelPricing();
|
|
1334
1418
|
init_runGuards();
|
|
1335
1419
|
init_codeZones();
|
|
1336
1420
|
init_effort();
|
|
@@ -1428,18 +1512,12 @@ var init_swarmFiles = __esm({
|
|
|
1428
1512
|
});
|
|
1429
1513
|
|
|
1430
1514
|
// src/pricing.ts
|
|
1431
|
-
function
|
|
1432
|
-
const
|
|
1433
|
-
if (!intro) return pricing;
|
|
1434
|
-
const day = at.toISOString().slice(0, 10);
|
|
1435
|
-
return day <= intro.until ? { inputPerMTok: intro.inputPerMTok, outputPerMTok: intro.outputPerMTok } : pricing;
|
|
1436
|
-
}
|
|
1437
|
-
function costFromUsage(usage2, model, table = MODEL_PRICING, at = /* @__PURE__ */ new Date()) {
|
|
1438
|
-
const entry = model ? table[model] : void 0;
|
|
1515
|
+
function costFromUsage(usage2, model, table = MODEL_PRICING) {
|
|
1516
|
+
const entry = findModelPricing(model, table);
|
|
1439
1517
|
if (!entry) {
|
|
1440
1518
|
return { costUsd: 0, warning: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0430: "${model ?? ""}" \u2014 cost \u043D\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D` };
|
|
1441
1519
|
}
|
|
1442
|
-
const pricing =
|
|
1520
|
+
const pricing = entry;
|
|
1443
1521
|
const input = usage2.input_tokens ?? 0;
|
|
1444
1522
|
const output = usage2.output_tokens ?? 0;
|
|
1445
1523
|
const cacheWrite = usage2.cache_creation_input_tokens ?? 0;
|
|
@@ -1447,24 +1525,10 @@ function costFromUsage(usage2, model, table = MODEL_PRICING, at = /* @__PURE__ *
|
|
|
1447
1525
|
const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * CACHE_READ_FACTOR) / 1e6;
|
|
1448
1526
|
return { costUsd };
|
|
1449
1527
|
}
|
|
1450
|
-
var MODEL_PRICING, CACHE_READ_FACTOR, CACHE_WRITE_5M_FACTOR;
|
|
1451
1528
|
var init_pricing = __esm({
|
|
1452
1529
|
"src/pricing.ts"() {
|
|
1453
1530
|
"use strict";
|
|
1454
|
-
|
|
1455
|
-
// Sonnet 5: вводные 2/10 по 31.08.2026 включительно, дальше базовые 3/15.
|
|
1456
|
-
"claude-sonnet-5": {
|
|
1457
|
-
inputPerMTok: 3,
|
|
1458
|
-
outputPerMTok: 15,
|
|
1459
|
-
intro: { inputPerMTok: 2, outputPerMTok: 10, until: "2026-08-31" }
|
|
1460
|
-
},
|
|
1461
|
-
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1462
|
-
// Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
|
|
1463
|
-
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1464
|
-
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
1465
|
-
};
|
|
1466
|
-
CACHE_READ_FACTOR = 0.1;
|
|
1467
|
-
CACHE_WRITE_5M_FACTOR = 1.25;
|
|
1531
|
+
init_src();
|
|
1468
1532
|
}
|
|
1469
1533
|
});
|
|
1470
1534
|
|
|
@@ -2103,7 +2167,7 @@ var init_package = __esm({
|
|
|
2103
2167
|
"package.json"() {
|
|
2104
2168
|
package_default = {
|
|
2105
2169
|
name: "@taskless-app/swarm",
|
|
2106
|
-
version: "0.6.
|
|
2170
|
+
version: "0.6.14",
|
|
2107
2171
|
type: "module",
|
|
2108
2172
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2109
2173
|
keywords: [
|
|
@@ -2153,7 +2217,7 @@ var init_package = __esm({
|
|
|
2153
2217
|
execa: "^10.0.0",
|
|
2154
2218
|
zod: "3.25.76"
|
|
2155
2219
|
},
|
|
2156
|
-
gitHead: "
|
|
2220
|
+
gitHead: "a3b11498dbed5db3642fe4fc83bcd2054efde055"
|
|
2157
2221
|
};
|
|
2158
2222
|
}
|
|
2159
2223
|
});
|
|
@@ -2677,6 +2741,7 @@ function rowToRun(row) {
|
|
|
2677
2741
|
area: row.area ?? null,
|
|
2678
2742
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
2679
2743
|
model: row.model ?? null,
|
|
2744
|
+
review_model: row.review_model ?? null,
|
|
2680
2745
|
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
2681
2746
|
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
2682
2747
|
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
@@ -2754,6 +2819,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
2754
2819
|
area TEXT,
|
|
2755
2820
|
depends_on TEXT,
|
|
2756
2821
|
model TEXT,
|
|
2822
|
+
review_model TEXT,
|
|
2757
2823
|
effort TEXT,
|
|
2758
2824
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
2759
2825
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
@@ -2867,7 +2933,11 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2867
2933
|
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
2868
2934
|
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
2869
2935
|
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
2870
|
-
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
2936
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT",
|
|
2937
|
+
// TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
|
|
2938
|
+
// Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
|
|
2939
|
+
// оттуда, а не ходят за задачей в прод в момент спавна.
|
|
2940
|
+
"ALTER TABLE runs ADD COLUMN review_model TEXT"
|
|
2871
2941
|
]) {
|
|
2872
2942
|
try {
|
|
2873
2943
|
this.db.exec(alter);
|
|
@@ -2887,7 +2957,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2887
2957
|
createRun(input) {
|
|
2888
2958
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
2889
2959
|
const res = this.db.prepare(
|
|
2890
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2960
|
+
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, review_model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2891
2961
|
).run(
|
|
2892
2962
|
input.task_slug,
|
|
2893
2963
|
name,
|
|
@@ -2900,6 +2970,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2900
2970
|
input.area ?? null,
|
|
2901
2971
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
2902
2972
|
input.model ?? null,
|
|
2973
|
+
input.review_model ?? null,
|
|
2903
2974
|
input.effort ?? null,
|
|
2904
2975
|
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
2905
2976
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
@@ -5390,6 +5461,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5390
5461
|
if (patch.status === "running") out.executor = this.executorId;
|
|
5391
5462
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
5392
5463
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
5464
|
+
if ("review_model" in patch) out.reviewModel = patch.review_model ?? null;
|
|
5393
5465
|
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
5394
5466
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
5395
5467
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
@@ -5463,6 +5535,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
5463
5535
|
area: input.area ?? null,
|
|
5464
5536
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
5465
5537
|
model: input.model ?? null,
|
|
5538
|
+
// TASWM-451: чем проверять эту работу. У прогона исполнителя приезжает из задачи
|
|
5539
|
+
// (claim в проде), у локально заведённого ревьюера/верификатора — наследством от
|
|
5540
|
+
// проверяемого прогона, которое ставит spawnReviewer/spawnVerifier.
|
|
5541
|
+
review_model: input.review_model ?? null,
|
|
5466
5542
|
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
5467
5543
|
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
5468
5544
|
effort: input.effort ?? null,
|
|
@@ -5521,6 +5597,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5521
5597
|
status: "queued",
|
|
5522
5598
|
area: d.area,
|
|
5523
5599
|
model: d.model,
|
|
5600
|
+
review_model: d.review_model,
|
|
5601
|
+
// TASWM-451: чем проверять — едет рядом с моделью
|
|
5524
5602
|
effort: d.effort,
|
|
5525
5603
|
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
5526
5604
|
name: d.name,
|
|
@@ -6648,7 +6726,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6648
6726
|
*
|
|
6649
6727
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
6650
6728
|
*/
|
|
6651
|
-
async heartbeat(daemonVersion, pause, capacity, resources, storm) {
|
|
6729
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm, signIn) {
|
|
6652
6730
|
try {
|
|
6653
6731
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
6654
6732
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -6658,6 +6736,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
6658
6736
|
args.paused = !!pause?.paused;
|
|
6659
6737
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
6660
6738
|
}
|
|
6739
|
+
if (signIn) {
|
|
6740
|
+
args.signedIn = signIn.signedIn;
|
|
6741
|
+
if (signIn.webUrl) args.webUrl = signIn.webUrl;
|
|
6742
|
+
}
|
|
6661
6743
|
if (storm !== void 0 && storm !== null) {
|
|
6662
6744
|
args.storm = storm.active;
|
|
6663
6745
|
if (storm.reason) args.stormReason = storm.reason;
|
|
@@ -6889,6 +6971,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
6889
6971
|
// существующая локальная запись НЕ перетирается (поле не в field-copy ниже),
|
|
6890
6972
|
// в отличие от respawns, — авто-ревью v2, TASWM-162.
|
|
6891
6973
|
review_cycle: 0,
|
|
6974
|
+
// TASWM-451: модель ПРОВЕРКИ — прод-поле, заполняется ниже из ответа (как base_branch).
|
|
6975
|
+
review_model: null,
|
|
6892
6976
|
task_title: null,
|
|
6893
6977
|
task_desc: null,
|
|
6894
6978
|
task_url: null,
|
|
@@ -6941,6 +7025,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6941
7025
|
local.area = pr.area ?? null;
|
|
6942
7026
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
6943
7027
|
local.model = pr.model ?? null;
|
|
7028
|
+
local.review_model = pr.reviewModel ?? null;
|
|
6944
7029
|
local.effort = toEffortLevel(pr.effort);
|
|
6945
7030
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
6946
7031
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
@@ -9985,12 +10070,26 @@ function pick(v) {
|
|
|
9985
10070
|
const s = typeof v === "string" ? v.trim() : "";
|
|
9986
10071
|
return s.length > 0 ? s : null;
|
|
9987
10072
|
}
|
|
10073
|
+
function isReviewKind(kind) {
|
|
10074
|
+
return kind === "reviewer" || kind === "verifier";
|
|
10075
|
+
}
|
|
10076
|
+
function modelRoleOf(kind) {
|
|
10077
|
+
return isReviewKind(kind) ? "review" : "executor";
|
|
10078
|
+
}
|
|
9988
10079
|
function forKind(kind, swarm, norm) {
|
|
9989
10080
|
if (!swarm) return null;
|
|
9990
|
-
return kind
|
|
10081
|
+
return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
10082
|
+
}
|
|
10083
|
+
function resolveRunModelRef(run, swarm, env = process.env) {
|
|
10084
|
+
const configured = parseModelRef(
|
|
10085
|
+
pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL)
|
|
10086
|
+
);
|
|
10087
|
+
if (configured) return configured;
|
|
10088
|
+
const providerId = pick(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
|
|
10089
|
+
return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
|
|
9991
10090
|
}
|
|
9992
10091
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
9993
|
-
return
|
|
10092
|
+
return resolveRunModelRef(run, swarm, env).model;
|
|
9994
10093
|
}
|
|
9995
10094
|
function resolveRunEffort(run, swarm, env = process.env) {
|
|
9996
10095
|
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
@@ -11922,6 +12021,13 @@ switch (cmd) {
|
|
|
11922
12021
|
// должен внезапно поднимать дрона другим харнессом.
|
|
11923
12022
|
executor: EXECUTOR,
|
|
11924
12023
|
droneIsolation: DRONE_ISOLATION,
|
|
12024
|
+
// TASWM-450: ПОТОЛОК ТРАТ обязателен и здесь. Демон собирает лаунчер с
|
|
12025
|
+
// `maxCostUsd: () => lastMaxCostUsd`, а этот путь его не передавал вовсе — значит
|
|
12026
|
+
// разбуженный руками прогон уходил БЕЗ `--max-budget-usd` и мог жечь сколько угодно.
|
|
12027
|
+
// Живой настройки роя у CLI нет (демон не отвечает — иначе мы сюда не попали бы),
|
|
12028
|
+
// поэтому источник тот же, что и у остальных ~40 ручек: env машины, а за ним дефолт
|
|
12029
|
+
// ядра. Это ровно то, что делает `launchBudgetUsd(null, …)`.
|
|
12030
|
+
maxCostUsd: () => null,
|
|
11925
12031
|
...DRONE_CHANNEL_ENABLED ? {
|
|
11926
12032
|
channel: {
|
|
11927
12033
|
url: DRONE_CHANNEL_URL,
|
package/dist/overmind.js
CHANGED
|
@@ -425,6 +425,40 @@ var init_mergeGate = __esm({
|
|
|
425
425
|
}
|
|
426
426
|
});
|
|
427
427
|
|
|
428
|
+
// ../swarm-core/src/modelPolicy.ts
|
|
429
|
+
function findProvider(id) {
|
|
430
|
+
const key = typeof id === "string" ? id.trim().toLowerCase() : "";
|
|
431
|
+
return MODEL_PROVIDERS.find((p) => p.id === key) ?? null;
|
|
432
|
+
}
|
|
433
|
+
function parseModelRef(ref) {
|
|
434
|
+
const s = typeof ref === "string" ? ref.trim() : "";
|
|
435
|
+
if (!s) return null;
|
|
436
|
+
const slash = s.indexOf("/");
|
|
437
|
+
if (slash < 0) return { providerId: DEFAULT_PROVIDER_ID, model: s };
|
|
438
|
+
const providerId = s.slice(0, slash).trim().toLowerCase();
|
|
439
|
+
const model = s.slice(slash + 1).trim();
|
|
440
|
+
if (!model) return null;
|
|
441
|
+
return { providerId: providerId || DEFAULT_PROVIDER_ID, model };
|
|
442
|
+
}
|
|
443
|
+
function defaultModelFor(role, providerId = null) {
|
|
444
|
+
const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
|
|
445
|
+
return provider.roles[role];
|
|
446
|
+
}
|
|
447
|
+
var MODEL_PROVIDERS, DEFAULT_PROVIDER_ID;
|
|
448
|
+
var init_modelPolicy = __esm({
|
|
449
|
+
"../swarm-core/src/modelPolicy.ts"() {
|
|
450
|
+
"use strict";
|
|
451
|
+
MODEL_PROVIDERS = [
|
|
452
|
+
{
|
|
453
|
+
id: "anthropic",
|
|
454
|
+
label: "Anthropic",
|
|
455
|
+
roles: { executor: "claude-opus-5", review: "claude-sonnet-5" }
|
|
456
|
+
}
|
|
457
|
+
];
|
|
458
|
+
DEFAULT_PROVIDER_ID = MODEL_PROVIDERS[0].id;
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
428
462
|
// ../swarm-core/src/status.ts
|
|
429
463
|
function isAwaitingCi(status, stopReason) {
|
|
430
464
|
return status === "paused" && stopReason === AWAITING_CI_REASON;
|
|
@@ -989,6 +1023,42 @@ var init_cost = __esm({
|
|
|
989
1023
|
}
|
|
990
1024
|
});
|
|
991
1025
|
|
|
1026
|
+
// ../swarm-core/src/modelPricing.ts
|
|
1027
|
+
function findModelPricing(model, table = MODEL_PRICING) {
|
|
1028
|
+
if (!model) return void 0;
|
|
1029
|
+
const exact = table[model];
|
|
1030
|
+
if (exact) return exact;
|
|
1031
|
+
let bestKey = "";
|
|
1032
|
+
for (const key of Object.keys(table)) {
|
|
1033
|
+
if (model.startsWith(key) && key.length > bestKey.length) bestKey = key;
|
|
1034
|
+
}
|
|
1035
|
+
return bestKey ? table[bestKey] : void 0;
|
|
1036
|
+
}
|
|
1037
|
+
var MODEL_PRICING, CACHE_READ_FACTOR, CACHE_WRITE_5M_FACTOR;
|
|
1038
|
+
var init_modelPricing = __esm({
|
|
1039
|
+
"../swarm-core/src/modelPricing.ts"() {
|
|
1040
|
+
"use strict";
|
|
1041
|
+
MODEL_PRICING = {
|
|
1042
|
+
// Sonnet 5 — 2/10. Цена ПОСТОЯННАЯ: 18.08.2026 владелец подтвердил, что вводной
|
|
1043
|
+
// она быть перестала. Даты годности у тарифа тут больше нет и заводить её заново не
|
|
1044
|
+
// нужно: тариф со сроком означает, что в календарный день расчёт молча меняет цифру,
|
|
1045
|
+
// и «почему прогон вчера стоил иначе» приходится выяснять по коду, а не по таблице.
|
|
1046
|
+
// Меняется цена — меняется строка, одним видимым коммитом.
|
|
1047
|
+
"claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
|
|
1048
|
+
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1049
|
+
"claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
1050
|
+
// Прогоны и вызовы на 4.x остаются в истории — строки держим ради их стоимости.
|
|
1051
|
+
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1052
|
+
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1053
|
+
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1054
|
+
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
1055
|
+
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
1056
|
+
};
|
|
1057
|
+
CACHE_READ_FACTOR = 0.1;
|
|
1058
|
+
CACHE_WRITE_5M_FACTOR = 1.25;
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
|
|
992
1062
|
// ../swarm-core/src/runGuards.ts
|
|
993
1063
|
function remainingRunBudgetUsd(maxCostUsd, spentUsd) {
|
|
994
1064
|
if (!(maxCostUsd > 0)) return null;
|
|
@@ -1448,6 +1518,7 @@ var init_src = __esm({
|
|
|
1448
1518
|
init_reviewPhase();
|
|
1449
1519
|
init_mergeGate();
|
|
1450
1520
|
init_verifyVerdict();
|
|
1521
|
+
init_modelPolicy();
|
|
1451
1522
|
init_status();
|
|
1452
1523
|
init_deps();
|
|
1453
1524
|
init_time();
|
|
@@ -1463,6 +1534,7 @@ var init_src = __esm({
|
|
|
1463
1534
|
init_hostCommands();
|
|
1464
1535
|
init_relief();
|
|
1465
1536
|
init_cost();
|
|
1537
|
+
init_modelPricing();
|
|
1466
1538
|
init_runGuards();
|
|
1467
1539
|
init_codeZones();
|
|
1468
1540
|
init_effort();
|
|
@@ -1597,18 +1669,12 @@ var init_swarmFiles = __esm({
|
|
|
1597
1669
|
});
|
|
1598
1670
|
|
|
1599
1671
|
// src/pricing.ts
|
|
1600
|
-
function
|
|
1601
|
-
const
|
|
1602
|
-
if (!intro) return pricing;
|
|
1603
|
-
const day = at.toISOString().slice(0, 10);
|
|
1604
|
-
return day <= intro.until ? { inputPerMTok: intro.inputPerMTok, outputPerMTok: intro.outputPerMTok } : pricing;
|
|
1605
|
-
}
|
|
1606
|
-
function costFromUsage(usage, model, table = MODEL_PRICING, at = /* @__PURE__ */ new Date()) {
|
|
1607
|
-
const entry = model ? table[model] : void 0;
|
|
1672
|
+
function costFromUsage(usage, model, table = MODEL_PRICING) {
|
|
1673
|
+
const entry = findModelPricing(model, table);
|
|
1608
1674
|
if (!entry) {
|
|
1609
1675
|
return { costUsd: 0, warning: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0430: "${model ?? ""}" \u2014 cost \u043D\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D` };
|
|
1610
1676
|
}
|
|
1611
|
-
const pricing =
|
|
1677
|
+
const pricing = entry;
|
|
1612
1678
|
const input = usage.input_tokens ?? 0;
|
|
1613
1679
|
const output = usage.output_tokens ?? 0;
|
|
1614
1680
|
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
@@ -1616,24 +1682,10 @@ function costFromUsage(usage, model, table = MODEL_PRICING, at = /* @__PURE__ */
|
|
|
1616
1682
|
const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * CACHE_READ_FACTOR) / 1e6;
|
|
1617
1683
|
return { costUsd };
|
|
1618
1684
|
}
|
|
1619
|
-
var MODEL_PRICING, CACHE_READ_FACTOR, CACHE_WRITE_5M_FACTOR;
|
|
1620
1685
|
var init_pricing = __esm({
|
|
1621
1686
|
"src/pricing.ts"() {
|
|
1622
1687
|
"use strict";
|
|
1623
|
-
|
|
1624
|
-
// Sonnet 5: вводные 2/10 по 31.08.2026 включительно, дальше базовые 3/15.
|
|
1625
|
-
"claude-sonnet-5": {
|
|
1626
|
-
inputPerMTok: 3,
|
|
1627
|
-
outputPerMTok: 15,
|
|
1628
|
-
intro: { inputPerMTok: 2, outputPerMTok: 10, until: "2026-08-31" }
|
|
1629
|
-
},
|
|
1630
|
-
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1631
|
-
// Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
|
|
1632
|
-
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1633
|
-
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
1634
|
-
};
|
|
1635
|
-
CACHE_READ_FACTOR = 0.1;
|
|
1636
|
-
CACHE_WRITE_5M_FACTOR = 1.25;
|
|
1688
|
+
init_src();
|
|
1637
1689
|
}
|
|
1638
1690
|
});
|
|
1639
1691
|
|
|
@@ -3500,6 +3552,7 @@ function bad(status, error) {
|
|
|
3500
3552
|
function scopeOf(store, run) {
|
|
3501
3553
|
const ids = /* @__PURE__ */ new Set([run.id]);
|
|
3502
3554
|
if (run.reviews_run_id != null) ids.add(run.reviews_run_id);
|
|
3555
|
+
if (run.verifies_run_id != null) ids.add(run.verifies_run_id);
|
|
3503
3556
|
if (run.kind === "hero") for (const r of store.squadRuns(run.id)) ids.add(r.id);
|
|
3504
3557
|
return ids;
|
|
3505
3558
|
}
|
|
@@ -3512,6 +3565,10 @@ function invalidPatchValue(field, v) {
|
|
|
3512
3565
|
return typeof v === "string" && v in STATUS_META ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441: ${String(v)}`;
|
|
3513
3566
|
case "review_verdict":
|
|
3514
3567
|
return v === null || v === "approve" || v === "changes" ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442: ${String(v)}`;
|
|
3568
|
+
// TASWM-444: словарь исходов верификатора — один на весь продукт (swarm-core), поэтому
|
|
3569
|
+
// проверяем его же функцией, а не повторяем литералы третьим списком.
|
|
3570
|
+
case "verify_verdict":
|
|
3571
|
+
return v === null || isVerifyVerdict(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430: ${String(v)}`;
|
|
3515
3572
|
case "review_cycle":
|
|
3516
3573
|
return typeof v === "number" && Number.isInteger(v) && v >= 0 ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0446\u0438\u043A\u043B \u0440\u0435\u0432\u044C\u044E: ${String(v)}`;
|
|
3517
3574
|
case "pr_url":
|
|
@@ -3775,7 +3832,16 @@ var init_droneChannel = __esm({
|
|
|
3775
3832
|
"use strict";
|
|
3776
3833
|
init_src();
|
|
3777
3834
|
LOCAL_CLI_KEY = "cli";
|
|
3778
|
-
PATCHABLE = /* @__PURE__ */ new Set([
|
|
3835
|
+
PATCHABLE = /* @__PURE__ */ new Set([
|
|
3836
|
+
"status",
|
|
3837
|
+
"pr_url",
|
|
3838
|
+
"review_verdict",
|
|
3839
|
+
// TASWM-444: вердикт верификатора — такая же сдача работы, как review_verdict у ревьюера,
|
|
3840
|
+
// и пишется он тем же путём на ПРОВЕРЯЕМЫЙ прогон. Без него роль verifier была немой:
|
|
3841
|
+
// вердикт выносился комментарием на PR, а `done` отбивался каналом.
|
|
3842
|
+
"verify_verdict",
|
|
3843
|
+
"review_cycle"
|
|
3844
|
+
]);
|
|
3779
3845
|
EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
3780
3846
|
ChannelUnavailableError = class extends Error {
|
|
3781
3847
|
constructor(downMs, message) {
|
|
@@ -4347,6 +4413,10 @@ function authStateLine(nowMs = Date.now()) {
|
|
|
4347
4413
|
if (!denial) return null;
|
|
4348
4414
|
return `\u0414\u041E\u0421\u0422\u0423\u041F \u041A TASKLESS \u041E\u0422\u041E\u0417\u0412\u0410\u041D \u2014 ${deniedSinceLine(nowMs)}. \u041F\u043E\u0447\u0438\u043D\u0438 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: swarm login`;
|
|
4349
4415
|
}
|
|
4416
|
+
function signedInToTaskless() {
|
|
4417
|
+
if (denial) return false;
|
|
4418
|
+
return Boolean(loadAgentToken()?.token);
|
|
4419
|
+
}
|
|
4350
4420
|
function deniedSinceLine(nowMs) {
|
|
4351
4421
|
const d = denial;
|
|
4352
4422
|
if (!d) return "";
|
|
@@ -4910,6 +4980,7 @@ function rowToRun(row) {
|
|
|
4910
4980
|
area: row.area ?? null,
|
|
4911
4981
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
4912
4982
|
model: row.model ?? null,
|
|
4983
|
+
review_model: row.review_model ?? null,
|
|
4913
4984
|
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
4914
4985
|
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
4915
4986
|
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
@@ -4987,6 +5058,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4987
5058
|
area TEXT,
|
|
4988
5059
|
depends_on TEXT,
|
|
4989
5060
|
model TEXT,
|
|
5061
|
+
review_model TEXT,
|
|
4990
5062
|
effort TEXT,
|
|
4991
5063
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
4992
5064
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
@@ -5100,7 +5172,11 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5100
5172
|
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
5101
5173
|
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
5102
5174
|
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
5103
|
-
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
5175
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT",
|
|
5176
|
+
// TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
|
|
5177
|
+
// Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
|
|
5178
|
+
// оттуда, а не ходят за задачей в прод в момент спавна.
|
|
5179
|
+
"ALTER TABLE runs ADD COLUMN review_model TEXT"
|
|
5104
5180
|
]) {
|
|
5105
5181
|
try {
|
|
5106
5182
|
this.db.exec(alter);
|
|
@@ -5120,7 +5196,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5120
5196
|
createRun(input) {
|
|
5121
5197
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
5122
5198
|
const res = this.db.prepare(
|
|
5123
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
5199
|
+
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, review_model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
5124
5200
|
).run(
|
|
5125
5201
|
input.task_slug,
|
|
5126
5202
|
name,
|
|
@@ -5133,6 +5209,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5133
5209
|
input.area ?? null,
|
|
5134
5210
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
5135
5211
|
input.model ?? null,
|
|
5212
|
+
input.review_model ?? null,
|
|
5136
5213
|
input.effort ?? null,
|
|
5137
5214
|
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
5138
5215
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
@@ -6292,6 +6369,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6292
6369
|
if (patch.status === "running") out.executor = this.executorId;
|
|
6293
6370
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
6294
6371
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
6372
|
+
if ("review_model" in patch) out.reviewModel = patch.review_model ?? null;
|
|
6295
6373
|
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
6296
6374
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
6297
6375
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
@@ -6365,6 +6443,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
6365
6443
|
area: input.area ?? null,
|
|
6366
6444
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
6367
6445
|
model: input.model ?? null,
|
|
6446
|
+
// TASWM-451: чем проверять эту работу. У прогона исполнителя приезжает из задачи
|
|
6447
|
+
// (claim в проде), у локально заведённого ревьюера/верификатора — наследством от
|
|
6448
|
+
// проверяемого прогона, которое ставит spawnReviewer/spawnVerifier.
|
|
6449
|
+
review_model: input.review_model ?? null,
|
|
6368
6450
|
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
6369
6451
|
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
6370
6452
|
effort: input.effort ?? null,
|
|
@@ -6423,6 +6505,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
6423
6505
|
status: "queued",
|
|
6424
6506
|
area: d.area,
|
|
6425
6507
|
model: d.model,
|
|
6508
|
+
review_model: d.review_model,
|
|
6509
|
+
// TASWM-451: чем проверять — едет рядом с моделью
|
|
6426
6510
|
effort: d.effort,
|
|
6427
6511
|
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
6428
6512
|
name: d.name,
|
|
@@ -7550,7 +7634,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7550
7634
|
*
|
|
7551
7635
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
7552
7636
|
*/
|
|
7553
|
-
async heartbeat(daemonVersion, pause, capacity, resources, storm) {
|
|
7637
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm, signIn) {
|
|
7554
7638
|
try {
|
|
7555
7639
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
7556
7640
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -7560,6 +7644,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
7560
7644
|
args.paused = !!pause?.paused;
|
|
7561
7645
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
7562
7646
|
}
|
|
7647
|
+
if (signIn) {
|
|
7648
|
+
args.signedIn = signIn.signedIn;
|
|
7649
|
+
if (signIn.webUrl) args.webUrl = signIn.webUrl;
|
|
7650
|
+
}
|
|
7563
7651
|
if (storm !== void 0 && storm !== null) {
|
|
7564
7652
|
args.storm = storm.active;
|
|
7565
7653
|
if (storm.reason) args.stormReason = storm.reason;
|
|
@@ -7791,6 +7879,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7791
7879
|
// существующая локальная запись НЕ перетирается (поле не в field-copy ниже),
|
|
7792
7880
|
// в отличие от respawns, — авто-ревью v2, TASWM-162.
|
|
7793
7881
|
review_cycle: 0,
|
|
7882
|
+
// TASWM-451: модель ПРОВЕРКИ — прод-поле, заполняется ниже из ответа (как base_branch).
|
|
7883
|
+
review_model: null,
|
|
7794
7884
|
task_title: null,
|
|
7795
7885
|
task_desc: null,
|
|
7796
7886
|
task_url: null,
|
|
@@ -7843,6 +7933,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7843
7933
|
local.area = pr.area ?? null;
|
|
7844
7934
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
7845
7935
|
local.model = pr.model ?? null;
|
|
7936
|
+
local.review_model = pr.reviewModel ?? null;
|
|
7846
7937
|
local.effort = toEffortLevel(pr.effort);
|
|
7847
7938
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
7848
7939
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
@@ -8881,12 +8972,26 @@ function pick(v) {
|
|
|
8881
8972
|
const s = typeof v === "string" ? v.trim() : "";
|
|
8882
8973
|
return s.length > 0 ? s : null;
|
|
8883
8974
|
}
|
|
8975
|
+
function isReviewKind(kind) {
|
|
8976
|
+
return kind === "reviewer" || kind === "verifier";
|
|
8977
|
+
}
|
|
8978
|
+
function modelRoleOf(kind) {
|
|
8979
|
+
return isReviewKind(kind) ? "review" : "executor";
|
|
8980
|
+
}
|
|
8884
8981
|
function forKind(kind, swarm, norm) {
|
|
8885
8982
|
if (!swarm) return null;
|
|
8886
|
-
return kind
|
|
8983
|
+
return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
8984
|
+
}
|
|
8985
|
+
function resolveRunModelRef(run, swarm, env = process.env) {
|
|
8986
|
+
const configured = parseModelRef(
|
|
8987
|
+
pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL)
|
|
8988
|
+
);
|
|
8989
|
+
if (configured) return configured;
|
|
8990
|
+
const providerId = pick(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
|
|
8991
|
+
return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
|
|
8887
8992
|
}
|
|
8888
8993
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
8889
|
-
return
|
|
8994
|
+
return resolveRunModelRef(run, swarm, env).model;
|
|
8890
8995
|
}
|
|
8891
8996
|
function resolveRunEffort(run, swarm, env = process.env) {
|
|
8892
8997
|
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
@@ -10108,11 +10213,16 @@ import { z } from "zod";
|
|
|
10108
10213
|
init_src();
|
|
10109
10214
|
|
|
10110
10215
|
// src/reviewCycle.ts
|
|
10216
|
+
init_src();
|
|
10111
10217
|
function reviewTransitionPatch(run, prUrl) {
|
|
10112
|
-
const isFixIteration = run.review_verdict === "changes";
|
|
10218
|
+
const isFixIteration = run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
|
|
10113
10219
|
if (isFixIteration) {
|
|
10114
10220
|
const cycle = (run.review_cycle ?? 0) + 1;
|
|
10115
|
-
return {
|
|
10221
|
+
return {
|
|
10222
|
+
isFixIteration: true,
|
|
10223
|
+
cycle,
|
|
10224
|
+
patch: { status: "review", pr_url: prUrl, review_cycle: cycle, review_verdict: null, verify_verdict: null }
|
|
10225
|
+
};
|
|
10116
10226
|
}
|
|
10117
10227
|
return { isFixIteration: false, cycle: null, patch: { status: "review", pr_url: prUrl } };
|
|
10118
10228
|
}
|
|
@@ -12187,7 +12297,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
12187
12297
|
// package.json
|
|
12188
12298
|
var package_default = {
|
|
12189
12299
|
name: "@taskless-app/swarm",
|
|
12190
|
-
version: "0.6.
|
|
12300
|
+
version: "0.6.14",
|
|
12191
12301
|
type: "module",
|
|
12192
12302
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
12193
12303
|
keywords: [
|
|
@@ -12237,7 +12347,7 @@ var package_default = {
|
|
|
12237
12347
|
execa: "^10.0.0",
|
|
12238
12348
|
zod: "3.25.76"
|
|
12239
12349
|
},
|
|
12240
|
-
gitHead: "
|
|
12350
|
+
gitHead: "a3b11498dbed5db3642fe4fc83bcd2054efde055"
|
|
12241
12351
|
};
|
|
12242
12352
|
|
|
12243
12353
|
// src/version.ts
|
|
@@ -14974,7 +15084,14 @@ async function prGateForReviewer(deps, run, prUrl, when) {
|
|
|
14974
15084
|
}
|
|
14975
15085
|
}
|
|
14976
15086
|
function doneMergedPrForSlug(db, slug) {
|
|
14977
|
-
const done = db.listRuns("done").find(
|
|
15087
|
+
const done = db.listRuns("done").find(
|
|
15088
|
+
(r) => r.task_slug === slug && r.pr_url && r.kind !== "hero" && r.resolves_run_id == null && r.reviews_run_id == null && // TASWM-446: и НЕ верификатор. «done = смержено» верно только для прогона, который PR
|
|
15089
|
+
// делал; верификатор же заканчивает в `done`, держа `pr_url` ПРОВЕРЯЕМОГО — открытого —
|
|
15090
|
+
// PR, и его завершение читалось как доказательство мержа. Дальше pre-spawn гейт закрывал
|
|
15091
|
+
// исполнителя как дубль: 17.08 так погибли пять живых прогонов подряд, а задача после
|
|
15092
|
+
// этого не бралась вовсе — следующий дрон умирал на том же месте.
|
|
15093
|
+
r.verifies_run_id == null
|
|
15094
|
+
);
|
|
14978
15095
|
return done?.pr_url ?? null;
|
|
14979
15096
|
}
|
|
14980
15097
|
async function closeDuplicateTaskRun(deps, run, mergedUrl) {
|
|
@@ -14993,7 +15110,8 @@ async function closeDuplicateTaskRun(deps, run, mergedUrl) {
|
|
|
14993
15110
|
async function closeMergedTaskRuns(deps) {
|
|
14994
15111
|
const bySlug = /* @__PURE__ */ new Map();
|
|
14995
15112
|
for (const run of deps.db.listRuns()) {
|
|
14996
|
-
if (run.kind === "hero" || run.resolves_run_id != null || run.reviews_run_id != null)
|
|
15113
|
+
if (run.kind === "hero" || run.resolves_run_id != null || run.reviews_run_id != null || run.verifies_run_id != null)
|
|
15114
|
+
continue;
|
|
14997
15115
|
let group = bySlug.get(run.task_slug);
|
|
14998
15116
|
if (!group) bySlug.set(run.task_slug, group = []);
|
|
14999
15117
|
group.push(run);
|
|
@@ -15190,6 +15308,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
15190
15308
|
"3. `/verify` \u2014 \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u043C\u0435\u043D\u044F\u043B \u0432\u0438\u0434\u0438\u043C\u044B\u0439 UI/\u0432\u0438\u0437\u0443\u0430\u043B: \u043F\u0440\u043E\u0433\u043E\u043D\u0438 \u0444\u0438\u0447\u0443 \u043D\u0430 \u0436\u0438\u0432\u043E\u043C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 (\u043A\u0430\u043A \u0435\u0433\u043E \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u2014 \u0432 \u0441\u043A\u0438\u043B\u043B\u0430\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F) \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435, \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E \u043A\u043E\u043C\u043F\u0438\u043B\u0438\u0440\u0443\u0435\u0442\u0441\u044F.",
|
|
15191
15309
|
`4. \u0421\u043A\u0440\u0438\u043D\u044B mobile+desktop \u2014 \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u043C\u0435\u043D\u044F\u043B \u0432\u0438\u0434\u0438\u043C\u044B\u0439 UI/\u0432\u0438\u0437\u0443\u0430\u043B, \u041E\u0414\u041D\u041E\u0419 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: \`swarm shot <url> <out-prefix>\` (headless Chromium \u0441\u0430\u043C \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u043E\u0431\u0430 \u0432\u044C\u044E\u043F\u043E\u0440\u0442\u0430, \u0441\u043D\u0438\u043C\u0430\u0435\u0442 \u0438 \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u2014 \u043D\u0435 \u0434\u0435\u0440\u0436\u0438\u0442 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C). \u041F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C \u2014 \u043E\u0431\u0430 \u0444\u0430\u0439\u043B\u0430 \u0418\u0414\u0423\u0422 \u0412 \u0417\u0410\u0414\u0410\u0427\u0423: \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u0438\u0445 \u043F\u0443\u0442\u0438 \u0432 \`done(pr_url, summary, screenshots: ["\u2026-desktop.png", "\u2026-mobile.png"])\`, \u0434\u0435\u043C\u043E\u043D \u043F\u0440\u0438\u043B\u043E\u0436\u0438\u0442 \u0438\u0445 \u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F\u043C\u0438 \u043A \u0442\u0432\u043E\u0435\u043C\u0443 \u043E\u0442\u0447\u0451\u0442\u0443 (TASWM-294). \u0427\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u0442\u0440\u0435\u043A\u0435\u0440, \u0430 \u043D\u0435 \u0433\u0438\u0442\u0445\u0430\u0431: \u0437\u0430\u0434\u0430\u0447\u0430 \u0431\u0435\u0437 \u0441\u043A\u0440\u0438\u043D\u043E\u0432 = \u043D\u0435\u0434\u043E\u043A\u0430\u0437\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430. \u0412 PR \u2014 \u0414\u041E\u041F\u041E\u041B\u041D\u0418\u0422\u0415\u041B\u042C\u041D\u041E, \u043F\u043E \u0436\u0435\u043B\u0430\u043D\u0438\u044E; \u0432\u043C\u0435\u0441\u0442\u043E \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u043D\u0435\u0442. \u23F1 \u0425\u0430\u0440\u0434-\u043B\u0438\u043C\u0438\u0442 \u043D\u0430 \u0432\u0435\u0441\u044C \u0448\u0430\u0433 \u0441\u043A\u0440\u0438\u043D\u043E\u0432 \u2014 5 \u043C\u0438\u043D\u0443\u0442. \u041D\u0415 \u0433\u043E\u043D\u044F\u0439 Playwright/\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0439 Chrome \u0432\u0440\u0443\u0447\u043D\u0443\u044E \u043F\u043E \u0448\u0430\u0433\u0430\u043C (\u043E\u0442\u043A\u0440\u044B\u043B\u2192\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u043B\u2192\u043A\u043B\u0438\u043A\u043D\u0443\u043B\u2192\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u043B\u2026) \u2014 \u0442\u0430\u043A \u0434\u0440\u043E\u043D \u0414\u0412\u0410\u0416\u0414\u042B \u0437\u0430\u043B\u0438\u043F\u0430\u043B \u043D\u0430 \u044D\u0442\u043E\u043C (TASWM-30, >30 \u043C\u0438\u043D stall \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u0434\u0430\u043B\u044C\u0448\u0435) \u0432\u043C\u0435\u0441\u0442\u043E \u0442\u043E\u0433\u043E \u0447\u0442\u043E\u0431\u044B \u0441\u0434\u0430\u0442\u044C \u0433\u043E\u0442\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443. \u041D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u0441\u044F \u0432 \u043B\u0438\u043C\u0438\u0442 \u0438\u043B\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u043D\u0435 \u0438\u0434\u0451\u0442 \u2014 \u043F\u0440\u0438\u043B\u043E\u0436\u0438 \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u043D\u044F\u043B, \u0438\u043B\u0438 \u043F\u0440\u044F\u043C\u043E \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 PR/\u0437\u0430\u0434\u0430\u0447\u0435 \xAB\u0441\u043A\u0440\u0438\u043D\u044B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\xBB \u0438 \u0421\u0420\u0410\u0417\u0423 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url) \u2014 \u043D\u0435 \u043F\u0435\u0440\u0435\u0438\u0433\u0440\u044B\u0432\u0430\u0439 \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u043F\u043E \u043A\u0440\u0443\u0433\u0443.`,
|
|
15192
15310
|
"4a. \u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0417\u0410 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0431\u0435\u0437 \u0433\u043E\u0442\u043E\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u0430\u0451\u0442 \u044D\u043A\u0440\u0430\u043D \u043B\u043E\u0433\u0438\u043D\u0430, \u0430 \u043D\u0435 \u0446\u0435\u043B\u0435\u0432\u043E\u0439 \u044D\u043A\u0440\u0430\u043D \u2014 \u0441\u043D\u0438\u043C\u0430\u0442\u044C \u0435\u0451 \u043D\u0443\u0436\u043D\u043E \u0441 `--storage-state <\u0444\u0430\u0439\u043B>` (\u043A\u0430\u043A \u0437\u0430\u0432\u0435\u0441\u0442\u0438 \u0442\u0430\u043A\u043E\u0439 \u0444\u0430\u0439\u043B \u0432 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u0432 \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u0430\u0445). \u041F\u043E\u0440\u044F\u0434\u043E\u043A \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u0432\u0430\u0436\u0435\u043D: `--storage-state` \u0411\u0415\u0417 \u043F\u0440\u0435\u0434\u0448\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0435\u0433\u043E `<out-prefix>` \u043C\u043E\u043B\u0447\u0430 \u0441\u044A\u0435\u0434\u0430\u0435\u0442 \u0435\u0433\u043E \u043C\u0435\u0441\u0442\u043E, \u0438 \u0444\u043B\u0430\u0433 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u0441\u044F (\u0441\u043C. shotCli.ts parseShotArgs) \u2014 \u0435\u0441\u043B\u0438 \u043D\u0430 \u0441\u043D\u0438\u043C\u043A\u0435 \u044D\u043A\u0440\u0430\u043D \u043B\u043E\u0433\u0438\u043D\u0430 \u0432\u043C\u0435\u0441\u0442\u043E \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F, \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u044D\u0442\u043E.",
|
|
15311
|
+
"4b. \u0421\u041D\u0418\u041C\u0410\u0419 \u041F\u0420\u041E\u0414\u0423\u041A\u0422, \u0410 \u041D\u0415 \u041F\u0420\u0415\u0412\u042C\u042E-\u0421\u0422\u0420\u0410\u041D\u0418\u0426\u0423 (TASWM-404). \u0414\u0435\u043C\u043E-\u0440\u043E\u0443\u0442 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u0430 (`/dev/*`, \u043F\u0435\u0441\u043E\u0447\u043D\u0438\u0446\u0430, \u0441\u0442\u043E\u0440\u0438 \u0432 \u043C\u043E\u0431\u0438\u043B\u044C\u043D\u043E\u043C \u0444\u0440\u0435\u0439\u043C\u0435) \u2014 \u041D\u0415 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u043C\u043E\u0431\u0438\u043B\u044C\u043D\u043E\u0439 \u0432\u0451\u0440\u0441\u0442\u043A\u0438: \u0443 \u0442\u0430\u043A\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0441\u0432\u043E\u044F \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0430, \u0435\u0451 \u043C\u0435\u043D\u044E \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u0443\u0437\u043A\u043E\u043C \u044D\u043A\u0440\u0430\u043D\u0435, \u0438 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u043C\u0443 \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043F\u043E\u043B\u043E\u0441\u0430 \u0432 \u043F\u0430\u0440\u0443 \u0441\u043E\u0442\u0435\u043D \u043F\u0438\u043A\u0441\u0435\u043B\u0435\u0439 \u0432\u043C\u0435\u0441\u0442\u043E \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0439 \u0448\u0438\u0440\u0438\u043D\u044B. \u041F\u043E \u0442\u0430\u043A\u043E\u043C\u0443 \u0441\u043D\u0438\u043C\u043A\u0443 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u043D\u0438 \u0448\u0438\u0440\u0438\u043D\u044B \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u0438 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u044F \u0448\u0430\u043F\u043A\u0438, \u043D\u0438 \u043D\u0430\u043F\u043E\u043B\u0437\u0430\u043D\u0438\u044F \u043B\u0438\u043F\u043A\u0438\u0445 \u043F\u043E\u0434\u043F\u0438\u0441\u0435\u0439 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u0412\u0421\u0415\u0413\u0414\u0410 \u0438 \u043D\u0435 \u043B\u043E\u0432\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E (\u0442\u0430\u043A \u0441\u0434\u0430\u043B\u0438 \u0441\u043B\u043E\u043C\u0430\u043D\u043D\u0443\u044E \u043C\u043E\u0431\u0438\u043B\u0443 \u0432 TASWM-403, \u0441\u043A\u0440\u0438\u043D \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u0431\u044B\u043B \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D). \u0421\u043D\u0438\u043C\u0430\u0439 \u0420\u0415\u0410\u041B\u042C\u041D\u042B\u0419 \u0440\u043E\u0443\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043F\u043E\u0434 \u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439; \u043A\u0430\u043A \u044D\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0432 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u0432 \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u0430\u0445. \u041F\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u043D\u043E \u0440\u043E\u0432\u043D\u043E \u0442\u043E\u0433\u0434\u0430, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u0430\u0432\u0438\u0448\u044C \u0441\u0430\u043C \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442 \u0432\u043D\u0435 \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F.",
|
|
15193
15312
|
"\u0427\u0438\u0441\u0442\u043E \u0431\u044D\u043A\u0435\u043D\u0434/\u043B\u043E\u0433\u0438\u043A\u0443/API/\u0434\u0435\u043C\u043E\u043D/CI/\u0442\u0438\u043F\u044B \u2014 \u0448\u0430\u0433\u0438 3\u20134 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438 (\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u041D\u0415 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 \u0438 \u0441\u043A\u0440\u0438\u043D\u044B \u043D\u0435 \u0441\u043D\u0438\u043C\u0430\u0439): \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B (\u0448\u0430\u0433 1) + e2e \u0432 CI \u044D\u0442\u043E \u0443\u0436\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u044E\u0442; \u044D\u043A\u043E\u043D\u043E\u043C\u0438\u043C \u0432\u0440\u0435\u043C\u044F \u0438 \u0442\u043E\u043A\u0435\u043D\u044B.",
|
|
15194
15313
|
"\u0422\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E \u0437\u043E\u0432\u0438 done(pr_url), \u0438 \u0432 \u0441\u0434\u0430\u0447\u0435 \u043F\u043E\u043A\u0430\u0436\u0438 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E (\u0437\u0435\u043B\u0451\u043D\u044B\u0439 \u0432\u044B\u0432\u043E\u0434/\u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435), \u0430 \u043D\u0435 \xAB\u0434\u043E\u043B\u0436\u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C\xBB.",
|
|
15195
15314
|
"\u0421\u043A\u0438\u043B\u043B\u044B \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u043A \u0442\u0435\u0431\u0435 \u0414\u0412\u0423\u041C\u042F \u043D\u0430\u0431\u043E\u0440\u0430\u043C\u0438, \u0438 \u043E\u0431\u0430 \u0443\u0436\u0435 \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D\u044B (\u0441\u043F\u0430\u0432\u043D \u0438\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u043D \u043E\u0442 \u044E\u0437\u0435\u0440\u0441\u043A\u043E\u0433\u043E ~/.claude, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u041B\u0418\u0427\u041D\u042B\u0415 \u0441\u043A\u0438\u043B\u043B\u044B \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0442\u0435\u0431\u0435 \u043D\u0435 \u0432\u0438\u0434\u043D\u044B). \u2460 \u0420\u043E\u0439\u043D\u044B\u0435 `swarm-drone:*` \u0435\u0434\u0443\u0442 \u0441 \u0420\u041E\u0415\u041C \u0432 \u043B\u044E\u0431\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u2014 \u0434\u0438\u0441\u0446\u0438\u043F\u043B\u0438\u043D\u0430, \u0432\u0435\u0440\u043D\u0430\u044F \u0432\u0435\u0437\u0434\u0435: swarm-save-often (\u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0438\u0440\u0430\u0435\u0442 \u0441 \u0434\u0440\u043E\u043D\u043E\u043C), swarm-merge-main (\u0431\u0430\u0437\u043E\u0432\u0443\u044E \u0432\u0435\u0442\u043A\u0443 \u0432\u043B\u0438\u0432\u0430\u0442\u044C, \u0430 \u043D\u0435 \u0440\u0435\u0431\u0435\u0439\u0437\u0438\u0442\u044C \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u0443\u044E), swarm-await-process (\u0436\u0434\u0430\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u0435, \u043D\u0435 sleep), swarm-verify-green (\u0447\u0442\u043E \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C), systematic-debugging (\u043F\u0435\u0440\u0432\u043E\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0434\u043E \u0444\u0438\u043A\u0441\u0430). \u2461 \u0421\u043A\u0438\u043B\u043B\u044B \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F (.claude/skills, \u0441\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430) \u2014 \u0437\u043D\u0430\u043D\u0438\u044F \u043E\u0431 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435: \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0441\u0442\u0435\u043A, \u043A\u0430\u043A \u0433\u043E\u043D\u044F\u0442\u044C \u0442\u0435\u0441\u0442\u044B \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u043E\u0435, UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430. \u041F\u043E\u043B\u044C\u0437\u0443\u0439\u0441\u044F \u043E\u0431\u043E\u0438\u043C\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u0438\u0437\u043E\u0431\u0440\u0435\u0442\u0430\u0439; \u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u043D\u0438\u044F \u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0431\u0435\u043B \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u043E\u0432, \u0441\u043A\u0430\u0436\u0438 note(), \u0430 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B."
|
|
@@ -16782,7 +16901,7 @@ async function tickOnce(deps) {
|
|
|
16782
16901
|
} else if (!st.conflicting && deps.ci && deps.respawn && !stormActive) {
|
|
16783
16902
|
await handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap);
|
|
16784
16903
|
}
|
|
16785
|
-
if (run.review_verdict === "changes" && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
|
|
16904
|
+
if ((run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict))) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
|
|
16786
16905
|
continue;
|
|
16787
16906
|
}
|
|
16788
16907
|
const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
|
|
@@ -17205,8 +17324,14 @@ async function replayDecision(deps, qid, chosen, by = "human") {
|
|
|
17205
17324
|
repo: old.repo,
|
|
17206
17325
|
area: old.area,
|
|
17207
17326
|
decision: { question: q.text, options: q.options, chosen, prior: q.answer, priorState: old.status },
|
|
17208
|
-
workspace_id: old.workspace_id
|
|
17327
|
+
workspace_id: old.workspace_id,
|
|
17209
17328
|
// переигровка той же задачи — тот же воркспейс (TASWM-288)
|
|
17329
|
+
// TASWM-451: та же ЗАДАЧА — те же модели. Переигровка это продолжение той же работы, а не
|
|
17330
|
+
// новая: без переноса любой вопрос дрона оператору молча возвращал бы задачу на дефолт
|
|
17331
|
+
// роли, и выбор постановщика («эту делаем сильной моделью») терялся бы ровно там, где
|
|
17332
|
+
// работа оказалась сложной. Тот же довод, что у area и workspace_id строкой выше.
|
|
17333
|
+
model: old.model,
|
|
17334
|
+
review_model: old.review_model
|
|
17210
17335
|
});
|
|
17211
17336
|
deps.db.updateRun(old.id, { status: "superseded" });
|
|
17212
17337
|
deps.db.supersede(old.id, fresh.id, by);
|
|
@@ -17234,8 +17359,13 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
17234
17359
|
area: reviewRun.area,
|
|
17235
17360
|
// держим area-lock, пока чиним — не начинаем конфликтующее в зоне
|
|
17236
17361
|
resolves_run_id: reviewRun.id,
|
|
17237
|
-
workspace_id: reviewRun.workspace_id
|
|
17362
|
+
workspace_id: reviewRun.workspace_id,
|
|
17238
17363
|
// та же задача, тот же воркспейс (TASWM-288)
|
|
17364
|
+
// TASWM-451: резолвер доделывает ТУ ЖЕ работу — разводит конфликт в её же ветке, и
|
|
17365
|
+
// модель ему нужна та же, что выбрали задаче. Модель проверки несём дальше, чтобы её не
|
|
17366
|
+
// потеряли проверяющие, которых поднимут уже по прогону резолвера.
|
|
17367
|
+
model: reviewRun.model,
|
|
17368
|
+
review_model: reviewRun.review_model
|
|
17239
17369
|
});
|
|
17240
17370
|
const wt = join29(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
17241
17371
|
try {
|
|
@@ -17282,6 +17412,11 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
17282
17412
|
task_slug: reviewRun.task_slug,
|
|
17283
17413
|
repo,
|
|
17284
17414
|
kind: "reviewer",
|
|
17415
|
+
// TASWM-451: ЧЕМ проверять — решение постановщика, приехавшее с задачей на прогон
|
|
17416
|
+
// исполнителя. Пин, а не «спросим настройки на спавне»: ревьюер заводится локально, и
|
|
17417
|
+
// задача ему недоступна. Пусто в задаче → null → дефолт роли (модель проверки).
|
|
17418
|
+
model: reviewRun.review_model,
|
|
17419
|
+
review_model: reviewRun.review_model,
|
|
17285
17420
|
reviews_run_id: reviewRun.id,
|
|
17286
17421
|
review_cycle: reviewRun.review_cycle,
|
|
17287
17422
|
// штамп цикла (авто-ревью v2, TASWM-162): reviewerCountFor текущего цикла
|
|
@@ -17354,6 +17489,11 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
17354
17489
|
task_slug: verifiedRun.task_slug,
|
|
17355
17490
|
repo,
|
|
17356
17491
|
kind: "verifier",
|
|
17492
|
+
// TASWM-451: та же модель проверки, что у ревьюера, и из того же источника — задачи.
|
|
17493
|
+
// Две проверки одного PR должны стоить одинаково, иначе одна из них незаметно уедет
|
|
17494
|
+
// на модель исполнителя (ровно это и стоило $45.94 на TAS-524).
|
|
17495
|
+
model: verifiedRun.review_model,
|
|
17496
|
+
review_model: verifiedRun.review_model,
|
|
17357
17497
|
verifies_run_id: verifiedRun.id,
|
|
17358
17498
|
review_cycle: verifiedRun.review_cycle,
|
|
17359
17499
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
@@ -17724,7 +17864,17 @@ async function runDaemon(opts) {
|
|
|
17724
17864
|
control.state ?? null,
|
|
17725
17865
|
lastOwnCapacity,
|
|
17726
17866
|
hostResourcesForProd(),
|
|
17727
|
-
{ active: stormActive, reason: stormStartedAt ? `\u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441 ${stormStartedAt} UTC` : null }
|
|
17867
|
+
{ active: stormActive, reason: stormStartedAt ? `\u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441 ${stormStartedAt} UTC` : null },
|
|
17868
|
+
// TASWM-393: ВХОД в Taskless — та же природа, что пауза и шторм: состояние
|
|
17869
|
+
// машины, о котором знает только она. Пульс идёт и без входа, поэтому «на
|
|
17870
|
+
// связи» без этого поля читалось как «возьмёт работу» (12.08 — шесть прогонов
|
|
17871
|
+
// в очереди у машины, которая снаружи выглядела здоровой). Адрес шлём
|
|
17872
|
+
// ФАКТИЧЕСКИЙ (web.port), а не зашитый: порт настраивается.
|
|
17873
|
+
// signedInToTaskless(), а НЕ !authStateLine(): отказ — это «токен есть, но его
|
|
17874
|
+
// отвергают», а 12.08 входа не делали вовсе, и по отказу машина числилась бы
|
|
17875
|
+
// вошедшей. Адрес — WEB_PORT (он же SWARM_WEB_PORT из окружения, на нём и
|
|
17876
|
+
// поднимается морда): зашитый 7420 отправил бы человека туда, где ничего нет.
|
|
17877
|
+
{ signedIn: signedInToTaskless(), webUrl: `http://127.0.0.1:${WEB_PORT}` }
|
|
17728
17878
|
);
|
|
17729
17879
|
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
17730
17880
|
if (reply.commands.length) onHostCommands(reply.commands);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
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": "a3b11498dbed5db3642fe4fc83bcd2054efde055",
|
|
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",
|