@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.
Files changed (3) hide show
  1. package/dist/cli.js +43 -11
  2. package/dist/overmind.js +694 -198
  3. 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 existsSync11, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
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 join18, sep as sep2 } from "path";
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(join18(dir, ".git")).isFile();
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 (!existsSync11(worktree)) return false;
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 (existsSync11(worktree)) {
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 = existsSync11(busyAt);
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 = join18(commonDir, "info");
3903
- const excludeFile = join18(infoDir, "exclude");
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 : join18(worktree, 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 = join18(gitDir, "swarm-hooks");
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(join18(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3938
- await chmod(join18(hooksDir, hook), 493);
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 ? join18(commonDir, "hooks") : null;
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) => join18(dir, d.name));
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 readFileSync17, writeFileSync as writeFileSync11 } from "fs";
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 = readFileSync17(path, "utf8").trim();
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
- writeFileSync11(path, secret, { mode: 384 });
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 bad(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`);
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 bad(400, why);
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 join22 } from "path";
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 = join22(opts.worktree, ATTACHMENTS_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(join22(dir, name), file.bytes);
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 join23 } from "path";
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 : join23(opts.worktree, 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 readFileSync18, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
4878
- import { join as join24 } from "path";
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(readFileSync18(STORE_PATH2, "utf8"));
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
- writeFileSync12(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
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(readFileSync18(CLIENT_PATH, "utf8")).clientId;
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
- writeFileSync12(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
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 = join24(SWARM_HOME, "oauth.json");
5014
- CLIENT_PATH = join24(SWARM_HOME, "oauth-client.json");
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 readFileSync19, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
5061
- import { join as join25 } from "path";
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(readFileSync19(WORKSPACE_PATH, "utf8"));
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 = join25(SWARM_HOME, "workspace.json");
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 existsSync14 } from "fs";
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 (!existsSync14(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
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 existsSync19, readFileSync as readFileSync23, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6652
- import { basename as basename6, dirname as dirname5, join as join32 } from "path";
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, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 --continue), \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)`;
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 median(xs) {
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 ? median(gaps) : null,
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 existsSync10, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
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 join15 } from "path";
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 join14, relative, sep } from "path";
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 = join14(droneHome, existing);
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 = join14(droneHome, name);
10472
+ const link2 = join15(droneHome, name);
10171
10473
  try {
10172
10474
  lstatSync(link2);
10173
10475
  continue;
10174
10476
  } catch {
10175
10477
  }
10176
- const target = join14(realHome, name);
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, join14(realHome, ...segments)]));
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 = join15(worktree, ".turbo", "cache");
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 join15(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
10639
+ return join16(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
10338
10640
  }
10339
10641
  function roleDir(worktree, kind) {
10340
- return join15(worktree, ".claude", "roles", kind);
10642
+ return join16(worktree, ".claude", "roles", kind);
10341
10643
  }
10342
10644
  function roleDirs(worktree, kind) {
10343
- return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync10(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 join15(dir, `${next}.json`);
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 && !existsSync10(join15(cfg.droneIsolation.swarmHome, "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 ${join15(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.`
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 readFileSync13, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10506
- import { join as join16 } from "path";
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 = join16(dir, f);
10830
+ const full = join17(dir, f);
10529
10831
  let raw = null;
10530
10832
  try {
10531
- raw = readFileSync13(full, "utf8");
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
- renameSync8(full, `${full}.done`);
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 readFileSync14 } from "fs";
10581
- import { join as join17 } from "path";
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 readFileSync14(join17(dir, latest.name), "utf8");
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 existsSync13, readdirSync as readdirSync12, readFileSync as readFileSync16 } from "fs";
10642
- import { join as join21 } from "path";
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 join19 } from "path";
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 join19(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
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 existsSync12, readFileSync as readFileSync15 } from "fs";
10716
- import { join as join20 } from "path";
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 = join20(src, e.name);
10731
- const to = join20(dst, e.name);
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 join20(cacheRoot(io), key);
11046
+ return join21(cacheRoot(io), key);
10745
11047
  }
10746
11048
  function readManifest(dir) {
10747
11049
  try {
10748
- const parsed = JSON.parse(readFileSync15(join20(dir, "manifest.json"), "utf8"));
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 ?? existsSync12;
10761
- return exists(join20(snapshotDir(io, key), "manifest.json"));
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 ?? existsSync12;
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 = join20(worktree, rel);
11075
+ const target = join21(worktree, rel);
10774
11076
  if (exists(target)) continue;
10775
- await clone(join20(dir, "trees", String(i)), target);
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(join20(worktree, rel), { recursive: true, force: true }).catch(() => {
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 ?? existsSync12;
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(join20(worktree, rel)));
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 = join20(root, `.tmp-${key}-${process.pid}`);
11100
+ const tmp = join21(root, `.tmp-${key}-${process.pid}`);
10799
11101
  try {
10800
11102
  await rm2(tmp, { recursive: true, force: true });
10801
- await mkdir2(join20(tmp, "trees"), { recursive: true });
10802
- for (let i = 0; i < present.length; i++) await clone(join20(worktree, present[i] ?? ""), join20(tmp, "trees", String(i)));
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(join20(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
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(join20(root, name)).catch(() => null);
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(join20(root, name), { recursive: true, force: true }).catch(() => {
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(join20(root, name)).catch(() => null);
11148
+ const s = await lstat(join21(root, name)).catch(() => null);
10847
11149
  if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
10848
- await rm2(join20(root, name), { recursive: true, force: true }).catch(() => {
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 readFileSync16(p, "utf8");
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 ?? existsSync13;
11217
+ const exists = io.exists ?? existsSync14;
10916
11218
  const readText = io.readText ?? safeReadText;
10917
- const hasPkgJson = exists(join21(worktree, "package.json"));
11219
+ const hasPkgJson = exists(join22(worktree, "package.json"));
10918
11220
  if (!hasPkgJson) {
10919
- if (exists(join21(worktree, "deno.json")) || exists(join21(worktree, "deno.jsonc")) || exists(join21(worktree, "deno.lock"))) return "deno";
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, join21(worktree, "package.json"));
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(join21(worktree, "bun.lock")) || exists(join21(worktree, "bun.lockb"))) return "bun";
10926
- if (exists(join21(worktree, "pnpm-lock.yaml"))) return "pnpm";
10927
- if (exists(join21(worktree, "yarn.lock"))) return "yarn";
10928
- if (exists(join21(worktree, "package-lock.json"))) return "npm";
10929
- if (exists(join21(worktree, "deno.lock"))) return "deno";
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 ?? existsSync13;
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: join21(worktree, "node_modules"), source: "detect" }
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(join21(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
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(join21(worktree, refine.file))) continue;
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 = join21(worktree, ".github", "workflows");
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(join21(dir, name));
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(join21(worktree, "pnpm-workspace.yaml"));
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, join21(worktree, "package.json"));
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 = join21(worktree, glob);
11116
- if (io.exists(join21(dir, "package.json"))) dirs.add(dir);
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 = join21(worktree, glob.slice(0, star).replace(/\/$/, ""));
11421
+ const base3 = join22(worktree, glob.slice(0, star).replace(/\/$/, ""));
11120
11422
  for (const name of io.listDirs(base3)) {
11121
- const dir = join21(base3, name);
11122
- if (io.exists(join21(dir, "package.json"))) dirs.add(dir);
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 ?? existsSync13;
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, join21(dir, "package.json"));
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(join21(dir, e)));
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(join21(worktree, file));
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 ?? existsSync13;
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(join21(dir, "prisma", "schema.prisma")) && listDirs(join21(dir, "prisma", "migrations")).length > 0);
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 ?? existsSync13;
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(join21(worktree, name));
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(join21(worktree, rel, "package.json"));
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) => join21(rel, "node_modules"))] };
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 ?? existsSync13;
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(join21(worktree, "node_modules"))) {
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.32",
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: "f40f0fb4d2f96809f88b7d6ae4f086c5f4f004da"
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 join26 } from "path";
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(join26(worktree, b.path)) - mtime(join26(worktree, a.path)));
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 existsSync16 } from "fs";
14468
- import { join as join28 } from "path";
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 existsSync15, readFileSync as readFileSync20 } from "fs";
14474
- import { delimiter, join as join27 } from "path";
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 existsSync15(name) ? name : null;
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 = join27(dir, name + ext);
14482
- if (existsSync15(candidate)) return candidate;
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 has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
14501
- return has("npm") ? "npm" : "unknown";
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 ?? existsSync16;
14566
- const marker = COMPOSE_MARKERS.find((f) => exists(join28(repo, f)));
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 readFileSync21, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
16310
- import { join as join29 } from "path";
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 join29(home, "host-command.json");
16670
+ return join30(home, "host-command.json");
16313
16671
  }
