@taskless-app/swarm 0.6.32 → 0.6.34
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 +43 -11
- package/dist/overmind.js +694 -198
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -1310,6 +1310,152 @@ var init_runGuards = __esm({
|
|
|
1310
1310
|
}
|
|
1311
1311
|
});
|
|
1312
1312
|
|
|
1313
|
+
// ../swarm-core/src/taskWeight.ts
|
|
1314
|
+
function median(values) {
|
|
1315
|
+
const xs = [...values].sort((a, b) => a - b);
|
|
1316
|
+
const mid = Math.floor(xs.length / 2);
|
|
1317
|
+
return xs.length % 2 ? xs[mid] : (xs[mid - 1] + xs[mid]) / 2;
|
|
1318
|
+
}
|
|
1319
|
+
function classify(expectedUsd, t) {
|
|
1320
|
+
if (expectedUsd >= t.medianUsd * t.heavyFactor) return "heavy";
|
|
1321
|
+
if (expectedUsd <= t.medianUsd * t.lightFactor) return "light";
|
|
1322
|
+
return "normal";
|
|
1323
|
+
}
|
|
1324
|
+
function estimateTaskWeight(samples, opts) {
|
|
1325
|
+
const t = opts?.thresholds ?? DEFAULT_WEIGHT_THRESHOLDS;
|
|
1326
|
+
const usable = samples.filter((s) => s.productive && Number.isFinite(s.costUsd) && s.costUsd > 0);
|
|
1327
|
+
const own = usable.filter((s) => s.ownTask);
|
|
1328
|
+
const pool = own.length ? own : usable;
|
|
1329
|
+
if (pool.length) {
|
|
1330
|
+
const expectedUsd = median(pool.map((s) => s.costUsd));
|
|
1331
|
+
return { weight: classify(expectedUsd, t), expectedUsd, basis: own.length ? "own" : "similar", samples: pool.length };
|
|
1332
|
+
}
|
|
1333
|
+
if (opts?.hint) {
|
|
1334
|
+
const expectedUsd = opts.hint === "heavy" ? t.medianUsd * t.heavyFactor : opts.hint === "light" ? t.medianUsd * t.lightFactor : t.medianUsd;
|
|
1335
|
+
return { weight: opts.hint, expectedUsd, basis: "hint", samples: 0 };
|
|
1336
|
+
}
|
|
1337
|
+
return { weight: "normal", expectedUsd: t.medianUsd, basis: "default", samples: 0 };
|
|
1338
|
+
}
|
|
1339
|
+
function isRunProgressing(p) {
|
|
1340
|
+
return p.commits > 0 || p.hasPr || p.acceptanceChecks > 0;
|
|
1341
|
+
}
|
|
1342
|
+
function raisedCeilingUsd(baseUsd, grantedUsd) {
|
|
1343
|
+
if (!(baseUsd > 0)) return 0;
|
|
1344
|
+
const from = grantedUsd != null && Number.isFinite(grantedUsd) && grantedUsd > baseUsd ? grantedUsd : baseUsd;
|
|
1345
|
+
const next = from + baseUsd * HEAVY_CEILING_STEP;
|
|
1346
|
+
return Math.round(Math.min(next, baseUsd * HEAVY_CEILING_MAX_FACTOR) * 100) / 100;
|
|
1347
|
+
}
|
|
1348
|
+
function decideHeavyClaim(input) {
|
|
1349
|
+
const spent = Number.isFinite(input.spentUsd) && input.spentUsd > 0 ? input.spentUsd : 0;
|
|
1350
|
+
const bar = input.estimate.expectedUsd * HEAVY_CLAIM_MIN_SPENT_RATIO;
|
|
1351
|
+
if (spent < bar) {
|
|
1352
|
+
return {
|
|
1353
|
+
verdict: "too_early",
|
|
1354
|
+
ceilingUsd: null,
|
|
1355
|
+
reason: `\u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E $${spent.toFixed(2)} \u043F\u0440\u0438 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u044B\u0445 $${input.estimate.expectedUsd.toFixed(2)} \u2014 \u0434\u043E \u043F\u043E\u043B\u043E\u0432\u0438\u043D\u044B \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E\u0433\u043E \u0435\u0449\u0451 \u0434\u0430\u043B\u0435\u043A\u043E, \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u0442\u044C \u043F\u043E\u043A\u0430 \u043D\u0435\u0447\u0435\u0433\u043E`
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
if (!isRunProgressing(input.progress)) {
|
|
1359
|
+
return {
|
|
1360
|
+
verdict: "stalled",
|
|
1361
|
+
ceilingUsd: null,
|
|
1362
|
+
reason: `\u0442\u0440\u0430\u0442\u0430 \u0440\u0430\u0441\u0442\u0451\u0442 ($${spent.toFixed(2)}), \u0430 \u043F\u0440\u043E\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442: \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432 \u0432 \u0432\u0435\u0442\u043A\u0435, \u043D\u0438 PR, \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u043D\u043E\u0433\u043E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u044F. \u042D\u0442\u043E \u043D\u0435 \u0442\u044F\u0436\u0451\u043B\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430, \u0430 \u043F\u0440\u043E\u0433\u043E\u043D, \u0441\u0442\u043E\u044F\u0449\u0438\u0439 \u043D\u0430 \u043C\u0435\u0441\u0442\u0435, \u2014 \u0432\u0435\u0441\u0430 \u043D\u0435 \u0434\u0430\u0451\u043C, \u0431\u044E\u0434\u0436\u0435\u0442 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C`
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
const granted = input.grantedUsd ?? null;
|
|
1366
|
+
const next = raisedCeilingUsd(input.baseCeilingUsd, granted);
|
|
1367
|
+
if (granted != null && next <= granted) {
|
|
1368
|
+
return {
|
|
1369
|
+
verdict: "capped",
|
|
1370
|
+
ceilingUsd: granted,
|
|
1371
|
+
reason: `\u0432\u0435\u0441 \u0443\u0436\u0435 \u043F\u0440\u0438\u0437\u043D\u0430\u043D, \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u0434\u043D\u044F\u0442 \u0434\u043E $${granted.toFixed(2)} \u2014 \u044D\u0442\u043E \u043F\u0440\u0435\u0434\u0435\u043B \u0434\u043E\u0431\u0430\u0432\u043A\u0438 (${HEAVY_CEILING_MAX_FACTOR}\xD7 \u0431\u0430\u0437\u043E\u0432\u043E\u0433\u043E $${input.baseCeilingUsd.toFixed(2)})`
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
verdict: "granted",
|
|
1376
|
+
ceilingUsd: next,
|
|
1377
|
+
reason: `\u0440\u0430\u0431\u043E\u0442\u0430 \u0442\u044F\u0436\u0451\u043B\u0430\u044F \u043F\u043E \u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044E: \u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043E $${spent.toFixed(2)} \u043F\u0440\u0438 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u044B\u0445 $${input.estimate.expectedUsd.toFixed(2)}, \u043F\u0440\u043E\u0433\u043E\u043D \u043F\u0440\u043E\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F (${progressWords(input.progress)}) \u2014 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u0434\u043D\u044F\u0442 \u0434\u043E $${next.toFixed(2)}`
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function progressWords(p) {
|
|
1381
|
+
const parts = [];
|
|
1382
|
+
if (p.commits > 0) parts.push(`\u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432: ${p.commits}`);
|
|
1383
|
+
if (p.hasPr) parts.push("PR \u043E\u0442\u043A\u0440\u044B\u0442");
|
|
1384
|
+
if (p.acceptanceChecks > 0) parts.push(`\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0437\u0430\u043A\u0440\u044B\u0442\u043E: ${p.acceptanceChecks}`);
|
|
1385
|
+
return parts.join(", ") || "\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u0435 \u0435\u0441\u0442\u044C";
|
|
1386
|
+
}
|
|
1387
|
+
function heavyClaimText(note) {
|
|
1388
|
+
return `${HEAVY_CLAIM_MARK} \u2014 ${note}`;
|
|
1389
|
+
}
|
|
1390
|
+
function heavyGrantText(ceilingUsd, reason) {
|
|
1391
|
+
return `${HEAVY_GRANT_MARK} (\u043F\u043E\u0442\u043E\u043B\u043E\u043A $${ceilingUsd.toFixed(2)}) \u2014 ${reason}`;
|
|
1392
|
+
}
|
|
1393
|
+
function heavyRefusedText(reason) {
|
|
1394
|
+
return `${HEAVY_REFUSED_MARK} \u2014 ${reason}`;
|
|
1395
|
+
}
|
|
1396
|
+
function isHeavyClaimEvent(e) {
|
|
1397
|
+
return has(e, HEAVY_CLAIM_MARK);
|
|
1398
|
+
}
|
|
1399
|
+
function isHeavyGrantEvent(e) {
|
|
1400
|
+
return has(e, HEAVY_GRANT_MARK);
|
|
1401
|
+
}
|
|
1402
|
+
function isHeavyRefusedEvent(e) {
|
|
1403
|
+
return has(e, HEAVY_REFUSED_MARK);
|
|
1404
|
+
}
|
|
1405
|
+
function grantedCeilingUsd(events) {
|
|
1406
|
+
let max = null;
|
|
1407
|
+
for (const e of events) {
|
|
1408
|
+
if (!isHeavyGrantEvent(e)) continue;
|
|
1409
|
+
const m = /потолок \$([0-9]+(?:\.[0-9]+)?)/.exec(e.text ?? "");
|
|
1410
|
+
if (!m) continue;
|
|
1411
|
+
const v = Number(m[1]);
|
|
1412
|
+
if (Number.isFinite(v) && (max == null || v > max)) max = v;
|
|
1413
|
+
}
|
|
1414
|
+
return max;
|
|
1415
|
+
}
|
|
1416
|
+
function countAcceptanceChecks(events) {
|
|
1417
|
+
return events.filter((e) => has(e, ACCEPTANCE_CHECK_MARK)).length;
|
|
1418
|
+
}
|
|
1419
|
+
function isHeavyRun(events) {
|
|
1420
|
+
return events.some(isHeavyGrantEvent);
|
|
1421
|
+
}
|
|
1422
|
+
function hasOpenHeavyClaim(events) {
|
|
1423
|
+
let open = false;
|
|
1424
|
+
for (const e of events) {
|
|
1425
|
+
if (isHeavyClaimEvent(e)) open = true;
|
|
1426
|
+
else if (isHeavyGrantEvent(e) || isHeavyRefusedEvent(e)) open = false;
|
|
1427
|
+
}
|
|
1428
|
+
return open;
|
|
1429
|
+
}
|
|
1430
|
+
function effectiveCostCeilingUsd(baseUsd, events) {
|
|
1431
|
+
if (!(baseUsd > 0)) return baseUsd;
|
|
1432
|
+
const granted = grantedCeilingUsd(events);
|
|
1433
|
+
if (granted == null) return baseUsd;
|
|
1434
|
+
return Math.min(Math.max(baseUsd, granted), baseUsd * HEAVY_CEILING_MAX_FACTOR);
|
|
1435
|
+
}
|
|
1436
|
+
function intakeHoldReason(heavyLabel) {
|
|
1437
|
+
return `\u0442\u044F\u0436\u0451\u043B\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0432 \u0440\u0430\u0431\u043E\u0442\u0435 (${heavyLabel}) \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u043C, \u043F\u043E\u043A\u0430 \u043E\u043D \u043D\u0435 \u0441\u0434\u0430\u0441\u0442: \u0435\u043C\u0443 \u043D\u0443\u0436\u043D\u043E \u043C\u0435\u0441\u0442\u043E, \u0430 \u043D\u0435 \u0441\u043E\u0441\u0435\u0434\u0438 (TASWM-535)`;
|
|
1438
|
+
}
|
|
1439
|
+
var DEFAULT_WEIGHT_THRESHOLDS, HEAVY_CLAIM_MIN_SPENT_RATIO, HEAVY_CEILING_STEP, HEAVY_CEILING_MAX_FACTOR, HEAVY_CLAIM_MARK, HEAVY_GRANT_MARK, HEAVY_REFUSED_MARK, ACCEPTANCE_CHECK_MARK, has;
|
|
1440
|
+
var init_taskWeight = __esm({
|
|
1441
|
+
"../swarm-core/src/taskWeight.ts"() {
|
|
1442
|
+
"use strict";
|
|
1443
|
+
DEFAULT_WEIGHT_THRESHOLDS = {
|
|
1444
|
+
medianUsd: 1.81,
|
|
1445
|
+
lightFactor: 0.5,
|
|
1446
|
+
heavyFactor: 3
|
|
1447
|
+
};
|
|
1448
|
+
HEAVY_CLAIM_MIN_SPENT_RATIO = 0.5;
|
|
1449
|
+
HEAVY_CEILING_STEP = 0.5;
|
|
1450
|
+
HEAVY_CEILING_MAX_FACTOR = 2;
|
|
1451
|
+
HEAVY_CLAIM_MARK = "\u0432\u0435\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430: \u0442\u044F\u0436\u0435\u043B\u0435\u0435 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0439";
|
|
1452
|
+
HEAVY_GRANT_MARK = "\u0432\u0435\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430: \u0442\u044F\u0436\u0451\u043B\u0430\u044F";
|
|
1453
|
+
HEAVY_REFUSED_MARK = "\u0432\u0435\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430: \u043F\u0440\u043E\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442";
|
|
1454
|
+
ACCEPTANCE_CHECK_MARK = "\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0438";
|
|
1455
|
+
has = (e, mark) => !!e.text?.includes(mark);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1313
1459
|
// ../swarm-core/src/codeZones.ts
|
|
1314
1460
|
function hasZoneModel(m) {
|
|
1315
1461
|
return m.tasks > 0 && m.zones.length > 0;
|
|
@@ -1806,6 +1952,7 @@ var init_src = __esm({
|
|
|
1806
1952
|
init_cost();
|
|
1807
1953
|
init_modelPricing();
|
|
1808
1954
|
init_runGuards();
|
|
1955
|
+
init_taskWeight();
|
|
1809
1956
|
init_codeZones();
|
|
1810
1957
|
init_effort();
|
|
1811
1958
|
init_queuePlan();
|
|
@@ -3764,9 +3911,9 @@ var init_procCleanup = __esm({
|
|
|
3764
3911
|
});
|
|
3765
3912
|
|
|
3766
3913
|
// src/git.ts
|
|
3767
|
-
import { existsSync as
|
|
3914
|
+
import { existsSync as existsSync12, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
3768
3915
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3769
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3916
|
+
import { isAbsolute as isAbsolute2, join as join19, sep as sep2 } from "path";
|
|
3770
3917
|
import { $ as $2 } from "execa";
|
|
3771
3918
|
function gitNet(repo, args, timeoutMs) {
|
|
3772
3919
|
return withHardTimeout(
|
|
@@ -3789,7 +3936,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
3789
3936
|
}
|
|
3790
3937
|
function isLinkedWorktree(dir) {
|
|
3791
3938
|
try {
|
|
3792
|
-
return statSync8(
|
|
3939
|
+
return statSync8(join19(dir, ".git")).isFile();
|
|
3793
3940
|
} catch {
|
|
3794
3941
|
return false;
|
|
3795
3942
|
}
|
|
@@ -3801,7 +3948,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
3801
3948
|
}
|
|
3802
3949
|
}
|
|
3803
3950
|
async function ensureWorktreeDirGone(worktree) {
|
|
3804
|
-
if (!
|
|
3951
|
+
if (!existsSync12(worktree)) return false;
|
|
3805
3952
|
if (!isLinkedWorktree(worktree)) {
|
|
3806
3953
|
console.error(
|
|
3807
3954
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -3816,7 +3963,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
3816
3963
|
);
|
|
3817
3964
|
return true;
|
|
3818
3965
|
}
|
|
3819
|
-
if (
|
|
3966
|
+
if (existsSync12(worktree)) {
|
|
3820
3967
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
3821
3968
|
}
|
|
3822
3969
|
return true;
|
|
@@ -3864,7 +4011,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3864
4011
|
}
|
|
3865
4012
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
3866
4013
|
if (busyAt) {
|
|
3867
|
-
const exists =
|
|
4014
|
+
const exists = existsSync12(busyAt);
|
|
3868
4015
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
3869
4016
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
3870
4017
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -3899,8 +4046,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3899
4046
|
try {
|
|
3900
4047
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3901
4048
|
if (!commonDir) return false;
|
|
3902
|
-
const infoDir =
|
|
3903
|
-
const excludeFile =
|
|
4049
|
+
const infoDir = join19(commonDir, "info");
|
|
4050
|
+
const excludeFile = join19(infoDir, "exclude");
|
|
3904
4051
|
let current = "";
|
|
3905
4052
|
try {
|
|
3906
4053
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3920,7 +4067,7 @@ async function gitPath(worktree, flag) {
|
|
|
3920
4067
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3921
4068
|
const raw = out.stdout.trim();
|
|
3922
4069
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3923
|
-
return isAbsolute2(raw) ? raw :
|
|
4070
|
+
return isAbsolute2(raw) ? raw : join19(worktree, raw);
|
|
3924
4071
|
}
|
|
3925
4072
|
async function ensureWorktreeHygiene(worktree) {
|
|
3926
4073
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3930,12 +4077,12 @@ async function installGuardHooks(worktree) {
|
|
|
3930
4077
|
try {
|
|
3931
4078
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3932
4079
|
if (!gitDir) return false;
|
|
3933
|
-
const hooksDir =
|
|
4080
|
+
const hooksDir = join19(gitDir, "swarm-hooks");
|
|
3934
4081
|
const prev = await previousHooksPath(worktree);
|
|
3935
4082
|
await mkdir(hooksDir, { recursive: true });
|
|
3936
4083
|
for (const hook of GUARD_HOOKS) {
|
|
3937
|
-
await writeFile(
|
|
3938
|
-
await chmod(
|
|
4084
|
+
await writeFile(join19(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
4085
|
+
await chmod(join19(hooksDir, hook), 493);
|
|
3939
4086
|
}
|
|
3940
4087
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3941
4088
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3954,7 +4101,7 @@ async function previousHooksPath(worktree) {
|
|
|
3954
4101
|
if (out.exitCode === 0 && value) return value;
|
|
3955
4102
|
}
|
|
3956
4103
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3957
|
-
return commonDir ?
|
|
4104
|
+
return commonDir ? join19(commonDir, "hooks") : null;
|
|
3958
4105
|
}
|
|
3959
4106
|
async function removeWorktree(repo, path) {
|
|
3960
4107
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3979,7 +4126,7 @@ async function worktreeDirty(path) {
|
|
|
3979
4126
|
async function repairMovedWorktrees(targetDir) {
|
|
3980
4127
|
const subdirs = (dir) => {
|
|
3981
4128
|
try {
|
|
3982
|
-
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) =>
|
|
4129
|
+
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join19(dir, d.name));
|
|
3983
4130
|
} catch {
|
|
3984
4131
|
return [];
|
|
3985
4132
|
}
|
|
@@ -4096,6 +4243,7 @@ var init_git = __esm({
|
|
|
4096
4243
|
var droneChannel_exports = {};
|
|
4097
4244
|
__export(droneChannel_exports, {
|
|
4098
4245
|
ChannelUnavailableError: () => ChannelUnavailableError,
|
|
4246
|
+
PATCHABLE: () => PATCHABLE,
|
|
4099
4247
|
channelDroneStore: () => channelDroneStore,
|
|
4100
4248
|
closeDroneChannel: () => closeDroneChannel,
|
|
4101
4249
|
loadChannelSecret: () => loadChannelSecret,
|
|
@@ -4108,18 +4256,18 @@ __export(droneChannel_exports, {
|
|
|
4108
4256
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
4109
4257
|
});
|
|
4110
4258
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
4111
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as
|
|
4259
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
4112
4260
|
import { dirname as dirname4 } from "path";
|
|
4113
4261
|
import { serve } from "@hono/node-server";
|
|
4114
4262
|
function loadChannelSecret(path) {
|
|
4115
4263
|
try {
|
|
4116
|
-
const s =
|
|
4264
|
+
const s = readFileSync18(path, "utf8").trim();
|
|
4117
4265
|
if (s.length >= 32) return s;
|
|
4118
4266
|
} catch {
|
|
4119
4267
|
}
|
|
4120
4268
|
const secret = randomBytes(32).toString("hex");
|
|
4121
4269
|
mkdirSync9(dirname4(path), { recursive: true });
|
|
4122
|
-
|
|
4270
|
+
writeFileSync12(path, secret, { mode: 384 });
|
|
4123
4271
|
try {
|
|
4124
4272
|
chmodSync2(path, 384);
|
|
4125
4273
|
} catch {
|
|
@@ -4179,6 +4327,16 @@ function invalidPatchValue(field, v) {
|
|
|
4179
4327
|
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)}`;
|
|
4180
4328
|
case "pr_url":
|
|
4181
4329
|
return v === null || typeof v === "string" ? null : "pr_url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439";
|
|
4330
|
+
// TASWM-532: имя ветки уходит в аргументы git у ревьюера и резолвера, поэтому проверяем
|
|
4331
|
+
// форму, а не только тип: пустая строка и пробелы внутри — это не ref, а сбой чтения.
|
|
4332
|
+
case "branch":
|
|
4333
|
+
return typeof v === "string" && v.length > 0 && v.length <= BRANCH_LEN_MAX && !BAD_IN_BRANCH.test(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0438\u043C\u044F \u0432\u0435\u0442\u043A\u0438: ${String(v).slice(0, 80)}`;
|
|
4334
|
+
case "stop_reason":
|
|
4335
|
+
return v === null || typeof v === "string" && v.length <= STOP_REASON_LEN_MAX ? null : "stop_reason \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439 (\u0438\u043B\u0438 null) \u0432 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445 \u043F\u043E\u0442\u043E\u043B\u043A\u0430 \u0434\u043B\u0438\u043D\u044B";
|
|
4336
|
+
// Словарь кодов возврата один на весь продукт (swarm-core) — проверяем его же функцией,
|
|
4337
|
+
// как verify_verdict выше, а не повторяем литералы ещё одним списком.
|
|
4338
|
+
case "return_reason":
|
|
4339
|
+
return v === null || isReturnReason(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u043A\u043E\u0434 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430: ${String(v)}`;
|
|
4182
4340
|
case "changed_paths": {
|
|
4183
4341
|
if (!Array.isArray(v)) return "changed_paths \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C \u043F\u0443\u0442\u0435\u0439";
|
|
4184
4342
|
if (v.length > CHANGED_PATHS_MAX) return `changed_paths: \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u043F\u0443\u0442\u0435\u0439 (${v.length} > ${CHANGED_PATHS_MAX})`;
|
|
@@ -4242,10 +4400,18 @@ function makeChannelHandler(deps) {
|
|
|
4242
4400
|
if (id == null) return bad(403, "\u043F\u0440\u043E\u0433\u043E\u043D \u0432\u043D\u0435 \u0441\u043A\u043E\u0443\u043F\u0430 \u043A\u0430\u043D\u0430\u043B\u0430");
|
|
4243
4401
|
const raw = p.patch ?? {};
|
|
4244
4402
|
const patch = {};
|
|
4403
|
+
const refuse = (status, why) => {
|
|
4404
|
+
log(`[\u043A\u0430\u043D\u0430\u043B] \u043F\u0430\u0442\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${id} \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D (${status}): ${why}`);
|
|
4405
|
+
try {
|
|
4406
|
+
deps.store.addEvent(id, "step", `\u043A\u0430\u043D\u0430\u043B \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u043F\u0430\u0442\u0447 \u0434\u0440\u043E\u043D\u0430 (${status}): ${why}`, null);
|
|
4407
|
+
} catch {
|
|
4408
|
+
}
|
|
4409
|
+
return bad(status, why);
|
|
4410
|
+
};
|
|
4245
4411
|
for (const [k, v] of Object.entries(raw)) {
|
|
4246
|
-
if (!PATCHABLE.has(k)) return
|
|
4412
|
+
if (!PATCHABLE.has(k)) return refuse(403, `\u043F\u043E\u043B\u0435 \xAB${k}\xBB \u043A\u0430\u043D\u0430\u043B\u0443 \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442\u0441\u044F`);
|
|
4247
4413
|
const why = invalidPatchValue(k, v);
|
|
4248
|
-
if (why) return
|
|
4414
|
+
if (why) return refuse(400, why);
|
|
4249
4415
|
patch[k] = v;
|
|
4250
4416
|
}
|
|
4251
4417
|
deps.store.updateRun(id, patch);
|
|
@@ -4438,7 +4604,7 @@ function channelDroneStore(opts) {
|
|
|
4438
4604
|
openQuestionsFor: async () => (await call("openQuestionsFor")).questions
|
|
4439
4605
|
};
|
|
4440
4606
|
}
|
|
4441
|
-
var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
|
|
4607
|
+
var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, BRANCH_LEN_MAX, STOP_REASON_LEN_MAX, BAD_IN_BRANCH, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
|
|
4442
4608
|
var init_droneChannel = __esm({
|
|
4443
4609
|
"src/droneChannel.ts"() {
|
|
4444
4610
|
"use strict";
|
|
@@ -4458,10 +4624,24 @@ var init_droneChannel = __esm({
|
|
|
4458
4624
|
// worktree), а не модель — со стороны канала это такой же самоотчёт прогона о себе, как
|
|
4459
4625
|
// pr_url. Потолки на размер — ниже, в invalidPatchValue: список идёт в гейт раздачи, и
|
|
4460
4626
|
// сорвавшийся дрон не должен уметь заявить занятым весь репозиторий.
|
|
4461
|
-
"changed_paths"
|
|
4627
|
+
"changed_paths",
|
|
4628
|
+
// TASWM-532: ФАКТИЧЕСКАЯ ветка сданного PR. Её подмешивает в патч сдачи не модель, а наш код
|
|
4629
|
+
// (droneServer.done → branchFromPrPatch по ответу `gh pr view --json headRefName`) — тот же
|
|
4630
|
+
// довод, что у changed_paths строкой выше. «Ветка и так известна из прогона» — неправда:
|
|
4631
|
+
// поле пишется на спавне из слага задачи, а дрон мог продолжать ЧУЖОЙ PR, и по этому полю
|
|
4632
|
+
// ревьюер строит worktree `origin/<branch>` (TASWM-356).
|
|
4633
|
+
"branch",
|
|
4634
|
+
// TASWM-532: возврат задачи автору (`return_task`) пишет их одним патчем со статусом
|
|
4635
|
+
// "returned" — проза для ленты задачи и код причины для распределения по бэклогу
|
|
4636
|
+
// (TASWM-505). Без них 403 ложился на возврат ровно так же, как на сдачу.
|
|
4637
|
+
"stop_reason",
|
|
4638
|
+
"return_reason"
|
|
4462
4639
|
]);
|
|
4463
4640
|
CHANGED_PATHS_MAX = 1e3;
|
|
4464
4641
|
CHANGED_PATH_LEN_MAX = 1024;
|
|
4642
|
+
BRANCH_LEN_MAX = 255;
|
|
4643
|
+
STOP_REASON_LEN_MAX = 2e3;
|
|
4644
|
+
BAD_IN_BRANCH = /[\s~^:?*[\\\p{Cc}]/u;
|
|
4465
4645
|
EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
4466
4646
|
ChannelUnavailableError = class extends Error {
|
|
4467
4647
|
constructor(downMs, message) {
|
|
@@ -4477,7 +4657,7 @@ var init_droneChannel = __esm({
|
|
|
4477
4657
|
|
|
4478
4658
|
// src/attachments.ts
|
|
4479
4659
|
import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4480
|
-
import { join as
|
|
4660
|
+
import { join as join23 } from "path";
|
|
4481
4661
|
function attachmentsFromDoc(doc, origin) {
|
|
4482
4662
|
const out = [];
|
|
4483
4663
|
const walk2 = (node) => {
|
|
@@ -4568,7 +4748,7 @@ async function materializeAttachments(opts) {
|
|
|
4568
4748
|
};
|
|
4569
4749
|
const result = { files: [], skipped: [] };
|
|
4570
4750
|
if (opts.refs.length === 0) return result;
|
|
4571
|
-
const dir =
|
|
4751
|
+
const dir = join23(opts.worktree, ATTACHMENTS_DIR);
|
|
4572
4752
|
await io.removeDir(dir);
|
|
4573
4753
|
let dirReady = false;
|
|
4574
4754
|
let total = 0;
|
|
@@ -4614,7 +4794,7 @@ async function materializeAttachments(opts) {
|
|
|
4614
4794
|
await io.mkdir(dir);
|
|
4615
4795
|
dirReady = true;
|
|
4616
4796
|
}
|
|
4617
|
-
await io.writeFile(
|
|
4797
|
+
await io.writeFile(join23(dir, name), file.bytes);
|
|
4618
4798
|
} catch (e) {
|
|
4619
4799
|
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText2(e)})`);
|
|
4620
4800
|
continue;
|
|
@@ -4767,7 +4947,7 @@ var init_attachments = __esm({
|
|
|
4767
4947
|
|
|
4768
4948
|
// src/shotUpload.ts
|
|
4769
4949
|
import { readFile as fsReadFile } from "fs/promises";
|
|
4770
|
-
import { basename as basename4, isAbsolute as isAbsolute3, join as
|
|
4950
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join24 } from "path";
|
|
4771
4951
|
function shotMimetype(path) {
|
|
4772
4952
|
const dot = path.lastIndexOf(".");
|
|
4773
4953
|
if (dot < 0) return null;
|
|
@@ -4793,7 +4973,7 @@ async function readShots(paths, opts) {
|
|
|
4793
4973
|
}
|
|
4794
4974
|
let bytes;
|
|
4795
4975
|
try {
|
|
4796
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
4976
|
+
bytes = await read(isAbsolute3(path) ? path : join24(opts.worktree, path));
|
|
4797
4977
|
} catch (e) {
|
|
4798
4978
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
4799
4979
|
continue;
|
|
@@ -4874,8 +5054,8 @@ var init_shotUpload = __esm({
|
|
|
4874
5054
|
|
|
4875
5055
|
// src/oauth.ts
|
|
4876
5056
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
4877
|
-
import { readFileSync as
|
|
4878
|
-
import { join as
|
|
5057
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13, unlinkSync as unlinkSync4 } from "fs";
|
|
5058
|
+
import { join as join25 } from "path";
|
|
4879
5059
|
function makePkce() {
|
|
4880
5060
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4881
5061
|
const challenge = createHash3("sha256").update(verifier).digest("base64url");
|
|
@@ -4959,13 +5139,13 @@ function toStore(clientId, t, now) {
|
|
|
4959
5139
|
}
|
|
4960
5140
|
function loadStore() {
|
|
4961
5141
|
try {
|
|
4962
|
-
return JSON.parse(
|
|
5142
|
+
return JSON.parse(readFileSync19(STORE_PATH2, "utf8"));
|
|
4963
5143
|
} catch {
|
|
4964
5144
|
return null;
|
|
4965
5145
|
}
|
|
4966
5146
|
}
|
|
4967
5147
|
function saveStore(s) {
|
|
4968
|
-
|
|
5148
|
+
writeFileSync13(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
4969
5149
|
}
|
|
4970
5150
|
function clearStore() {
|
|
4971
5151
|
try {
|
|
@@ -4975,7 +5155,7 @@ function clearStore() {
|
|
|
4975
5155
|
}
|
|
4976
5156
|
function loadClientId() {
|
|
4977
5157
|
try {
|
|
4978
|
-
return JSON.parse(
|
|
5158
|
+
return JSON.parse(readFileSync19(CLIENT_PATH, "utf8")).clientId;
|
|
4979
5159
|
} catch {
|
|
4980
5160
|
return null;
|
|
4981
5161
|
}
|
|
@@ -4984,7 +5164,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
4984
5164
|
const existing = loadClientId();
|
|
4985
5165
|
if (existing) return existing;
|
|
4986
5166
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
4987
|
-
|
|
5167
|
+
writeFileSync13(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
4988
5168
|
return clientId;
|
|
4989
5169
|
}
|
|
4990
5170
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -5010,8 +5190,8 @@ var init_oauth = __esm({
|
|
|
5010
5190
|
AUTHORIZE = "/api/oauth/authorize";
|
|
5011
5191
|
TOKEN = "/api/oauth/token";
|
|
5012
5192
|
SCOPE = "openid profile email taskless:full";
|
|
5013
|
-
STORE_PATH2 =
|
|
5014
|
-
CLIENT_PATH =
|
|
5193
|
+
STORE_PATH2 = join25(SWARM_HOME, "oauth.json");
|
|
5194
|
+
CLIENT_PATH = join25(SWARM_HOME, "oauth-client.json");
|
|
5015
5195
|
base2 = (api) => api.replace(/\/$/, "");
|
|
5016
5196
|
}
|
|
5017
5197
|
});
|
|
@@ -5057,11 +5237,11 @@ var init_authState = __esm({
|
|
|
5057
5237
|
});
|
|
5058
5238
|
|
|
5059
5239
|
// src/workspace.ts
|
|
5060
|
-
import { readFileSync as
|
|
5061
|
-
import { join as
|
|
5240
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5 } from "fs";
|
|
5241
|
+
import { join as join26 } from "path";
|
|
5062
5242
|
function loadWorkspace() {
|
|
5063
5243
|
try {
|
|
5064
|
-
const w = JSON.parse(
|
|
5244
|
+
const w = JSON.parse(readFileSync20(WORKSPACE_PATH, "utf8"));
|
|
5065
5245
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
5066
5246
|
} catch {
|
|
5067
5247
|
return null;
|
|
@@ -5075,7 +5255,7 @@ var init_workspace = __esm({
|
|
|
5075
5255
|
"src/workspace.ts"() {
|
|
5076
5256
|
"use strict";
|
|
5077
5257
|
init_config();
|
|
5078
|
-
WORKSPACE_PATH =
|
|
5258
|
+
WORKSPACE_PATH = join26(SWARM_HOME, "workspace.json");
|
|
5079
5259
|
}
|
|
5080
5260
|
});
|
|
5081
5261
|
|
|
@@ -5656,7 +5836,7 @@ __export(db_exports, {
|
|
|
5656
5836
|
});
|
|
5657
5837
|
import { createRequire as createRequire2 } from "module";
|
|
5658
5838
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5659
|
-
import { existsSync as
|
|
5839
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5660
5840
|
import { resolve as resolve3 } from "path";
|
|
5661
5841
|
function rowToQuestion(row) {
|
|
5662
5842
|
return {
|
|
@@ -6279,7 +6459,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6279
6459
|
const repoN = normalizeRepo(repo);
|
|
6280
6460
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
6281
6461
|
if (already) return { imported: false, runs: already.run_count };
|
|
6282
|
-
if (!
|
|
6462
|
+
if (!existsSync15(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
6283
6463
|
const src = new this.databaseCtor(abs);
|
|
6284
6464
|
try {
|
|
6285
6465
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -6648,8 +6828,8 @@ var init_droneStore = __esm({
|
|
|
6648
6828
|
|
|
6649
6829
|
// src/overmind.ts
|
|
6650
6830
|
import { homedir as homedir6 } from "os";
|
|
6651
|
-
import { existsSync as
|
|
6652
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
6831
|
+
import { existsSync as existsSync20, readFileSync as readFileSync24, readdirSync as readdirSync13, renameSync as renameSync12 } from "fs";
|
|
6832
|
+
import { basename as basename6, dirname as dirname5, join as join33 } from "path";
|
|
6653
6833
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
6654
6834
|
import { spawn as spawn4 } from "child_process";
|
|
6655
6835
|
|
|
@@ -9972,6 +10152,7 @@ function loadHostIdentity(deps) {
|
|
|
9972
10152
|
}
|
|
9973
10153
|
|
|
9974
10154
|
// src/overmind.ts
|
|
10155
|
+
init_src();
|
|
9975
10156
|
init_resources();
|
|
9976
10157
|
|
|
9977
10158
|
// src/relief.ts
|
|
@@ -9988,7 +10169,13 @@ var DEFAULT_RELIEF_CONFIG = {
|
|
|
9988
10169
|
function resolveReliefConfig(cfg) {
|
|
9989
10170
|
return { ...DEFAULT_RELIEF_CONFIG, ...cfg ?? {} };
|
|
9990
10171
|
}
|
|
9991
|
-
var RELIEF_PAUSE_REASON = `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM
|
|
10172
|
+
var RELIEF_PAUSE_REASON = `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM), \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043F\u043E\u0441\u043B\u0435 \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 (TASWM-151)`;
|
|
10173
|
+
function reliefPauseReason(canContinue) {
|
|
10174
|
+
return canContinue ? `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 --continue: worktree \u0438 \u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435)` : `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C \u2014 worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0441\u0442\u0430\u0440\u0442\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E)`;
|
|
10175
|
+
}
|
|
10176
|
+
function isReliefPause(stopReason) {
|
|
10177
|
+
return !!stopReason && stopReason.startsWith(RELIEF_PAUSE_MARK);
|
|
10178
|
+
}
|
|
9992
10179
|
function reliefResumeReason(mode) {
|
|
9993
10180
|
return mode === "continue" ? `${RELIEF_PAUSE_MARK} \u043E\u0442\u043F\u0443\u0441\u0442\u0438\u043B\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 (--continue: \u0440\u0430\u0431\u043E\u0442\u0430 \u0432 origin + \u0441\u0435\u0441\u0441\u0438\u044F \u0446\u0435\u043B\u044B)` : `${RELIEF_PAUSE_MARK} \u043E\u0442\u043F\u0443\u0441\u0442\u0438\u043B\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443, \u0441\u0442\u0430\u0440\u0442 \u0437\u0430\u043D\u043E\u0432\u043E (\u0441\u0435\u0441\u0441\u0438\u044F/worktree \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B)`;
|
|
9994
10181
|
}
|
|
@@ -9999,9 +10186,15 @@ function pauseDue(reliefSince, lastPauseAt, now, cfg) {
|
|
|
9999
10186
|
if (reliefSince == null || now - reliefSince < cfg.sustainMs) return false;
|
|
10000
10187
|
return lastPauseAt == null || now - lastPauseAt >= cfg.pauseWindowMs;
|
|
10001
10188
|
}
|
|
10002
|
-
function pickPauseCandidate(runs) {
|
|
10189
|
+
function pickPauseCandidate(runs, protectedRunIds) {
|
|
10003
10190
|
const candidates = runs.filter(
|
|
10004
|
-
(r) => r.kind === "drone" && r.status === "running" && r.pid != null && r.resolves_run_id == null && r.reviews_run_id == null
|
|
10191
|
+
(r) => r.kind === "drone" && r.status === "running" && r.pid != null && r.resolves_run_id == null && r.reviews_run_id == null && // TASWM-535: прогон, признанный ТЯЖЁЛЫМ, разгрузка не снимает. Правильная реакция на
|
|
10192
|
+
// «тяжело» — уменьшить число соседей, а не убить того, кто работает: 27.08 перегруженная
|
|
10193
|
+
// машина снимала дрона разгрузкой, он начинал заново, и так задача сожгла $20.01
|
|
10194
|
+
// кругами (TASWM-531). Тяжёлого паузим только тогда, когда паузить больше некого, —
|
|
10195
|
+
// нет: не паузим вовсе. Освобождает машину придержанный интейк (соседи доигрывают и не
|
|
10196
|
+
// заменяются новыми), а не снятие того, кто ближе всех к сдаче.
|
|
10197
|
+
!protectedRunIds?.has(r.id)
|
|
10005
10198
|
);
|
|
10006
10199
|
if (!candidates.length) return null;
|
|
10007
10200
|
return candidates.reduce((youngest, r) => r.created_at > youngest.created_at ? r : youngest);
|
|
@@ -10012,8 +10205,117 @@ function pickResumeCandidate(pausedForRelief) {
|
|
|
10012
10205
|
return candidates.reduce((oldest, r) => r.updated_at < oldest.updated_at ? r : oldest);
|
|
10013
10206
|
}
|
|
10014
10207
|
|
|
10208
|
+
// src/reliefLoop.ts
|
|
10209
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
10210
|
+
import { join as join14 } from "path";
|
|
10211
|
+
var numEnv3 = (name, def) => {
|
|
10212
|
+
const v = Number(process.env[name]);
|
|
10213
|
+
return Number.isFinite(v) && v > 0 ? v : def;
|
|
10214
|
+
};
|
|
10215
|
+
var DEFAULT_RELIEF_LOOP_CONFIG = {
|
|
10216
|
+
limit: numEnv3("SWARM_RELIEF_LOOP_LIMIT", 3),
|
|
10217
|
+
backoffMaxMs: numEnv3("SWARM_RELIEF_BACKOFF_MAX_MS", 10 * 6e4),
|
|
10218
|
+
seriesTtlMs: numEnv3("SWARM_RELIEF_LOOP_TTL_MS", 6 * 60 * 6e4)
|
|
10219
|
+
};
|
|
10220
|
+
function resolveReliefLoopConfig(cfg) {
|
|
10221
|
+
return { ...DEFAULT_RELIEF_LOOP_CONFIG, ...cfg ?? {} };
|
|
10222
|
+
}
|
|
10223
|
+
function reliefLoopKey(repo, slug) {
|
|
10224
|
+
return `${repo}#${slug}`;
|
|
10225
|
+
}
|
|
10226
|
+
function recordReliefLoopPause(state, key, now, delivered, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10227
|
+
const prev = state[key];
|
|
10228
|
+
const continues = !!prev && !delivered && now - prev.lastPauseAt <= cfg.seriesTtlMs;
|
|
10229
|
+
const entry = continues ? { pauses: prev.pauses + 1, lastPauseAt: now, ...prev.escalated ? { escalated: true } : {} } : { pauses: 1, lastPauseAt: now };
|
|
10230
|
+
state[key] = entry;
|
|
10231
|
+
return entry;
|
|
10232
|
+
}
|
|
10233
|
+
function clearReliefLoop(state, key) {
|
|
10234
|
+
delete state[key];
|
|
10235
|
+
}
|
|
10236
|
+
function reliefBackoffMs(pauses, baseMs, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10237
|
+
if (pauses <= 1) return baseMs;
|
|
10238
|
+
const grown = baseMs * 2 ** Math.min(pauses - 1, 30);
|
|
10239
|
+
return Math.min(grown, cfg.backoffMaxMs);
|
|
10240
|
+
}
|
|
10241
|
+
function reliefLoopTripped(entry, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10242
|
+
return !!entry && entry.pauses >= cfg.limit;
|
|
10243
|
+
}
|
|
10244
|
+
function reliefLoopStatePath(home) {
|
|
10245
|
+
return join14(home, "relief-loop.json");
|
|
10246
|
+
}
|
|
10247
|
+
function loadReliefLoopState(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10248
|
+
const exists = io.exists ?? existsSync10;
|
|
10249
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync13(p, "utf8"));
|
|
10250
|
+
const path = reliefLoopStatePath(home);
|
|
10251
|
+
if (!exists(path)) return {};
|
|
10252
|
+
try {
|
|
10253
|
+
const raw = JSON.parse(readFile2(path));
|
|
10254
|
+
const out = {};
|
|
10255
|
+
for (const [key, value] of Object.entries(raw ?? {})) {
|
|
10256
|
+
const v = value;
|
|
10257
|
+
const pauses = Number(v?.pauses);
|
|
10258
|
+
const lastPauseAt = Number(v?.lastPauseAt);
|
|
10259
|
+
if (!Number.isFinite(pauses) || pauses <= 0 || !Number.isFinite(lastPauseAt)) continue;
|
|
10260
|
+
if (now - lastPauseAt > cfg.seriesTtlMs) continue;
|
|
10261
|
+
out[key] = { pauses, lastPauseAt, ...v?.escalated ? { escalated: true } : {} };
|
|
10262
|
+
}
|
|
10263
|
+
return out;
|
|
10264
|
+
} catch {
|
|
10265
|
+
return {};
|
|
10266
|
+
}
|
|
10267
|
+
}
|
|
10268
|
+
function saveReliefLoopState(home, state, io = {}) {
|
|
10269
|
+
const path = reliefLoopStatePath(home);
|
|
10270
|
+
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
10271
|
+
writeFileSync11(`${p}.tmp`, data);
|
|
10272
|
+
renameSync8(`${p}.tmp`, p);
|
|
10273
|
+
});
|
|
10274
|
+
writeFile4(path, JSON.stringify(state, null, 2));
|
|
10275
|
+
}
|
|
10276
|
+
var ReliefLoopStore = class {
|
|
10277
|
+
constructor(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10278
|
+
this.home = home;
|
|
10279
|
+
this.io = io;
|
|
10280
|
+
this.cfg = cfg;
|
|
10281
|
+
this.state = loadReliefLoopState(home, now, io, cfg);
|
|
10282
|
+
}
|
|
10283
|
+
home;
|
|
10284
|
+
io;
|
|
10285
|
+
cfg;
|
|
10286
|
+
state;
|
|
10287
|
+
get(key) {
|
|
10288
|
+
return this.state[key];
|
|
10289
|
+
}
|
|
10290
|
+
/** Учесть снятие и сразу сохранить. Возвращает запись после учёта. */
|
|
10291
|
+
recordPause(key, now, delivered) {
|
|
10292
|
+
const entry = recordReliefLoopPause(this.state, key, now, delivered, this.cfg);
|
|
10293
|
+
this.flush();
|
|
10294
|
+
return entry;
|
|
10295
|
+
}
|
|
10296
|
+
/** Пометить, что человека уже позвали — чтобы не звать на каждом тике. */
|
|
10297
|
+
markEscalated(key) {
|
|
10298
|
+
const entry = this.state[key];
|
|
10299
|
+
if (!entry || entry.escalated) return;
|
|
10300
|
+
entry.escalated = true;
|
|
10301
|
+
this.flush();
|
|
10302
|
+
}
|
|
10303
|
+
clear(key) {
|
|
10304
|
+
if (!(key in this.state)) return;
|
|
10305
|
+
clearReliefLoop(this.state, key);
|
|
10306
|
+
this.flush();
|
|
10307
|
+
}
|
|
10308
|
+
flush() {
|
|
10309
|
+
try {
|
|
10310
|
+
saveReliefLoopState(this.home, this.state, this.io);
|
|
10311
|
+
} catch (e) {
|
|
10312
|
+
console.warn("[overmind] \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043F\u0435\u0442\u043B\u0438 \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0438 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C:", e);
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
};
|
|
10316
|
+
|
|
10015
10317
|
// src/reliefCost.ts
|
|
10016
|
-
function
|
|
10318
|
+
function median2(xs) {
|
|
10017
10319
|
const s = [...xs].sort((a, b) => a - b);
|
|
10018
10320
|
return s[Math.floor((s.length - 1) / 2)];
|
|
10019
10321
|
}
|
|
@@ -10069,7 +10371,7 @@ function computeReliefCost(state, now, windowMs) {
|
|
|
10069
10371
|
pauses: inWindow.length,
|
|
10070
10372
|
runs: byRun.size,
|
|
10071
10373
|
lostMs,
|
|
10072
|
-
stepsBetween: gaps.length ?
|
|
10374
|
+
stepsBetween: gaps.length ? median2(gaps) : null,
|
|
10073
10375
|
worst
|
|
10074
10376
|
};
|
|
10075
10377
|
}
|
|
@@ -10128,15 +10430,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
10128
10430
|
|
|
10129
10431
|
// src/launcher.ts
|
|
10130
10432
|
import { spawn as spawn2 } from "child_process";
|
|
10131
|
-
import { closeSync as closeSync3, existsSync as
|
|
10433
|
+
import { closeSync as closeSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
|
|
10132
10434
|
import { createRequire } from "module";
|
|
10133
10435
|
import { homedir as homedir5 } from "os";
|
|
10134
|
-
import { dirname as dirname3, join as
|
|
10436
|
+
import { dirname as dirname3, join as join16 } from "path";
|
|
10135
10437
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10136
10438
|
|
|
10137
10439
|
// src/droneHome.ts
|
|
10138
10440
|
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
10139
|
-
import { join as
|
|
10441
|
+
import { join as join15, relative, sep } from "path";
|
|
10140
10442
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
10141
10443
|
function droneHomeDeny(env = process.env) {
|
|
10142
10444
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -10160,20 +10462,20 @@ function buildDroneHome(opts) {
|
|
|
10160
10462
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
10161
10463
|
for (const existing of readdirSync7(droneHome)) {
|
|
10162
10464
|
if (wanted.has(existing)) continue;
|
|
10163
|
-
const p =
|
|
10465
|
+
const p = join15(droneHome, existing);
|
|
10164
10466
|
try {
|
|
10165
10467
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
10166
10468
|
} catch {
|
|
10167
10469
|
}
|
|
10168
10470
|
}
|
|
10169
10471
|
for (const name of wanted) {
|
|
10170
|
-
const link2 =
|
|
10472
|
+
const link2 = join15(droneHome, name);
|
|
10171
10473
|
try {
|
|
10172
10474
|
lstatSync(link2);
|
|
10173
10475
|
continue;
|
|
10174
10476
|
} catch {
|
|
10175
10477
|
}
|
|
10176
|
-
const target =
|
|
10478
|
+
const target = join15(realHome, name);
|
|
10177
10479
|
let isDir;
|
|
10178
10480
|
try {
|
|
10179
10481
|
isDir = statSync7(target).isDirectory();
|
|
@@ -10194,7 +10496,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
10194
10496
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
10195
10497
|
];
|
|
10196
10498
|
function realPathEnvPins(realHome) {
|
|
10197
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
10499
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join15(realHome, ...segments)]));
|
|
10198
10500
|
}
|
|
10199
10501
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
10200
10502
|
return [
|
|
@@ -10293,8 +10595,8 @@ function resolveTsxCli() {
|
|
|
10293
10595
|
}
|
|
10294
10596
|
}
|
|
10295
10597
|
var MCP_SERVER_NAME = "overmind";
|
|
10296
|
-
function launchBudgetUsd(limit, spentUsd, env = process.env) {
|
|
10297
|
-
return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
|
|
10598
|
+
function launchBudgetUsd(limit, spentUsd, env = process.env, weightEvents = []) {
|
|
10599
|
+
return remainingRunBudgetUsd(effectiveCostCeilingUsd(limit ?? envCapUsd(env), weightEvents), spentUsd);
|
|
10298
10600
|
}
|
|
10299
10601
|
function envCapUsd(env) {
|
|
10300
10602
|
const raw = env.SWARM_MAX_COST_USD;
|
|
@@ -10306,7 +10608,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
10306
10608
|
const env = { ...base3 };
|
|
10307
10609
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
10308
10610
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
10309
|
-
env.TURBO_CACHE_DIR =
|
|
10611
|
+
env.TURBO_CACHE_DIR = join16(worktree, ".turbo", "cache");
|
|
10310
10612
|
}
|
|
10311
10613
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
10312
10614
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -10334,13 +10636,13 @@ var DAEMON_STATE_ENV = [
|
|
|
10334
10636
|
"TASKLESS_TOKEN"
|
|
10335
10637
|
];
|
|
10336
10638
|
function bundledRoleDir(kind) {
|
|
10337
|
-
return
|
|
10639
|
+
return join16(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
10338
10640
|
}
|
|
10339
10641
|
function roleDir(worktree, kind) {
|
|
10340
|
-
return
|
|
10642
|
+
return join16(worktree, ".claude", "roles", kind);
|
|
10341
10643
|
}
|
|
10342
10644
|
function roleDirs(worktree, kind) {
|
|
10343
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
10645
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync11(dir));
|
|
10344
10646
|
}
|
|
10345
10647
|
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
10346
10648
|
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
@@ -10356,7 +10658,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
10356
10658
|
const m = /^(\d+)\.json/.exec(f);
|
|
10357
10659
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
10358
10660
|
}
|
|
10359
|
-
return
|
|
10661
|
+
return join16(dir, `${next}.json`);
|
|
10360
10662
|
}
|
|
10361
10663
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
10362
10664
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -10439,9 +10741,9 @@ function makeLauncher(cfg) {
|
|
|
10439
10741
|
}
|
|
10440
10742
|
const realHome = cfg.droneIsolation?.realHome ?? homedir5();
|
|
10441
10743
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
10442
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
10744
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync11(join16(cfg.droneIsolation.swarmHome, "token"))) {
|
|
10443
10745
|
console.error(
|
|
10444
|
-
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${
|
|
10746
|
+
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join16(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
|
|
10445
10747
|
);
|
|
10446
10748
|
}
|
|
10447
10749
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10460,7 +10762,7 @@ function makeLauncher(cfg) {
|
|
|
10460
10762
|
// момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
|
|
10461
10763
|
// стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
|
|
10462
10764
|
// ни жёг, — отсюда и $74 при лимите $20.
|
|
10463
|
-
maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
|
|
10765
|
+
maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd, process.env, cfg.weightEvents?.(run) ?? []),
|
|
10464
10766
|
// TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
|
|
10465
10767
|
effort
|
|
10466
10768
|
});
|
|
@@ -10502,8 +10804,8 @@ function makeLauncher(cfg) {
|
|
|
10502
10804
|
init_procCleanup();
|
|
10503
10805
|
|
|
10504
10806
|
// src/cost.ts
|
|
10505
|
-
import { readFileSync as
|
|
10506
|
-
import { join as
|
|
10807
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync9, renameSync as renameSync9 } from "fs";
|
|
10808
|
+
import { join as join17 } from "path";
|
|
10507
10809
|
init_executors();
|
|
10508
10810
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
10509
10811
|
function unprocessedLogFiles(dir) {
|
|
@@ -10525,10 +10827,10 @@ function accountRunCost(deps, run) {
|
|
|
10525
10827
|
let durationMs = 0;
|
|
10526
10828
|
const processed = [];
|
|
10527
10829
|
for (const f of files) {
|
|
10528
|
-
const full =
|
|
10830
|
+
const full = join17(dir, f);
|
|
10529
10831
|
let raw = null;
|
|
10530
10832
|
try {
|
|
10531
|
-
raw =
|
|
10833
|
+
raw = readFileSync14(full, "utf8");
|
|
10532
10834
|
} catch {
|
|
10533
10835
|
continue;
|
|
10534
10836
|
}
|
|
@@ -10554,7 +10856,7 @@ function accountRunCost(deps, run) {
|
|
|
10554
10856
|
}
|
|
10555
10857
|
for (const full of processed) {
|
|
10556
10858
|
try {
|
|
10557
|
-
|
|
10859
|
+
renameSync9(full, `${full}.done`);
|
|
10558
10860
|
} catch {
|
|
10559
10861
|
}
|
|
10560
10862
|
}
|
|
@@ -10577,8 +10879,8 @@ function makeCostSweeper(deps) {
|
|
|
10577
10879
|
}
|
|
10578
10880
|
|
|
10579
10881
|
// src/runOutcome.ts
|
|
10580
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
10581
|
-
import { join as
|
|
10882
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync15 } from "fs";
|
|
10883
|
+
import { join as join18 } from "path";
|
|
10582
10884
|
init_executors();
|
|
10583
10885
|
function endedItself(outcome) {
|
|
10584
10886
|
return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
|
|
@@ -10605,7 +10907,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
|
10605
10907
|
}
|
|
10606
10908
|
if (!latest) return null;
|
|
10607
10909
|
try {
|
|
10608
|
-
return
|
|
10910
|
+
return readFileSync15(join18(dir, latest.name), "utf8");
|
|
10609
10911
|
} catch {
|
|
10610
10912
|
return null;
|
|
10611
10913
|
}
|
|
@@ -10638,20 +10940,20 @@ init_src();
|
|
|
10638
10940
|
init_git();
|
|
10639
10941
|
|
|
10640
10942
|
// src/deps.ts
|
|
10641
|
-
import { existsSync as
|
|
10642
|
-
import { join as
|
|
10943
|
+
import { existsSync as existsSync14, readdirSync as readdirSync12, readFileSync as readFileSync17 } from "fs";
|
|
10944
|
+
import { join as join22 } from "path";
|
|
10643
10945
|
import { execa } from "execa";
|
|
10644
10946
|
|
|
10645
10947
|
// src/prepareLock.ts
|
|
10646
10948
|
import { createHash } from "crypto";
|
|
10647
10949
|
import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
|
|
10648
|
-
import { join as
|
|
10950
|
+
import { join as join20 } from "path";
|
|
10649
10951
|
import { tmpdir } from "os";
|
|
10650
10952
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
10651
10953
|
var POLL_MS = 300;
|
|
10652
10954
|
var STALE_MS = 20 * 6e4;
|
|
10653
10955
|
function machineLockPath(key, dir = tmpdir()) {
|
|
10654
|
-
return
|
|
10956
|
+
return join20(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
10655
10957
|
}
|
|
10656
10958
|
function defaultTryCreate(path, content) {
|
|
10657
10959
|
try {
|
|
@@ -10712,8 +11014,8 @@ init_config();
|
|
|
10712
11014
|
init_config();
|
|
10713
11015
|
import { createHash as createHash2 } from "crypto";
|
|
10714
11016
|
import { cp, link, lstat, mkdir as mkdir2, readdir, readlink, rename, rm as rm2, stat, symlink, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
10715
|
-
import { existsSync as
|
|
10716
|
-
import { join as
|
|
11017
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
11018
|
+
import { join as join21 } from "path";
|
|
10717
11019
|
function fingerprint(parts) {
|
|
10718
11020
|
const h = createHash2("sha256");
|
|
10719
11021
|
for (const p of parts) {
|
|
@@ -10727,8 +11029,8 @@ async function cloneTree(src, dst) {
|
|
|
10727
11029
|
const entries = await readdir(src, { withFileTypes: true });
|
|
10728
11030
|
await Promise.all(
|
|
10729
11031
|
entries.map(async (e) => {
|
|
10730
|
-
const from =
|
|
10731
|
-
const to =
|
|
11032
|
+
const from = join21(src, e.name);
|
|
11033
|
+
const to = join21(dst, e.name);
|
|
10732
11034
|
if (e.isDirectory()) return cloneTree(from, to);
|
|
10733
11035
|
if (e.isSymbolicLink()) {
|
|
10734
11036
|
return symlink(await readlink(from), to).catch(() => cp(from, to, { recursive: true, verbatimSymlinks: true }));
|
|
@@ -10741,11 +11043,11 @@ function cacheRoot(io) {
|
|
|
10741
11043
|
return io.root ?? ENV_CACHE_DIR;
|
|
10742
11044
|
}
|
|
10743
11045
|
function snapshotDir(io, key) {
|
|
10744
|
-
return
|
|
11046
|
+
return join21(cacheRoot(io), key);
|
|
10745
11047
|
}
|
|
10746
11048
|
function readManifest(dir) {
|
|
10747
11049
|
try {
|
|
10748
|
-
const parsed = JSON.parse(
|
|
11050
|
+
const parsed = JSON.parse(readFileSync16(join21(dir, "manifest.json"), "utf8"));
|
|
10749
11051
|
if (!parsed || typeof parsed !== "object") return null;
|
|
10750
11052
|
const m = parsed;
|
|
10751
11053
|
if (typeof m.key !== "string" || !Array.isArray(m.dirs)) return null;
|
|
@@ -10757,11 +11059,11 @@ function readManifest(dir) {
|
|
|
10757
11059
|
}
|
|
10758
11060
|
}
|
|
10759
11061
|
function hasEnvSnapshot(key, io = {}) {
|
|
10760
|
-
const exists = io.exists ??
|
|
10761
|
-
return exists(
|
|
11062
|
+
const exists = io.exists ?? existsSync13;
|
|
11063
|
+
return exists(join21(snapshotDir(io, key), "manifest.json"));
|
|
10762
11064
|
}
|
|
10763
11065
|
async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
10764
|
-
const exists = io.exists ??
|
|
11066
|
+
const exists = io.exists ?? existsSync13;
|
|
10765
11067
|
const clone = io.clone ?? cloneTree;
|
|
10766
11068
|
const dir = snapshotDir(io, key);
|
|
10767
11069
|
const manifest = readManifest(dir);
|
|
@@ -10770,13 +11072,13 @@ async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
|
10770
11072
|
try {
|
|
10771
11073
|
for (let i = 0; i < manifest.dirs.length; i++) {
|
|
10772
11074
|
const rel = manifest.dirs[i] ?? "";
|
|
10773
|
-
const target =
|
|
11075
|
+
const target = join21(worktree, rel);
|
|
10774
11076
|
if (exists(target)) continue;
|
|
10775
|
-
await clone(
|
|
11077
|
+
await clone(join21(dir, "trees", String(i)), target);
|
|
10776
11078
|
made.push(rel);
|
|
10777
11079
|
}
|
|
10778
11080
|
} catch {
|
|
10779
|
-
for (const rel of made) await rm2(
|
|
11081
|
+
for (const rel of made) await rm2(join21(worktree, rel), { recursive: true, force: true }).catch(() => {
|
|
10780
11082
|
});
|
|
10781
11083
|
return null;
|
|
10782
11084
|
}
|
|
@@ -10789,19 +11091,19 @@ async function touch(dir) {
|
|
|
10789
11091
|
});
|
|
10790
11092
|
}
|
|
10791
11093
|
async function saveEnvSnapshot(worktree, key, dirs, io = {}) {
|
|
10792
|
-
const exists = io.exists ??
|
|
11094
|
+
const exists = io.exists ?? existsSync13;
|
|
10793
11095
|
const clone = io.clone ?? cloneTree;
|
|
10794
11096
|
if (hasEnvSnapshot(key, io)) return true;
|
|
10795
|
-
const present = dirs.filter((rel) => exists(
|
|
11097
|
+
const present = dirs.filter((rel) => exists(join21(worktree, rel)));
|
|
10796
11098
|
if (!present.length) return false;
|
|
10797
11099
|
const root = cacheRoot(io);
|
|
10798
|
-
const tmp =
|
|
11100
|
+
const tmp = join21(root, `.tmp-${key}-${process.pid}`);
|
|
10799
11101
|
try {
|
|
10800
11102
|
await rm2(tmp, { recursive: true, force: true });
|
|
10801
|
-
await mkdir2(
|
|
10802
|
-
for (let i = 0; i < present.length; i++) await clone(
|
|
11103
|
+
await mkdir2(join21(tmp, "trees"), { recursive: true });
|
|
11104
|
+
for (let i = 0; i < present.length; i++) await clone(join21(worktree, present[i] ?? ""), join21(tmp, "trees", String(i)));
|
|
10803
11105
|
const manifest = { key, dirs: present, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10804
|
-
await writeFile2(
|
|
11106
|
+
await writeFile2(join21(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
10805
11107
|
await rename(tmp, snapshotDir(io, key));
|
|
10806
11108
|
return true;
|
|
10807
11109
|
} catch {
|
|
@@ -10821,13 +11123,13 @@ async function pruneEnvSnapshots(io = {}) {
|
|
|
10821
11123
|
}
|
|
10822
11124
|
const withTime = [];
|
|
10823
11125
|
for (const name of names) {
|
|
10824
|
-
const s = await stat(
|
|
11126
|
+
const s = await stat(join21(root, name)).catch(() => null);
|
|
10825
11127
|
if (s) withTime.push({ name, mtime: s.mtimeMs });
|
|
10826
11128
|
}
|
|
10827
11129
|
withTime.sort((a, b) => b.mtime - a.mtime);
|
|
10828
11130
|
let removed = 0;
|
|
10829
11131
|
for (const { name } of withTime.slice(Math.max(0, keep))) {
|
|
10830
|
-
await rm2(
|
|
11132
|
+
await rm2(join21(root, name), { recursive: true, force: true }).catch(() => {
|
|
10831
11133
|
});
|
|
10832
11134
|
removed++;
|
|
10833
11135
|
}
|
|
@@ -10843,9 +11145,9 @@ async function sweepEnvCacheTemp(io = {}) {
|
|
|
10843
11145
|
}
|
|
10844
11146
|
let removed = 0;
|
|
10845
11147
|
for (const name of names) {
|
|
10846
|
-
const s = await lstat(
|
|
11148
|
+
const s = await lstat(join21(root, name)).catch(() => null);
|
|
10847
11149
|
if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
|
|
10848
|
-
await rm2(
|
|
11150
|
+
await rm2(join21(root, name), { recursive: true, force: true }).catch(() => {
|
|
10849
11151
|
});
|
|
10850
11152
|
removed++;
|
|
10851
11153
|
}
|
|
@@ -10872,7 +11174,7 @@ async function runCommand(cmd, worktree) {
|
|
|
10872
11174
|
}
|
|
10873
11175
|
function safeReadText(p) {
|
|
10874
11176
|
try {
|
|
10875
|
-
return
|
|
11177
|
+
return readFileSync17(p, "utf8");
|
|
10876
11178
|
} catch {
|
|
10877
11179
|
return null;
|
|
10878
11180
|
}
|
|
@@ -10912,21 +11214,21 @@ function overridePlan(override) {
|
|
|
10912
11214
|
};
|
|
10913
11215
|
}
|
|
10914
11216
|
function detectPackageManager(worktree, io = {}) {
|
|
10915
|
-
const exists = io.exists ??
|
|
11217
|
+
const exists = io.exists ?? existsSync14;
|
|
10916
11218
|
const readText = io.readText ?? safeReadText;
|
|
10917
|
-
const hasPkgJson = exists(
|
|
11219
|
+
const hasPkgJson = exists(join22(worktree, "package.json"));
|
|
10918
11220
|
if (!hasPkgJson) {
|
|
10919
|
-
if (exists(
|
|
11221
|
+
if (exists(join22(worktree, "deno.json")) || exists(join22(worktree, "deno.jsonc")) || exists(join22(worktree, "deno.lock"))) return "deno";
|
|
10920
11222
|
return null;
|
|
10921
11223
|
}
|
|
10922
|
-
const pkg = readJson(readText,
|
|
11224
|
+
const pkg = readJson(readText, join22(worktree, "package.json"));
|
|
10923
11225
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
10924
11226
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
10925
|
-
if (exists(
|
|
10926
|
-
if (exists(
|
|
10927
|
-
if (exists(
|
|
10928
|
-
if (exists(
|
|
10929
|
-
if (exists(
|
|
11227
|
+
if (exists(join22(worktree, "bun.lock")) || exists(join22(worktree, "bun.lockb"))) return "bun";
|
|
11228
|
+
if (exists(join22(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
11229
|
+
if (exists(join22(worktree, "yarn.lock"))) return "yarn";
|
|
11230
|
+
if (exists(join22(worktree, "package-lock.json"))) return "npm";
|
|
11231
|
+
if (exists(join22(worktree, "deno.lock"))) return "deno";
|
|
10930
11232
|
return "npm";
|
|
10931
11233
|
}
|
|
10932
11234
|
function jsInstallCommand(pm) {
|
|
@@ -10982,7 +11284,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
10982
11284
|
// конфигурация — дело сборки, не подготовки
|
|
10983
11285
|
];
|
|
10984
11286
|
function detectPlan(worktree, io) {
|
|
10985
|
-
const exists = io.exists ??
|
|
11287
|
+
const exists = io.exists ?? existsSync14;
|
|
10986
11288
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
10987
11289
|
const pm = detectPackageManager(worktree, io);
|
|
10988
11290
|
if (pm) {
|
|
@@ -10990,7 +11292,7 @@ function detectPlan(worktree, io) {
|
|
|
10990
11292
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
10991
11293
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
10992
11294
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
10993
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
11295
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join22(worktree, "node_modules"), source: "detect" }
|
|
10994
11296
|
];
|
|
10995
11297
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
10996
11298
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -10999,12 +11301,12 @@ function detectPlan(worktree, io) {
|
|
|
10999
11301
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
11000
11302
|
}
|
|
11001
11303
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
11002
|
-
const hit = marker.files.some((f) => exists(
|
|
11304
|
+
const hit = marker.files.some((f) => exists(join22(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
11003
11305
|
if (!hit) continue;
|
|
11004
11306
|
let ecosystem = marker.ecosystem;
|
|
11005
11307
|
let command = marker.command;
|
|
11006
11308
|
for (const refine of marker.refine ?? []) {
|
|
11007
|
-
if (!exists(
|
|
11309
|
+
if (!exists(join22(worktree, refine.file))) continue;
|
|
11008
11310
|
ecosystem = refine.ecosystem;
|
|
11009
11311
|
command = refine.command;
|
|
11010
11312
|
break;
|
|
@@ -11061,12 +11363,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
11061
11363
|
function ciWorkflowPlan(worktree, io) {
|
|
11062
11364
|
const readText = io.readText ?? safeReadText;
|
|
11063
11365
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
11064
|
-
const dir =
|
|
11366
|
+
const dir = join22(worktree, ".github", "workflows");
|
|
11065
11367
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
11066
11368
|
const seen = /* @__PURE__ */ new Set();
|
|
11067
11369
|
const commands = [];
|
|
11068
11370
|
for (const name of files.sort()) {
|
|
11069
|
-
const yaml = readText(
|
|
11371
|
+
const yaml = readText(join22(dir, name));
|
|
11070
11372
|
if (yaml === null) continue;
|
|
11071
11373
|
for (const raw of workflowRunLines(yaml)) {
|
|
11072
11374
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -11081,7 +11383,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
11081
11383
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
11082
11384
|
}
|
|
11083
11385
|
function workspaceGlobs(worktree, readText) {
|
|
11084
|
-
const yaml = readText(
|
|
11386
|
+
const yaml = readText(join22(worktree, "pnpm-workspace.yaml"));
|
|
11085
11387
|
if (yaml !== null) {
|
|
11086
11388
|
const globs = [];
|
|
11087
11389
|
let inPackages = false;
|
|
@@ -11101,7 +11403,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
11101
11403
|
}
|
|
11102
11404
|
if (globs.length) return globs;
|
|
11103
11405
|
}
|
|
11104
|
-
const pkg = readJson(readText,
|
|
11406
|
+
const pkg = readJson(readText, join22(worktree, "package.json"));
|
|
11105
11407
|
const ws = pkg?.workspaces;
|
|
11106
11408
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
11107
11409
|
return list.filter((g) => typeof g === "string");
|
|
@@ -11112,14 +11414,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
11112
11414
|
if (glob.startsWith("!")) continue;
|
|
11113
11415
|
const star = glob.indexOf("*");
|
|
11114
11416
|
if (star === -1) {
|
|
11115
|
-
const dir =
|
|
11116
|
-
if (io.exists(
|
|
11417
|
+
const dir = join22(worktree, glob);
|
|
11418
|
+
if (io.exists(join22(dir, "package.json"))) dirs.add(dir);
|
|
11117
11419
|
continue;
|
|
11118
11420
|
}
|
|
11119
|
-
const base3 =
|
|
11421
|
+
const base3 = join22(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
11120
11422
|
for (const name of io.listDirs(base3)) {
|
|
11121
|
-
const dir =
|
|
11122
|
-
if (io.exists(
|
|
11423
|
+
const dir = join22(base3, name);
|
|
11424
|
+
if (io.exists(join22(dir, "package.json"))) dirs.add(dir);
|
|
11123
11425
|
}
|
|
11124
11426
|
}
|
|
11125
11427
|
return [...dirs];
|
|
@@ -11140,18 +11442,18 @@ function entryPaths(pkg) {
|
|
|
11140
11442
|
return out;
|
|
11141
11443
|
}
|
|
11142
11444
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
11143
|
-
const exists = io.exists ??
|
|
11445
|
+
const exists = io.exists ?? existsSync14;
|
|
11144
11446
|
const readText = io.readText ?? safeReadText;
|
|
11145
11447
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11146
11448
|
const globs = workspaceGlobs(worktree, readText);
|
|
11147
11449
|
if (!globs.length) return [];
|
|
11148
11450
|
const pkgs = [];
|
|
11149
11451
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
11150
|
-
const pkg = readJson(readText,
|
|
11452
|
+
const pkg = readJson(readText, join22(dir, "package.json"));
|
|
11151
11453
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
11152
11454
|
const scripts = pkg.scripts ?? {};
|
|
11153
11455
|
const entries = entryPaths(pkg);
|
|
11154
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
11456
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join22(dir, e)));
|
|
11155
11457
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
11156
11458
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
11157
11459
|
}
|
|
@@ -11179,7 +11481,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
11179
11481
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
11180
11482
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
11181
11483
|
for (const file of [".env", ".env.example"]) {
|
|
11182
|
-
const raw = readText(
|
|
11484
|
+
const raw = readText(join22(worktree, file));
|
|
11183
11485
|
if (raw === null) continue;
|
|
11184
11486
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
11185
11487
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -11195,11 +11497,11 @@ function isLocalDatabase(url) {
|
|
|
11195
11497
|
}
|
|
11196
11498
|
}
|
|
11197
11499
|
function prismaPackages(worktree, io) {
|
|
11198
|
-
const exists = io.exists ??
|
|
11500
|
+
const exists = io.exists ?? existsSync14;
|
|
11199
11501
|
const readText = io.readText ?? safeReadText;
|
|
11200
11502
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11201
11503
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
11202
|
-
return dirs.filter((dir) => exists(
|
|
11504
|
+
return dirs.filter((dir) => exists(join22(dir, "prisma", "schema.prisma")) && listDirs(join22(dir, "prisma", "migrations")).length > 0);
|
|
11203
11505
|
}
|
|
11204
11506
|
function jsExecBin(pm, command) {
|
|
11205
11507
|
switch (pm) {
|
|
@@ -11229,25 +11531,25 @@ function migrateCommands(worktree, pm, io) {
|
|
|
11229
11531
|
var ENV_DEFINING_FILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock", "pnpm-workspace.yaml"];
|
|
11230
11532
|
function envCachePlan(worktree, io = {}) {
|
|
11231
11533
|
if (!ENV_CACHE_ENABLED) return null;
|
|
11232
|
-
const exists = io.exists ??
|
|
11534
|
+
const exists = io.exists ?? existsSync14;
|
|
11233
11535
|
const readText = io.readText ?? safeReadText;
|
|
11234
11536
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11235
11537
|
const pm = detectPackageManager(worktree, io);
|
|
11236
11538
|
if (!pm) return null;
|
|
11237
11539
|
const parts = [`pm:${pm}`];
|
|
11238
11540
|
for (const name of ENV_DEFINING_FILES) {
|
|
11239
|
-
const raw = readText(
|
|
11541
|
+
const raw = readText(join22(worktree, name));
|
|
11240
11542
|
if (raw !== null) parts.push(`${name}
|
|
11241
11543
|
${raw}`);
|
|
11242
11544
|
}
|
|
11243
11545
|
const pkgDirs = workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs });
|
|
11244
11546
|
const rels = pkgDirs.map((dir) => dir.slice(worktree.length + 1)).sort();
|
|
11245
11547
|
for (const rel of ["", ...rels]) {
|
|
11246
|
-
const raw = readText(
|
|
11548
|
+
const raw = readText(join22(worktree, rel, "package.json"));
|
|
11247
11549
|
if (raw !== null) parts.push(`${rel}/package.json
|
|
11248
11550
|
${raw}`);
|
|
11249
11551
|
}
|
|
11250
|
-
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) =>
|
|
11552
|
+
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join22(rel, "node_modules"))] };
|
|
11251
11553
|
}
|
|
11252
11554
|
function planPrepare(worktree, io = {}) {
|
|
11253
11555
|
const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
|
|
@@ -11255,7 +11557,7 @@ function planPrepare(worktree, io = {}) {
|
|
|
11255
11557
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
11256
11558
|
}
|
|
11257
11559
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
11258
|
-
const exists = io.exists ??
|
|
11560
|
+
const exists = io.exists ?? existsSync14;
|
|
11259
11561
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
11260
11562
|
const approve = io.approve ?? (() => true);
|
|
11261
11563
|
const cachePlan = envCachePlan(worktree, io);
|
|
@@ -11272,7 +11574,7 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
11272
11574
|
restored: [],
|
|
11273
11575
|
cached: false
|
|
11274
11576
|
};
|
|
11275
|
-
if (cachePlan && !exists(
|
|
11577
|
+
if (cachePlan && !exists(join22(worktree, "node_modules"))) {
|
|
11276
11578
|
report.restored = await restoreEnvSnapshot(worktree, cachePlan.key, cacheIO) ?? [];
|
|
11277
11579
|
}
|
|
11278
11580
|
const plan = planPrepare(worktree, io);
|
|
@@ -11561,7 +11863,7 @@ function parkReasonUnretriable(reason) {
|
|
|
11561
11863
|
// package.json
|
|
11562
11864
|
var package_default = {
|
|
11563
11865
|
name: "@taskless-app/swarm",
|
|
11564
|
-
version: "0.6.
|
|
11866
|
+
version: "0.6.34",
|
|
11565
11867
|
type: "module",
|
|
11566
11868
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11567
11869
|
keywords: [
|
|
@@ -11612,7 +11914,7 @@ var package_default = {
|
|
|
11612
11914
|
execa: "^10.0.0",
|
|
11613
11915
|
zod: "3.25.76"
|
|
11614
11916
|
},
|
|
11615
|
-
gitHead: "
|
|
11917
|
+
gitHead: "4eabd911d4dfd557bfbfcbc27afd36415714b7eb"
|
|
11616
11918
|
};
|
|
11617
11919
|
|
|
11618
11920
|
// src/version.ts
|
|
@@ -11688,6 +11990,7 @@ function reviewBudgetMark(cycle) {
|
|
|
11688
11990
|
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";
|
|
11689
11991
|
var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
|
|
11690
11992
|
var CI_WAIT_ESCALATION_MARK = "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 CI \u0437\u0430\u0442\u044F\u043D\u0443\u043B\u043E\u0441\u044C";
|
|
11993
|
+
var RELIEF_LOOP_ESCALATION_MARK = "\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u0430\u0441\u044C";
|
|
11691
11994
|
var ESCALATIONS = [
|
|
11692
11995
|
{
|
|
11693
11996
|
mark: SELF_REFERENCE_MARK,
|
|
@@ -11750,6 +12053,21 @@ var ESCALATIONS = [
|
|
|
11750
12053
|
sides: ["author"],
|
|
11751
12054
|
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0443 PR \u043D\u0435 \u0431\u0443\u0434\u0435\u0442"
|
|
11752
12055
|
},
|
|
12056
|
+
{
|
|
12057
|
+
mark: RELIEF_LOOP_ESCALATION_MARK,
|
|
12058
|
+
// Проверяющего это не касается: работы, которую проверять, ещё нет. Автор — да, и это
|
|
12059
|
+
// главное: сторож прогресса (watchdog.decideFreshAction) обязан УВИДЕТЬ, что рой уже
|
|
12060
|
+
// сдался, иначе он поднимет свежего актора в ту же перегрузку — буквально следующий круг
|
|
12061
|
+
// петли, ровно то, что он и делал 27.08. Ручную побудку `swarm wake` этот маркер, как и
|
|
12062
|
+
// остальные author-маркеры, не запрещает — он открывает ей дверь (см. stoppedForHuman).
|
|
12063
|
+
//
|
|
12064
|
+
// Надзора НЕТ намеренно: маркер ложится на прогон в статусе `stalled`, а надзорная труба
|
|
12065
|
+
// (buildEscalationCandidates) ходит только по `listRuns("review")` — запись там была бы
|
|
12066
|
+
// мёртвой. Человека по такому прогону зовёт детектор run_stalled (anomalies.ts), причём
|
|
12067
|
+
// повторный заход по той же задаче он поднимает до уровня «нужен оператор» сам.
|
|
12068
|
+
sides: ["author"],
|
|
12069
|
+
why: "\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043D\u044F\u043B\u0430 \u043F\u0440\u043E\u0433\u043E\u043D N \u0440\u0430\u0437 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0439 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C, \u0437\u043E\u0432\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 run_stalled"
|
|
12070
|
+
},
|
|
11753
12071
|
{
|
|
11754
12072
|
mark: CI_WAIT_ESCALATION_MARK,
|
|
11755
12073
|
// Сторон НЕТ — и это решение, а не пропуск. Маркер лежит на прогоне РЕВЬЮЕРА (paused,
|
|
@@ -12213,7 +12531,7 @@ init_git();
|
|
|
12213
12531
|
|
|
12214
12532
|
// src/diff.ts
|
|
12215
12533
|
import { statSync as statSync10 } from "fs";
|
|
12216
|
-
import { join as
|
|
12534
|
+
import { join as join27 } from "path";
|
|
12217
12535
|
import { $ as $4 } from "execa";
|
|
12218
12536
|
var MAX_FILES = 200;
|
|
12219
12537
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -12381,7 +12699,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
12381
12699
|
files.push(pf);
|
|
12382
12700
|
}
|
|
12383
12701
|
}
|
|
12384
|
-
files.sort((a, b) => mtime(
|
|
12702
|
+
files.sort((a, b) => mtime(join27(worktree, b.path)) - mtime(join27(worktree, a.path)));
|
|
12385
12703
|
const truncatedFiles = files.length > MAX_FILES;
|
|
12386
12704
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
12387
12705
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -12396,6 +12714,20 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
12396
12714
|
...truncatedFiles ? { truncated: true } : {}
|
|
12397
12715
|
};
|
|
12398
12716
|
}
|
|
12717
|
+
async function runCommitCount(worktree, opts = {}) {
|
|
12718
|
+
if (!worktree) return 0;
|
|
12719
|
+
try {
|
|
12720
|
+
const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
|
|
12721
|
+
if (isWt !== "true") return 0;
|
|
12722
|
+
const base3 = await resolveBase(worktree, opts.baseBranch);
|
|
12723
|
+
if (!base3) return 0;
|
|
12724
|
+
const out = (await $4({ reject: false })`git -C ${worktree} rev-list --count ${`${base3}..HEAD`}`).stdout.trim();
|
|
12725
|
+
const n = Number(out);
|
|
12726
|
+
return Number.isInteger(n) && n > 0 ? n : 0;
|
|
12727
|
+
} catch {
|
|
12728
|
+
return 0;
|
|
12729
|
+
}
|
|
12730
|
+
}
|
|
12399
12731
|
async function changedPaths(worktree, opts = {}) {
|
|
12400
12732
|
if (!worktree) return [];
|
|
12401
12733
|
try {
|
|
@@ -12460,6 +12792,7 @@ function collectZoneNoteConfirmations(report) {
|
|
|
12460
12792
|
// src/droneServer.ts
|
|
12461
12793
|
init_src();
|
|
12462
12794
|
init_src();
|
|
12795
|
+
init_src();
|
|
12463
12796
|
|
|
12464
12797
|
// src/reviewCycle.ts
|
|
12465
12798
|
init_src();
|
|
@@ -12671,6 +13004,8 @@ ${failText}` : failText }],
|
|
|
12671
13004
|
isError: true
|
|
12672
13005
|
};
|
|
12673
13006
|
}
|
|
13007
|
+
await store.addEvent(null, "step", `${ACCEPTANCE_CHECK_MARK}: ${id} \u2192 ${state}`, null).catch(() => {
|
|
13008
|
+
});
|
|
12674
13009
|
return { content: [{ type: "text", text }] };
|
|
12675
13010
|
}
|
|
12676
13011
|
);
|
|
@@ -12707,6 +13042,29 @@ ${failText}` : failText }],
|
|
|
12707
13042
|
return { content: [{ type: "text", text: "\u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u0430 \u0438 \u0443\u0435\u0434\u0435\u0442 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0436\u0434\u0438." }] };
|
|
12708
13043
|
})
|
|
12709
13044
|
);
|
|
13045
|
+
server.tool(
|
|
13046
|
+
"report_weight",
|
|
13047
|
+
"\u0421\u043A\u0430\u0437\u0430\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0443, \u0447\u0442\u043E \u0420\u0410\u0411\u041E\u0422\u0410 \u0422\u042F\u0416\u0415\u041B\u0415\u0415 \u041E\u0416\u0418\u0414\u0410\u041D\u0418\u0419 \u2014 \u043F\u043E \u0445\u043E\u0434\u0443 \u0434\u0435\u043B\u0430, \u0430 \u043D\u0435 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435. \u0417\u043E\u0432\u0438, \u043A\u043E\u0433\u0434\u0430 \u0441\u043E\u0436\u0436\u0435\u043D\u043E \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u043B\u043E\u0432\u0438\u043D\u044B \u0431\u044E\u0434\u0436\u0435\u0442\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0430 \u0434\u043E \u0441\u0434\u0430\u0447\u0438 \u0435\u0449\u0451 \u0434\u0430\u043B\u0435\u043A\u043E. \u0414\u0435\u043C\u043E\u043D \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442 \u043F\u043E \u043B\u0435\u043D\u0442\u0435, \u0447\u0442\u043E \u0442\u044B \u041F\u0420\u041E\u0414\u0412\u0418\u0413\u0410\u0415\u0428\u042C\u0421\u042F (\u043A\u043E\u043C\u043C\u0438\u0442\u044B/PR/\u0437\u0430\u043A\u0440\u044B\u0442\u044B\u0435 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438), \u0438 \u0435\u0441\u043B\u0438 \u0434\u0430 \u2014 \u043F\u0440\u0438\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435, \u043D\u0435 \u0441\u043D\u0438\u043C\u0435\u0442 \u0442\u0435\u0431\u044F \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u043E\u0439 \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u0432 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0445 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445. \u0415\u0441\u043B\u0438 \u043F\u0440\u043E\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442 \u2014 \u0434\u043E\u0431\u0430\u0432\u043A\u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440: \u0442\u0440\u0430\u0442\u0430 \u0431\u0435\u0437 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u044D\u0442\u043E \u043D\u0435 \u0442\u044F\u0436\u0435\u0441\u0442\u044C, \u0430 \u043F\u043E\u043B\u043E\u043C\u043A\u0430. \u041D\u0415 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442: \u0441\u043A\u0430\u0437\u0430\u043B \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0434\u0430\u043B\u044C\u0448\u0435.",
|
|
13048
|
+
{
|
|
13049
|
+
note: z.string().describe("\u043F\u043E\u0447\u0435\u043C\u0443 \u0442\u044F\u0436\u0435\u043B\u0435\u0435 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0439: \u0447\u0442\u043E \u043E\u043A\u0430\u0437\u0430\u043B\u043E\u0441\u044C \u0431\u043E\u043B\u044C\u0448\u0435, \u0447\u0435\u043C \u0432\u044B\u0433\u043B\u044F\u0434\u0435\u043B\u043E (\u043F\u0440\u0430\u0432\u043A\u0430 \u0440\u0430\u0441\u043F\u043E\u043B\u0437\u043B\u0430\u0441\u044C \u043D\u0430 N \u043F\u0430\u043A\u0435\u0442\u043E\u0432, \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F, \u0447\u0443\u0436\u043E\u0439 \u0441\u043B\u043E\u043C\u0430\u043D\u043D\u044B\u0439 \u0442\u0435\u0441\u0442\u2026)")
|
|
13050
|
+
},
|
|
13051
|
+
guard(async ({ note }) => {
|
|
13052
|
+
if (await overmindDead()) return freeze();
|
|
13053
|
+
const clean = note.trim();
|
|
13054
|
+
if (!clean) {
|
|
13055
|
+
return { content: [{ type: "text", text: "\u043F\u0443\u0441\u0442\u0430\u044F \u0437\u0430\u044F\u0432\u043A\u0430: \u043D\u0430\u0437\u043E\u0432\u0438, \u0427\u0422\u041E \u043E\u043A\u0430\u0437\u0430\u043B\u043E\u0441\u044C \u0442\u044F\u0436\u0435\u043B\u0435\u0435 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0439 \u2014 \u043F\u043E \u043F\u0443\u0441\u0442\u043E\u0439 \u0434\u0435\u043C\u043E\u043D \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u0442." }], isError: true };
|
|
13056
|
+
}
|
|
13057
|
+
await store.addEvent(null, "note", heavyClaimText(clean), null);
|
|
13058
|
+
return {
|
|
13059
|
+
content: [
|
|
13060
|
+
{
|
|
13061
|
+
type: "text",
|
|
13062
|
+
text: "\u0437\u0430\u044F\u0432\u043A\u0430 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u0430 \u2014 \u0434\u0435\u043C\u043E\u043D \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u0442 \u0435\u0451 \u043D\u0430 \u0431\u043B\u0438\u0436\u0430\u0439\u0448\u0435\u043C \u0442\u0438\u043A\u0435 \u043F\u043E \u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044E (\u0442\u0440\u0430\u0442\u0430 \u043F\u0440\u043E\u0442\u0438\u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0437\u0430\u0434\u0430\u0447\u0438) \u0438 \u043F\u043E \u0442\u0432\u043E\u0435\u043C\u0443 \u043F\u0440\u043E\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044E. \u041E\u0442\u0432\u0435\u0442\u0430 \u043D\u0435 \u0436\u0434\u0438, \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u0440\u0438\u0435\u0434\u0435\u0442 \u0432 \u043B\u0435\u043D\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0430 \u043F\u043E\u0434\u043D\u044F\u0442\u044B\u0439 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u2014 \u0432 \u0431\u044E\u0434\u0436\u0435\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430. \u041F\u0440\u043E\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442 (\u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432, \u043D\u0438 PR, \u043D\u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044B\u0445 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432) \u2014 \u0434\u043E\u0431\u0430\u0432\u043A\u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442, \u043F\u043E\u0437\u043E\u0432\u0443\u0442 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430."
|
|
13063
|
+
}
|
|
13064
|
+
]
|
|
13065
|
+
};
|
|
13066
|
+
})
|
|
13067
|
+
);
|
|
12710
13068
|
server.tool(
|
|
12711
13069
|
"return_task",
|
|
12712
13070
|
"\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0410\u0412\u0422\u041E\u0420\u0423 \u043D\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0435 \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (\u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F, \u0440\u043E\u0439 \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u0417\u043E\u0432\u0438, \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0418\u041B\u0418 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0414\u0432\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B\u0445 \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u2014 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434: \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u041F\u043E\u043D\u044F\u0442\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u2014 \u0442\u043E\u0436\u0435 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434: \u044D\u0442\u043E ask. \u041E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0441\u043B\u0443\u0447\u0430\u0439 \u2014 \u0437\u0430\u0434\u0430\u0447\u0430 \u0421\u041B\u0418\u0428\u041A\u041E\u041C \u0412\u0415\u041B\u0418\u041A\u0410 (reason=too_big): \u043F\u043E\u043D\u044F\u0442\u043D\u0430 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u0430, \u043D\u043E \u044D\u0442\u043E \u043B\u0438\u0441\u0442 \u043D\u0430 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432, \u0438 \u0443\u043F\u0435\u0440\u0435\u0442\u044C\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043D\u0430 \u0441\u0435\u0440\u0435\u0434\u0438\u043D\u0435 \u0445\u0443\u0436\u0435, \u0447\u0435\u043C \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0435\u0451 \u0441\u0440\u0430\u0437\u0443; \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418\u0422\u042C (split), \u0430 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0435 \u0437\u0430\u0432\u043E\u0434\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0430\u0432\u0442\u043E\u0440\u0430. \u0417\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 (\u043D\u0430\u0437\u043D\u0430\u0447\u0438\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0451\u043C \u0432 \u0440\u0430\u0431\u043E\u0442\u0443), \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043E\u0442 \u0442\u0432\u043E\u0435\u0433\u043E \u0438\u043C\u0435\u043D\u0438. \u041F\u043E\u0441\u043B\u0435 \u0432\u044B\u0437\u043E\u0432\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0437\u043E\u0432\u0438 \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
|
|
@@ -14464,22 +14822,22 @@ function makeMergeGateParts(deps) {
|
|
|
14464
14822
|
}
|
|
14465
14823
|
|
|
14466
14824
|
// src/envGate.ts
|
|
14467
|
-
import { existsSync as
|
|
14468
|
-
import { join as
|
|
14825
|
+
import { existsSync as existsSync17 } from "fs";
|
|
14826
|
+
import { join as join29 } from "path";
|
|
14469
14827
|
import { createConnection } from "net";
|
|
14470
14828
|
import { execa as execa2 } from "execa";
|
|
14471
14829
|
|
|
14472
14830
|
// src/platform.ts
|
|
14473
|
-
import { existsSync as
|
|
14474
|
-
import { delimiter, join as
|
|
14831
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
|
|
14832
|
+
import { delimiter, join as join28 } from "path";
|
|
14475
14833
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
14476
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
14834
|
+
if (name.includes("/") || name.includes("\\")) return existsSync16(name) ? name : null;
|
|
14477
14835
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
14478
14836
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
14479
14837
|
for (const dir of dirs) {
|
|
14480
14838
|
for (const ext of exts) {
|
|
14481
|
-
const candidate =
|
|
14482
|
-
if (
|
|
14839
|
+
const candidate = join28(dir, name + ext);
|
|
14840
|
+
if (existsSync16(candidate)) return candidate;
|
|
14483
14841
|
}
|
|
14484
14842
|
}
|
|
14485
14843
|
return null;
|
|
@@ -14497,8 +14855,8 @@ function detectGlobalPm(entry, io = {}) {
|
|
|
14497
14855
|
if (/\/node_modules\//.test(p)) return "npm";
|
|
14498
14856
|
const env = io.env ?? process.env;
|
|
14499
14857
|
const platform = io.platform ?? process.platform;
|
|
14500
|
-
const
|
|
14501
|
-
return
|
|
14858
|
+
const has2 = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
|
|
14859
|
+
return has2("npm") ? "npm" : "unknown";
|
|
14502
14860
|
}
|
|
14503
14861
|
function globalInstallCommand(pm, spec) {
|
|
14504
14862
|
switch (pm) {
|
|
@@ -14562,8 +14920,8 @@ var COMPOSE_MARKERS = [
|
|
|
14562
14920
|
"compose.yaml"
|
|
14563
14921
|
];
|
|
14564
14922
|
function detectRequirements(repo, io = {}) {
|
|
14565
|
-
const exists = io.exists ??
|
|
14566
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
14923
|
+
const exists = io.exists ?? existsSync17;
|
|
14924
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join29(repo, f)));
|
|
14567
14925
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
14568
14926
|
}
|
|
14569
14927
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -16306,22 +16664,22 @@ function closeWebServer(server) {
|
|
|
16306
16664
|
|
|
16307
16665
|
// src/hostCommands.ts
|
|
16308
16666
|
init_src();
|
|
16309
|
-
import { readFileSync as
|
|
16310
|
-
import { join as
|
|
16667
|
+
import { readFileSync as readFileSync22, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
|
|
16668
|
+
import { join as join30 } from "path";
|
|
16311
16669
|
function hostCommandStatePath(home) {
|
|
16312
|
-
return
|
|
16670
|
+
return join30(home, "host-command.json");
|
|
16313
16671
|
}
|
|
16314
16672
|
function readState(path) {
|
|
16315
16673
|
try {
|
|
16316
|
-
return JSON.parse(
|
|
16674
|
+
return JSON.parse(readFileSync22(path, "utf8"));
|
|
16317
16675
|
} catch {
|
|
16318
16676
|
return {};
|
|
16319
16677
|
}
|
|
16320
16678
|
}
|
|
16321
16679
|
function writeState(path, state) {
|
|
16322
16680
|
try {
|
|
16323
|
-
|
|
16324
|
-
|
|
16681
|
+
writeFileSync15(`${path}.tmp`, JSON.stringify(state));
|
|
16682
|
+
renameSync10(`${path}.tmp`, path);
|
|
16325
16683
|
} catch {
|
|
16326
16684
|
}
|
|
16327
16685
|
}
|
|
@@ -16483,8 +16841,8 @@ async function doRestart(cmd, opts, note) {
|
|
|
16483
16841
|
|
|
16484
16842
|
// src/selfUpdate.ts
|
|
16485
16843
|
init_git();
|
|
16486
|
-
import { existsSync as
|
|
16487
|
-
import { join as
|
|
16844
|
+
import { existsSync as existsSync18 } from "fs";
|
|
16845
|
+
import { join as join31 } from "path";
|
|
16488
16846
|
import { $ as $5 } from "execa";
|
|
16489
16847
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
16490
16848
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -16497,7 +16855,7 @@ function tail(text, max = 400) {
|
|
|
16497
16855
|
}
|
|
16498
16856
|
async function runSelfUpdate(io) {
|
|
16499
16857
|
const run = io.run ?? defaultRun;
|
|
16500
|
-
const exists = io.exists ??
|
|
16858
|
+
const exists = io.exists ?? existsSync18;
|
|
16501
16859
|
const repo = io.repo;
|
|
16502
16860
|
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
16503
16861
|
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
@@ -16526,7 +16884,7 @@ async function runSelfUpdate(io) {
|
|
|
16526
16884
|
};
|
|
16527
16885
|
}
|
|
16528
16886
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
16529
|
-
if (!exists(
|
|
16887
|
+
if (!exists(join31(repo, "pnpm-lock.yaml"))) {
|
|
16530
16888
|
return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
|
|
16531
16889
|
}
|
|
16532
16890
|
const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -16685,19 +17043,19 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
16685
17043
|
}
|
|
16686
17044
|
|
|
16687
17045
|
// src/anomalyState.ts
|
|
16688
|
-
import { existsSync as
|
|
16689
|
-
import { join as
|
|
17046
|
+
import { existsSync as existsSync19, readFileSync as readFileSync23, renameSync as renameSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
17047
|
+
import { join as join32 } from "path";
|
|
16690
17048
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
16691
17049
|
var MAX_RESOLVED = 200;
|
|
16692
17050
|
function emptyState() {
|
|
16693
17051
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
16694
17052
|
}
|
|
16695
17053
|
function anomalyStatePath(home) {
|
|
16696
|
-
return
|
|
17054
|
+
return join32(home, "anomaly-state.json");
|
|
16697
17055
|
}
|
|
16698
17056
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
16699
|
-
const exists = io.exists ??
|
|
16700
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
17057
|
+
const exists = io.exists ?? existsSync19;
|
|
17058
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync23(p, "utf8"));
|
|
16701
17059
|
const path = anomalyStatePath(home);
|
|
16702
17060
|
if (!exists(path)) return emptyState();
|
|
16703
17061
|
try {
|
|
@@ -16727,8 +17085,8 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
16727
17085
|
function saveAnomalyState(home, state, io = {}) {
|
|
16728
17086
|
const path = anomalyStatePath(home);
|
|
16729
17087
|
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
16730
|
-
|
|
16731
|
-
|
|
17088
|
+
writeFileSync16(`${p}.tmp`, data);
|
|
17089
|
+
renameSync11(`${p}.tmp`, p);
|
|
16732
17090
|
});
|
|
16733
17091
|
writeFile4(path, JSON.stringify(state, null, 2));
|
|
16734
17092
|
}
|
|
@@ -16882,6 +17240,7 @@ function decideParkAction(db, droneCap, exclusivityBlocked) {
|
|
|
16882
17240
|
function decideFreshAction(db, wt) {
|
|
16883
17241
|
const stalled = db.listRuns("stalled").filter((r) => r.resolves_run_id == null && r.reviews_run_id == null);
|
|
16884
17242
|
for (const r of stalled) {
|
|
17243
|
+
if (findEscalation(db.events(r.id).map((e) => e.text ?? ""), r, "author")) continue;
|
|
16885
17244
|
const path = r.worktree_path;
|
|
16886
17245
|
const recoverable = !!path && wt.exists(path) && wt.hasTranscript(path);
|
|
16887
17246
|
if (recoverable) continue;
|
|
@@ -17229,6 +17588,7 @@ function verdictCommentMark(createdAt) {
|
|
|
17229
17588
|
return `\u043A\u043E\u043C\u043C\u0435\u043D\u0442-\u0432\u0435\u0440\u0434\u0438\u043A\u0442@${createdAt}`;
|
|
17230
17589
|
}
|
|
17231
17590
|
var WATCHDOG_PARK_REASON = "\u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430: \u0441\u043B\u043E\u0442 \u0437\u0430\u043D\u044F\u0442 \u043D\u0438\u0437\u043A\u043E\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u044B\u043C \u2014 \u043F\u0430\u0440\u043A\u0443\u044E, \u0432\u043F\u0443\u0441\u043A\u0430\u044E \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0436\u0434\u0443\u0449\u0435\u0433\u043E (TASWM-270)";
|
|
17591
|
+
var RELIEF_LOOP_STOP_REASON = "\u0440\u0435\u0441\u0443\u0440\u0441-\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u0430\u0441\u044C: \u0441\u043D\u044F\u0442\u0438\u0439 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0441\u0434\u0430\u0447\u0438 \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u0442\u043E\u043B\u043A\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A (TASWM-531)";
|
|
17232
17592
|
function livePpids() {
|
|
17233
17593
|
return livePpidsFrom(listProcs());
|
|
17234
17594
|
}
|
|
@@ -17784,7 +18144,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
17784
18144
|
// вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
|
|
17785
18145
|
// ветку заводит spawnDrone из слага) — прежняя формулировка.
|
|
17786
18146
|
`\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"}.`,
|
|
17787
|
-
"\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; return_task(reason, missing, readings, assumed, questions) \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0421\u042B\u0420\u0423\u042E \u0437\u0430\u0434\u0430\u0447\u0443 \u0430\u0432\u0442\u043E\u0440\u0443 (\u0441\u043C. \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0438\u0436\u0435); 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).",
|
|
18147
|
+
"\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; return_task(reason, missing, readings, assumed, questions) \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0421\u042B\u0420\u0423\u042E \u0437\u0430\u0434\u0430\u0447\u0443 \u0430\u0432\u0442\u043E\u0440\u0443 (\u0441\u043C. \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0438\u0436\u0435); 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). report_weight(note) \u2014 \u0441\u043A\u0430\u0436\u0438 \u0434\u0435\u043C\u043E\u043D\u0443, \u0447\u0442\u043E \u0420\u0410\u0411\u041E\u0422\u0410 \u0422\u042F\u0416\u0415\u041B\u0415\u0415 \u041E\u0416\u0418\u0414\u0410\u041D\u0418\u0419, \u043A\u043E\u0433\u0434\u0430 \u0441\u043E\u0436\u0436\u0435\u043D\u043E \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u043B\u043E\u0432\u0438\u043D\u044B \u0442\u0432\u043E\u0435\u0433\u043E \u0431\u044E\u0434\u0436\u0435\u0442\u0430, \u0430 \u0434\u043E \u0441\u0434\u0430\u0447\u0438 \u0434\u0430\u043B\u0435\u043A\u043E: \u043E\u043D \u043F\u0440\u0438\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435, \u043D\u0435 \u0441\u043D\u0438\u043C\u0435\u0442 \u0442\u0435\u0431\u044F \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u043E\u0439 \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u0432 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0445 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445. \u041F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442\u0441\u044F \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u2014 \u0432\u0435\u0441 \u0434\u0430\u044E\u0442 \u0442\u043E\u043C\u0443, \u043A\u0442\u043E \u041F\u0420\u041E\u0414\u0412\u0418\u0413\u0410\u0415\u0422\u0421\u042F (\u043A\u043E\u043C\u043C\u0438\u0442\u044B/PR/\u0437\u0430\u043A\u0440\u044B\u0442\u044B\u0435 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438); \u0442\u0440\u0430\u0442\u0430 \u0431\u0435\u0437 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u0431\u044E\u0434\u0436\u0435\u0442\u0430 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442, \u0435\u0451 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440. \u0417\u043E\u0432\u0438 \u041F\u041E \u0425\u041E\u0414\u0423, \u0430 \u043D\u0435 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435: \u043F\u043E\u0441\u0442\u0444\u0430\u043A\u0442\u0443\u043C \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u0442\u044C \u0443\u0436\u0435 \u043D\u0435\u0447\u0435\u0433\u043E.",
|
|
17788
18148
|
// TASWM-412: третий выход из непонятной задачи. Раньше их было два, и оба плохие —
|
|
17789
18149
|
// угадать постановку (сделает не то) или ask (слот занят, а ответить может только автор
|
|
17790
18150
|
// задачи, не оператор роя). Граница здесь ОБЯЗАТЕЛЬНА: дрон, легко возвращающий задачи,
|
|
@@ -17854,10 +18214,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
17854
18214
|
}
|
|
17855
18215
|
return parts.join("\n");
|
|
17856
18216
|
}
|
|
17857
|
-
var HERO_CHARTER_PATH =
|
|
18217
|
+
var HERO_CHARTER_PATH = join33(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
17858
18218
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
17859
18219
|
const name = run.name || `#${run.id}`;
|
|
17860
|
-
const charter =
|
|
18220
|
+
const charter = readFileSync24(HERO_CHARTER_PATH, "utf8");
|
|
17861
18221
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
17862
18222
|
|
|
17863
18223
|
${task.description}` : `\u042D\u041F\u0418\u041A ${run.task_slug} (\u0442\u0435\u043B\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E slug \u0438 \u0437\u0434\u0440\u0430\u0432\u043E\u043C\u0443 \u0441\u043C\u044B\u0441\u043B\u0443).`;
|
|
@@ -18165,7 +18525,7 @@ async function wakeRun(deps, runId) {
|
|
|
18165
18525
|
if (run.status === "review" && run.pr_url && !needsFixIteration(run) && !stoppedForHuman) {
|
|
18166
18526
|
return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0441\u0434\u0430\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E (${run.pr_url}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E; \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u043E\u0439 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443` };
|
|
18167
18527
|
}
|
|
18168
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18528
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
18169
18529
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18170
18530
|
const wt = run.worktree_path;
|
|
18171
18531
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -18285,7 +18645,7 @@ function repoHasCi(deps, run) {
|
|
|
18285
18645
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
18286
18646
|
for (const base3 of [worktreePath, repo]) {
|
|
18287
18647
|
if (!base3) continue;
|
|
18288
|
-
const dir =
|
|
18648
|
+
const dir = join33(base3, ".github", "workflows");
|
|
18289
18649
|
try {
|
|
18290
18650
|
if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
18291
18651
|
} catch {
|
|
@@ -18471,7 +18831,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
18471
18831
|
return true;
|
|
18472
18832
|
}
|
|
18473
18833
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
18474
|
-
const exists = deps.worktreeExists ??
|
|
18834
|
+
const exists = deps.worktreeExists ?? existsSync20;
|
|
18475
18835
|
const transcript = deps.hasTranscript ?? executorHasSession;
|
|
18476
18836
|
return exists(worktree) && transcript(worktree);
|
|
18477
18837
|
}
|
|
@@ -18781,6 +19141,13 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
18781
19141
|
// причина одна на машину — в лог уходит одной строкой, а не по строке на прогон
|
|
18782
19142
|
});
|
|
18783
19143
|
}
|
|
19144
|
+
const heavy = heavyRunsInFlight(deps);
|
|
19145
|
+
if (heavy.length && heavy.every((h) => h.id !== run.id)) {
|
|
19146
|
+
return deferSpawn(deps, run, intakeHoldReason(heavy.map(droneLabel).join(", ")), {
|
|
19147
|
+
logKey: "weight-hold"
|
|
19148
|
+
// причина одна на машину — в лог одной строкой, а не по строке на прогон
|
|
19149
|
+
});
|
|
19150
|
+
}
|
|
18784
19151
|
const spentAttempts = attemptsFor(deps, run.task_slug);
|
|
18785
19152
|
if (spentAttempts >= PRE_START_ATTEMPT_LIMIT) {
|
|
18786
19153
|
const last = lastPreStartAttempt(deps.db.listRuns(), run.task_slug);
|
|
@@ -18980,7 +19347,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
18980
19347
|
}
|
|
18981
19348
|
if (!candidate) return;
|
|
18982
19349
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
18983
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19350
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
18984
19351
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18985
19352
|
const wt = candidate.worktree_path;
|
|
18986
19353
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -19068,6 +19435,79 @@ async function applyWatchdogAction(deps, action) {
|
|
|
19068
19435
|
}
|
|
19069
19436
|
}
|
|
19070
19437
|
var GATE_LOG_HEARTBEAT_TICKS = 30;
|
|
19438
|
+
function wasProductive(r) {
|
|
19439
|
+
return r.pr_url != null || r.status === "done" || r.status === "review";
|
|
19440
|
+
}
|
|
19441
|
+
function weightSamples(history, run) {
|
|
19442
|
+
const all = history.filter((r) => r.id !== run.id && r.kind === "drone");
|
|
19443
|
+
const own = all.filter((r) => r.task_slug === run.task_slug);
|
|
19444
|
+
const similar = run.area ? all.filter((r) => r.area === run.area) : all;
|
|
19445
|
+
return [...own, ...similar.filter((r) => r.task_slug !== run.task_slug)].map((r) => ({
|
|
19446
|
+
costUsd: r.cost_usd,
|
|
19447
|
+
productive: wasProductive(r),
|
|
19448
|
+
ownTask: r.task_slug === run.task_slug
|
|
19449
|
+
}));
|
|
19450
|
+
}
|
|
19451
|
+
function refreshHeavyRuns(deps) {
|
|
19452
|
+
const live = deps.db.listRuns("running");
|
|
19453
|
+
deps.weightState = { heavy: live.filter((r) => r.kind === "drone" && isHeavyRun(deps.db.events(r.id))) };
|
|
19454
|
+
}
|
|
19455
|
+
function heavyRunsInFlight(deps) {
|
|
19456
|
+
return deps.weightState?.heavy ?? [];
|
|
19457
|
+
}
|
|
19458
|
+
async function manageWeight(deps) {
|
|
19459
|
+
const claimants = deps.db.listRuns("running").filter((r) => r.kind === "drone").map((run) => ({ run, events: deps.db.events(run.id) })).filter(({ events }) => hasOpenHeavyClaim(events));
|
|
19460
|
+
const history = claimants.length ? deps.db.listRuns() : [];
|
|
19461
|
+
for (const { run, events } of claimants) {
|
|
19462
|
+
let commits = 0;
|
|
19463
|
+
if (deps.runCommits) {
|
|
19464
|
+
try {
|
|
19465
|
+
commits = await deps.runCommits(run);
|
|
19466
|
+
} catch (e) {
|
|
19467
|
+
console.warn(`[overmind] \u0432\u0435\u0441: \u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043A\u043E\u043C\u043C\u0438\u0442\u044B ${droneLabel(run)}:`, e);
|
|
19468
|
+
}
|
|
19469
|
+
}
|
|
19470
|
+
const estimate = estimateTaskWeight(weightSamples(history, run));
|
|
19471
|
+
const decision = decideHeavyClaim({
|
|
19472
|
+
spentUsd: run.cost_usd,
|
|
19473
|
+
estimate,
|
|
19474
|
+
progress: { commits, hasPr: run.pr_url != null, acceptanceChecks: countAcceptanceChecks(events) },
|
|
19475
|
+
baseCeilingUsd: deps.maxCostUsd?.() ?? DEFAULT_RUN_LIMITS.maxCostUsd,
|
|
19476
|
+
grantedUsd: grantedCeilingUsd(events)
|
|
19477
|
+
});
|
|
19478
|
+
const basis = `${estimate.basis}, \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0432 \u0432\u044B\u0431\u043E\u0440\u043A\u0435: ${estimate.samples}`;
|
|
19479
|
+
if (decision.verdict === "stalled") {
|
|
19480
|
+
const why = `${heavyRefusedText(decision.reason)} (\u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u0435: ${basis})`;
|
|
19481
|
+
deps.db.addEvent(run.id, "note", why);
|
|
19482
|
+
deps.db.addEvent(run.id, "anomaly", why);
|
|
19483
|
+
narrate(deps, `${droneLabel(run)} \u043F\u0440\u043E\u0441\u0438\u0442 \u0431\u044E\u0434\u0436\u0435\u0442, \u043D\u043E \u043D\u0435 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F \u2014 \u0432\u0435\u0441\u0430 \u043D\u0435 \u0434\u0430\u044E, \u0437\u043E\u0432\u0443 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (TASWM-535).`);
|
|
19484
|
+
if (deps.watchdog?.notify) {
|
|
19485
|
+
try {
|
|
19486
|
+
await deps.watchdog.notify({
|
|
19487
|
+
key: `weight_stalled:${run.id}`,
|
|
19488
|
+
title: signalTitle(run, "\u0442\u0440\u0430\u0442\u0438\u0442, \u043D\u043E \u043D\u0435 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F"),
|
|
19489
|
+
body: `${run.task_slug}: \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0441\u0438\u0442 \u043F\u0440\u0438\u0437\u043D\u0430\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0442\u044F\u0436\u0451\u043B\u043E\u0439, \u043D\u043E \u043F\u0440\u043E\u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442 \u2014 \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u043E\u0432 \u0432 \u0432\u0435\u0442\u043A\u0435, \u043D\u0438 PR, \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0437\u0430\u043A\u0440\u044B\u0442\u043E\u0433\u043E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u044F \u043F\u0440\u0438 \u043F\u043E\u0442\u0440\u0430\u0447\u0435\u043D\u043D\u044B\u0445 $${run.cost_usd.toFixed(2)}. \u0411\u044E\u0434\u0436\u0435\u0442 \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u0442: \u044D\u0442\u043E \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D, \u043A\u0440\u0443\u0442\u044F\u0449\u0438\u0439\u0441\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435, \u0430 \u043D\u0435 \u043D\u0430 \u0442\u044F\u0436\u0451\u043B\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0447\u0442\u043E \u043E\u043D \u0434\u0435\u043B\u0430\u0435\u0442.`,
|
|
19490
|
+
runId: run.id,
|
|
19491
|
+
...hrefOf(run.task_url)
|
|
19492
|
+
});
|
|
19493
|
+
} catch (e) {
|
|
19494
|
+
console.error(`[overmind] \u0441\u0438\u0433\u043D\u0430\u043B \xAB\u0442\u0440\u0430\u0442\u0438\u0442, \u043D\u043E \u043D\u0435 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F\xBB \u043F\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id} \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B:`, e);
|
|
19495
|
+
}
|
|
19496
|
+
}
|
|
19497
|
+
continue;
|
|
19498
|
+
}
|
|
19499
|
+
if (decision.verdict === "too_early") {
|
|
19500
|
+
deps.db.addEvent(run.id, "step", `\u0432\u0435\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430: \u0437\u0430\u044F\u0432\u043A\u0430 \u0440\u0430\u043D\u043E \u2014 ${decision.reason} (\u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u0435: ${basis})`);
|
|
19501
|
+
continue;
|
|
19502
|
+
}
|
|
19503
|
+
deps.db.addEvent(run.id, "note", `${heavyGrantText(decision.ceilingUsd ?? 0, decision.reason)} (\u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u0435: ${basis})`);
|
|
19504
|
+
narrate(
|
|
19505
|
+
deps,
|
|
19506
|
+
`${droneLabel(run)}: \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u0440\u0438\u0437\u043D\u0430\u043D\u0430 \u0442\u044F\u0436\u0451\u043B\u043E\u0439 \u2014 \u043F\u043E\u0442\u043E\u043B\u043E\u043A $${(decision.ceilingUsd ?? 0).toFixed(2)}, \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043F\u043E\u043A\u0430 \u043D\u0435 \u0431\u0435\u0440\u0443 (TASWM-535).`
|
|
19507
|
+
);
|
|
19508
|
+
}
|
|
19509
|
+
refreshHeavyRuns(deps);
|
|
19510
|
+
}
|
|
19071
19511
|
async function manageRelief(deps, now, droneCap, stormActive) {
|
|
19072
19512
|
const state = deps.relief ?? (deps.relief = {});
|
|
19073
19513
|
const cfg = state.config ? resolveReliefConfig(state.config) : DEFAULT_RELIEF_CONFIG;
|
|
@@ -19076,7 +19516,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19076
19516
|
state.reliefSince = needsRelief ? state.reliefSince ?? now : null;
|
|
19077
19517
|
if (!resourcesOk2 || needsRelief) state.overloadedAt = now;
|
|
19078
19518
|
if (needsRelief && deps.kill && pauseDue(state.reliefSince, state.lastPauseAt ?? null, now, cfg)) {
|
|
19079
|
-
const candidate = pickPauseCandidate(deps.db.listRuns("running"));
|
|
19519
|
+
const candidate = pickPauseCandidate(deps.db.listRuns("running"), new Set(heavyRunsInFlight(deps).map((r) => r.id)));
|
|
19080
19520
|
if (candidate) {
|
|
19081
19521
|
let stepsSinceResume = null;
|
|
19082
19522
|
try {
|
|
@@ -19095,17 +19535,31 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19095
19535
|
console.error(`[overmind] \u0440\u0435\u043A\u043B\u0435\u0439\u043C worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
19096
19536
|
}
|
|
19097
19537
|
}
|
|
19538
|
+
let saved = false;
|
|
19098
19539
|
if (candidate.worktree_path && deps.pauseSnapshot) {
|
|
19099
19540
|
try {
|
|
19100
|
-
|
|
19541
|
+
saved = await deps.pauseSnapshot(candidate.worktree_path);
|
|
19101
19542
|
if (saved) deps.db.addEvent(candidate.id, "step", "\u043D\u0435\u0434\u043E\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043E+\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u043F\u0435\u0440\u0435\u0434 \u043F\u0430\u0443\u0437\u043E\u0439 (TASWM-229) \u2014 --continue \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u0441 \u044D\u0442\u043E\u0439 \u0442\u043E\u0447\u043A\u0438");
|
|
19102
19543
|
} catch (e) {
|
|
19103
19544
|
console.error(`[overmind] \u0441\u043D\u0430\u043F\u0448\u043E\u0442 worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
19104
19545
|
}
|
|
19105
19546
|
}
|
|
19547
|
+
const canContinue = !!candidate.worktree_path && worktreeAndTranscriptAlive(deps, candidate.worktree_path);
|
|
19106
19548
|
deps.db.updateRun(candidate.id, { status: "paused", stop_reason: RELIEF_PAUSE_REASON });
|
|
19107
|
-
deps.db.addEvent(candidate.id, "paused",
|
|
19549
|
+
deps.db.addEvent(candidate.id, "paused", reliefPauseReason(canContinue));
|
|
19108
19550
|
state.lastPauseAt = now;
|
|
19551
|
+
const loopEntry = deps.reliefLoop?.recordPause(
|
|
19552
|
+
reliefLoopKey(normalizeRepo(candidate.repo || deps.repoFallback), candidate.task_slug),
|
|
19553
|
+
now,
|
|
19554
|
+
candidate.pr_url != null || saved
|
|
19555
|
+
);
|
|
19556
|
+
if (loopEntry && loopEntry.pauses > 1) {
|
|
19557
|
+
deps.db.addEvent(
|
|
19558
|
+
candidate.id,
|
|
19559
|
+
"step",
|
|
19560
|
+
`${RELIEF_PAUSE_MARK}: \u0441\u043D\u044F\u0442\u0438\u0435 ${loopEntry.pauses}-\u0435 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u043D\u0430 ${Math.round(reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs) / 1e3)}\u0441 (TASWM-531)`
|
|
19561
|
+
);
|
|
19562
|
+
}
|
|
19109
19563
|
recordReliefPause(
|
|
19110
19564
|
state.cost ??= { pauses: [] },
|
|
19111
19565
|
{ runId: candidate.id, label: droneLabel(candidate), at: now, resumedAt: null, stepsSinceResume },
|
|
@@ -19137,7 +19591,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19137
19591
|
state.lastLoggedGateReason = null;
|
|
19138
19592
|
}
|
|
19139
19593
|
if (countActiveDrones(deps.db) >= droneCap) return;
|
|
19140
|
-
const pausedForRelief = deps.db.listRuns("paused").filter((r) => r.stop_reason
|
|
19594
|
+
const pausedForRelief = deps.db.listRuns("paused").filter((r) => isReliefPause(r.stop_reason));
|
|
19141
19595
|
const recovering = pausedForRelief.length > 0 || state.overloadedAt != null && now - state.overloadedAt < cfg.rampWindowMs;
|
|
19142
19596
|
if (!recovering) {
|
|
19143
19597
|
await batchSpawnQueue(deps, droneCap);
|
|
@@ -19148,10 +19602,27 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19148
19602
|
if (resumeCandidate) {
|
|
19149
19603
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
19150
19604
|
if (!deps.respawn) return;
|
|
19605
|
+
const loopKey = reliefLoopKey(normalizeRepo(resumeCandidate.repo || deps.repoFallback), resumeCandidate.task_slug);
|
|
19606
|
+
const loopEntry = deps.reliefLoop?.get(loopKey);
|
|
19607
|
+
if (loopEntry) {
|
|
19608
|
+
const loopCfg = resolveReliefLoopConfig(deps.reliefLoopConfig);
|
|
19609
|
+
if (reliefLoopTripped(loopEntry, loopCfg)) {
|
|
19610
|
+
if (!loopEntry.escalated) {
|
|
19611
|
+
deps.db.updateRun(resumeCandidate.id, { status: "stalled", stop_reason: RELIEF_LOOP_STOP_REASON });
|
|
19612
|
+
deps.db.addEvent(
|
|
19613
|
+
resumeCandidate.id,
|
|
19614
|
+
"step",
|
|
19615
|
+
`${RELIEF_LOOP_ESCALATION_MARK}: \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043D\u0438\u043C\u0430\u043B\u0430 \u043F\u0440\u043E\u0433\u043E\u043D ${loopEntry.pauses} \u0440\u0430\u0437(\u0430) \u043F\u043E\u0434\u0440\u044F\u0434 \u0438 \u043D\u0438 \u043E\u0434\u0438\u043D \u043A\u0440\u0443\u0433 \u043D\u0435 \u043F\u0440\u0438\u043D\u0451\u0441 \u0441\u0434\u0430\u0447\u0438 (\u043D\u0438 PR, \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u0430) \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A (\u043F\u043E\u0442\u043E\u043B\u043E\u043A ${loopCfg.limit}, TASWM-531)`
|
|
19616
|
+
);
|
|
19617
|
+
deps.reliefLoop?.markEscalated(loopKey);
|
|
19618
|
+
narrate(deps, `${droneLabel(resumeCandidate)} \u0441\u043D\u0438\u043C\u0430\u043B\u0441\u044F \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u043E\u0439 ${loopEntry.pauses} \u0440\u0430\u0437(\u0430) \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u2014 \u044D\u0442\u043E \u043F\u0435\u0442\u043B\u044F, \u0434\u0430\u043B\u044C\u0448\u0435 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440.`);
|
|
19619
|
+
}
|
|
19620
|
+
return;
|
|
19621
|
+
}
|
|
19622
|
+
if (now - loopEntry.lastPauseAt < reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs, loopCfg)) return;
|
|
19623
|
+
}
|
|
19151
19624
|
const wt = resumeCandidate.worktree_path;
|
|
19152
|
-
const
|
|
19153
|
-
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
19154
|
-
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
19625
|
+
const canContinue = !!wt && worktreeAndTranscriptAlive(deps, wt);
|
|
19155
19626
|
const mode = canContinue ? "continue" : "fresh";
|
|
19156
19627
|
if (mode === "continue" && wt && deps.reclaimWorktree) {
|
|
19157
19628
|
try {
|
|
@@ -19190,7 +19661,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
19190
19661
|
(runId) => {
|
|
19191
19662
|
const run = runById.get(runId);
|
|
19192
19663
|
if (!run) return now;
|
|
19193
|
-
if (run.status === "paused" && run.stop_reason
|
|
19664
|
+
if (run.status === "paused" && isReliefPause(run.stop_reason)) return null;
|
|
19194
19665
|
return sqliteMs(run.updated_at);
|
|
19195
19666
|
},
|
|
19196
19667
|
now
|
|
@@ -19206,7 +19677,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
19206
19677
|
if (!reliefAlertDue(cost, thresholds)) return [];
|
|
19207
19678
|
const pressure = deps.hostPressure?.() ?? { culprits: [], readings: [], totalGb: 0 };
|
|
19208
19679
|
const occupying = allRuns.filter(
|
|
19209
|
-
(r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || r.status === "paused" && r.stop_reason
|
|
19680
|
+
(r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || r.status === "paused" && isReliefPause(r.stop_reason))
|
|
19210
19681
|
);
|
|
19211
19682
|
const repoOf = (r) => normalizeRepo(r.repo || deps.repoFallback || "");
|
|
19212
19683
|
const busyRepos = [...new Set(occupying.map(repoOf).filter(Boolean))].filter(
|
|
@@ -19427,7 +19898,7 @@ function buildEscalationCandidates(db) {
|
|
|
19427
19898
|
}
|
|
19428
19899
|
async function tickOnce(deps) {
|
|
19429
19900
|
const now = deps.now ? deps.now() : Date.now();
|
|
19430
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19901
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
19431
19902
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
19432
19903
|
deps.sampleHost?.();
|
|
19433
19904
|
if (deps.rehydrate) {
|
|
@@ -19552,6 +20023,11 @@ async function tickOnce(deps) {
|
|
|
19552
20023
|
}
|
|
19553
20024
|
const droneCap = deps.maxDrones ? await deps.maxDrones() : Infinity;
|
|
19554
20025
|
if (stormActive) noteStormStall(deps);
|
|
20026
|
+
try {
|
|
20027
|
+
await manageWeight(deps);
|
|
20028
|
+
} catch (e) {
|
|
20029
|
+
console.error("[overmind] \u0440\u0430\u0437\u0431\u043E\u0440 \u0432\u0435\u0441\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
|
|
20030
|
+
}
|
|
19555
20031
|
if (deps.resourcesOk) {
|
|
19556
20032
|
await manageRelief(deps, now, droneCap, stormActive);
|
|
19557
20033
|
} else if (!stormActive) {
|
|
@@ -20013,7 +20489,7 @@ async function tickOnce(deps) {
|
|
|
20013
20489
|
}
|
|
20014
20490
|
for (const run of deps.db.listRuns("paused")) {
|
|
20015
20491
|
if (run.kind === "hero") continue;
|
|
20016
|
-
if (run.stop_reason === AWAITING_CI_REASON || run.stop_reason
|
|
20492
|
+
if (run.stop_reason === AWAITING_CI_REASON || isReliefPause(run.stop_reason)) continue;
|
|
20017
20493
|
const last = lastActivityMs(deps.db, run);
|
|
20018
20494
|
if (now - last <= STALL_FLOOR_MS) continue;
|
|
20019
20495
|
const evs = deps.db.events(run.id);
|
|
@@ -20029,7 +20505,7 @@ async function tickOnce(deps) {
|
|
|
20029
20505
|
for (const run of deps.db.listRuns("paused")) {
|
|
20030
20506
|
if (run.kind === "hero") continue;
|
|
20031
20507
|
if (run.reviews_run_id != null) continue;
|
|
20032
|
-
if (run.stop_reason
|
|
20508
|
+
if (isReliefPause(run.stop_reason)) continue;
|
|
20033
20509
|
if (NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r))) continue;
|
|
20034
20510
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
20035
20511
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
@@ -20146,7 +20622,12 @@ async function tickOnce(deps) {
|
|
|
20146
20622
|
console.error("[overmind] \u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430: \u0442\u0438\u043A \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
|
|
20147
20623
|
}
|
|
20148
20624
|
}
|
|
20149
|
-
|
|
20625
|
+
const heavyInFlight = heavyRunsInFlight(deps);
|
|
20626
|
+
if (heavyInFlight.length && deps.intake) {
|
|
20627
|
+
const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), "weight-intake-hold", intakeHoldReason(heavyInFlight.map(droneLabel).join(", ")), GATE_LOG_HEARTBEAT_TICKS);
|
|
20628
|
+
if (d.log) console.error(`[overmind] \u0430\u0432\u0442\u043E-\u0438\u043D\u0442\u0435\u0439\u043A \u043F\u0440\u0438\u0434\u0435\u0440\u0436\u0430\u043D: ${intakeHoldReason(heavyInFlight.map(droneLabel).join(", "))}${d.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${d.ticks}`}`);
|
|
20629
|
+
}
|
|
20630
|
+
if (deps.intake && !stormActive && !deps.control?.paused && !heavyInFlight.length && deps.intake.maxInFlight > 0 && (!deps.resourcesOk || deps.resourcesOk())) {
|
|
20150
20631
|
const it = deps.intake;
|
|
20151
20632
|
const claimCap = deps.maxDrones ? droneCap : it.maxInFlight;
|
|
20152
20633
|
const sinceLastClaim = it.lastClaimAt == null ? Infinity : now - it.lastClaimAt;
|
|
@@ -20223,7 +20704,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
20223
20704
|
model: reviewRun.model,
|
|
20224
20705
|
review_model: reviewRun.review_model
|
|
20225
20706
|
});
|
|
20226
|
-
const wt =
|
|
20707
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
20227
20708
|
try {
|
|
20228
20709
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20229
20710
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -20302,7 +20783,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
20302
20783
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
20303
20784
|
workspace_id: reviewRun.workspace_id
|
|
20304
20785
|
});
|
|
20305
|
-
const wt =
|
|
20786
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
20306
20787
|
try {
|
|
20307
20788
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20308
20789
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -20383,7 +20864,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
20383
20864
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
20384
20865
|
workspace_id: verifiedRun.workspace_id
|
|
20385
20866
|
});
|
|
20386
|
-
const wt =
|
|
20867
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
20387
20868
|
try {
|
|
20388
20869
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20389
20870
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
@@ -20416,7 +20897,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20416
20897
|
if (mode === "continue") {
|
|
20417
20898
|
const prompt2 = nudge ?? (run.kind === "hero" ? HERO_CONTINUE_NUDGE : CONTINUE_NUDGE);
|
|
20418
20899
|
const pid2 = deps.launch({ worktree: wt, run, prompt: prompt2, continueSession: true });
|
|
20419
|
-
deps.db.updateRun(run.id, { status: "running", pid: pid2 });
|
|
20900
|
+
deps.db.updateRun(run.id, { status: "running", pid: pid2, ...worktreePathPatch(run, wt) });
|
|
20420
20901
|
return;
|
|
20421
20902
|
}
|
|
20422
20903
|
const [task, verifyMode, projectBase, zoneNotes] = await Promise.all([
|
|
@@ -20429,7 +20910,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20429
20910
|
]);
|
|
20430
20911
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
20431
20912
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
20432
|
-
if (!
|
|
20913
|
+
if (!existsSync20(wt)) {
|
|
20433
20914
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
20434
20915
|
const source = await addWorktree(repo, wt, branch, {
|
|
20435
20916
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -20453,10 +20934,13 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20453
20934
|
const notice = depsPromptNotice(depsReport);
|
|
20454
20935
|
const prompt = [freshPrompt, notice, nudge].filter(Boolean).join("\n\n");
|
|
20455
20936
|
const pid = deps.launch({ worktree: wt, run: runToSpawn, prompt });
|
|
20456
|
-
deps.db.updateRun(run.id, { status: "running", pid });
|
|
20937
|
+
deps.db.updateRun(run.id, { status: "running", pid, ...worktreePathPatch(run, wt) });
|
|
20938
|
+
}
|
|
20939
|
+
function worktreePathPatch(run, wt) {
|
|
20940
|
+
return run.worktree_path === wt ? {} : { worktree_path: wt };
|
|
20457
20941
|
}
|
|
20458
20942
|
async function syncCrashedOnStartup(deps) {
|
|
20459
|
-
const worktreeExists = deps.worktreeExists ??
|
|
20943
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
20460
20944
|
let orphaned = 0;
|
|
20461
20945
|
let merged = 0;
|
|
20462
20946
|
let requeued = 0;
|
|
@@ -20610,7 +21094,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
20610
21094
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
20611
21095
|
targetDir: WORKTREES_DIR,
|
|
20612
21096
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
20613
|
-
exists: (d) =>
|
|
21097
|
+
exists: (d) => existsSync20(d),
|
|
20614
21098
|
isNonEmptyDir: (d) => {
|
|
20615
21099
|
try {
|
|
20616
21100
|
return readdirSync13(d).length > 0;
|
|
@@ -20619,13 +21103,13 @@ async function migrateWorktreesAtStartup() {
|
|
|
20619
21103
|
}
|
|
20620
21104
|
},
|
|
20621
21105
|
liveDronesUnder: (d) => pidsWithCwdUnder(d).length > 0,
|
|
20622
|
-
rename: (from, to) =>
|
|
21106
|
+
rename: (from, to) => renameSync12(from, to),
|
|
20623
21107
|
repairMoved: (t) => repairMovedWorktrees(t),
|
|
20624
21108
|
log: (m) => console.log(m)
|
|
20625
21109
|
});
|
|
20626
21110
|
} catch (e) {
|
|
20627
21111
|
console.error("[migrate] \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F worktrees \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u2014 \u0444\u043E\u043B\u0431\u044D\u043A:", e);
|
|
20628
|
-
return
|
|
21112
|
+
return existsSync20(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
20629
21113
|
}
|
|
20630
21114
|
}
|
|
20631
21115
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -20728,7 +21212,10 @@ async function runDaemon(opts) {
|
|
|
20728
21212
|
recordEffort: (runId, effort) => {
|
|
20729
21213
|
db.updateRun(runId, { effort });
|
|
20730
21214
|
},
|
|
20731
|
-
maxCostUsd: () => lastMaxCostUsd
|
|
21215
|
+
maxCostUsd: () => lastMaxCostUsd,
|
|
21216
|
+
// TASWM-535: поднятый потолок тяжёлого прогона живёт в его ленте — спавн читает её
|
|
21217
|
+
// оттуда же, откуда потом прочитает прод-сторож.
|
|
21218
|
+
weightEvents: (run) => db.events(run.id)
|
|
20732
21219
|
});
|
|
20733
21220
|
let prepareReader;
|
|
20734
21221
|
const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
|
|
@@ -21278,7 +21765,7 @@ async function runDaemon(opts) {
|
|
|
21278
21765
|
isDirty: (p) => worktreeDirty(p),
|
|
21279
21766
|
releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
|
|
21280
21767
|
pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
|
|
21281
|
-
worktreeExists:
|
|
21768
|
+
worktreeExists: existsSync20,
|
|
21282
21769
|
liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
|
|
21283
21770
|
narrate: narrator.narrate
|
|
21284
21771
|
});
|
|
@@ -21298,6 +21785,11 @@ async function runDaemon(opts) {
|
|
|
21298
21785
|
// mtime транскрипта claude-сессии как liveness (TASWM-136): точнее коммитов — обновляется
|
|
21299
21786
|
// на каждое действие дрона (Edit/Read/tool-use), не только на коммит.
|
|
21300
21787
|
transcriptMtimeMs: (p) => EXECUTOR.session.mtimeMs(p),
|
|
21788
|
+
// ВЕС ЗАДАЧИ (TASWM-535): чем демон проверяет, что заявивший о тяжести дрон РЕАЛЬНО
|
|
21789
|
+
// продвигается (коммиты от базы прогона), и от какого потолка считать ему добавку —
|
|
21790
|
+
// от той же живой настройки роя, что уезжает харнессу на спавне.
|
|
21791
|
+
runCommits: (run) => runCommitCount(run.worktree_path, { baseBranch: run.base_branch ?? null }),
|
|
21792
|
+
maxCostUsd: () => lastMaxCostUsd,
|
|
21301
21793
|
// Ресурс-гейт спавна (TASWM-130): реальные показания хоста (load1 / free RAM).
|
|
21302
21794
|
resourcesOk: hostResourcesOk,
|
|
21303
21795
|
// Замер занятости CPU раз в тик (TASWM-397): окно фиксированной длины набирает пульс
|
|
@@ -21318,6 +21810,9 @@ async function runDaemon(opts) {
|
|
|
21318
21810
|
// разгрузки (TASWM-316) — заводим ОДИН раз на цикл демона, tickOnce (manageRelief)
|
|
21319
21811
|
// мутирует эти же поля каждый тик.
|
|
21320
21812
|
relief: { cost: { pauses: [] } },
|
|
21813
|
+
// Счёт снятий разгрузкой подряд без сдачи (TASWM-531) — durable, в отличие от relief-состояния
|
|
21814
|
+
// выше: он обязан пережить и рестарт демона, и подмену прогона сторожем прогресса.
|
|
21815
|
+
reliefLoop: new ReliefLoopStore(SWARM_HOME),
|
|
21321
21816
|
// Гейт окружения машины (TASWM-313): не спавним дрона, если проекту не хватает сервисов
|
|
21322
21817
|
// (лежащий докер = заведомо красные api-тесты). Только client-режим — требования живут в
|
|
21323
21818
|
// настройках проекта; без него гейта нет и рой работает как раньше. Дедуп событий —
|
|
@@ -21401,7 +21896,7 @@ async function runDaemon(opts) {
|
|
|
21401
21896
|
mode,
|
|
21402
21897
|
nudge
|
|
21403
21898
|
),
|
|
21404
|
-
worktreeExists: (p) =>
|
|
21899
|
+
worktreeExists: (p) => existsSync20(p),
|
|
21405
21900
|
// TASWM-514: с этого момента считаем попытки спавна — счётчик живёт ровно одну жизнь
|
|
21406
21901
|
// процесса демона и рестарт не переживает (см. preStartAttempts).
|
|
21407
21902
|
startedAt: Date.now(),
|
|
@@ -21870,6 +22365,7 @@ export {
|
|
|
21870
22365
|
HERO_CONTINUE_NUDGE,
|
|
21871
22366
|
OPEN_PR_HOLD_MARK,
|
|
21872
22367
|
OVERMIND_ENTRY,
|
|
22368
|
+
RELIEF_LOOP_STOP_REASON,
|
|
21873
22369
|
RESOLVE_ESCALATION_MARK,
|
|
21874
22370
|
RESOLVE_LIMIT,
|
|
21875
22371
|
RESPAWN_LIMIT,
|