16314
16672
  function readState(path) {
16315
16673
  try {
16316
- return JSON.parse(readFileSync21(path, "utf8"));
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
- writeFileSync14(`${path}.tmp`, JSON.stringify(state));
16324
- renameSync9(`${path}.tmp`, path);
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 existsSync17 } from "fs";
16487
- import { join as join30 } from "path";
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 ?? existsSync17;
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(join30(repo, "pnpm-lock.yaml"))) {
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 existsSync18, readFileSync as readFileSync22, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
16689
- import { join as join31 } from "path";
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 join31(home, "anomaly-state.json");
17054
+ return join32(home, "anomaly-state.json");
16697
17055
  }
16698
17056
  function loadAnomalyState(home, now = Date.now(), io = {}) {
16699
- const exists = io.exists ?? existsSync18;
16700
- const readFile2 = io.readFile ?? ((p) => readFileSync22(p, "utf8"));
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
- writeFileSync15(`${p}.tmp`, data);
16731
- renameSync10(`${p}.tmp`, p);
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 = join32(dirname5(THIS_FILE), "..", "docs", "HERO.md");
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 = readFileSync23(HERO_CHARTER_PATH, "utf8");
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 ?? existsSync19;
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 = join32(base3, ".github", "workflows");
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 ?? existsSync19;
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 ?? existsSync19;
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
- const saved = await deps.pauseSnapshot(candidate.worktree_path);
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", RELIEF_PAUSE_REASON);
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 === RELIEF_PAUSE_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 worktreeExists = deps.worktreeExists ?? existsSync19;
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 === RELIEF_PAUSE_REASON) return null;
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 === RELIEF_PAUSE_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 ?? existsSync19;
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 === RELIEF_PAUSE_REASON) continue;
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 === RELIEF_PAUSE_REASON) continue;
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
- if (deps.intake && !stormActive && !deps.control?.paused && deps.intake.maxInFlight > 0 && (!deps.resourcesOk || deps.resourcesOk())) {
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 = join32(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
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 = join32(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
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 = join32(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
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 (!existsSync19(wt)) {
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 ?? existsSync19;
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) => existsSync19(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) => renameSync11(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 existsSync19(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
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: existsSync19,
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) => existsSync19(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,