@taskless-app/swarm 0.6.33 → 0.6.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1202,6 +1202,37 @@ var init_runGuards = __esm({
1202
1202
  }
1203
1203
  });
1204
1204
 
1205
+ // ../swarm-core/src/taskWeight.ts
1206
+ function isHeavyGrantEvent(e) {
1207
+ return has(e, HEAVY_GRANT_MARK);
1208
+ }
1209
+ function grantedCeilingUsd(events) {
1210
+ let max = null;
1211
+ for (const e of events) {
1212
+ if (!isHeavyGrantEvent(e)) continue;
1213
+ const m = /потолок \$([0-9]+(?:\.[0-9]+)?)/.exec(e.text ?? "");
1214
+ if (!m) continue;
1215
+ const v = Number(m[1]);
1216
+ if (Number.isFinite(v) && (max == null || v > max)) max = v;
1217
+ }
1218
+ return max;
1219
+ }
1220
+ function effectiveCostCeilingUsd(baseUsd, events) {
1221
+ if (!(baseUsd > 0)) return baseUsd;
1222
+ const granted = grantedCeilingUsd(events);
1223
+ if (granted == null) return baseUsd;
1224
+ return Math.min(Math.max(baseUsd, granted), baseUsd * HEAVY_CEILING_MAX_FACTOR);
1225
+ }
1226
+ var HEAVY_CEILING_MAX_FACTOR, HEAVY_GRANT_MARK, has;
1227
+ var init_taskWeight = __esm({
1228
+ "../swarm-core/src/taskWeight.ts"() {
1229
+ "use strict";
1230
+ HEAVY_CEILING_MAX_FACTOR = 2;
1231
+ HEAVY_GRANT_MARK = "\u0432\u0435\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430: \u0442\u044F\u0436\u0451\u043B\u0430\u044F";
1232
+ has = (e, mark) => !!e.text?.includes(mark);
1233
+ }
1234
+ });
1235
+
1205
1236
  // ../swarm-core/src/codeZones.ts
1206
1237
  function hasZoneModel(m) {
1207
1238
  return m.tasks > 0 && m.zones.length > 0;
@@ -1661,6 +1692,7 @@ var init_src = __esm({
1661
1692
  init_cost();
1662
1693
  init_modelPricing();
1663
1694
  init_runGuards();
1695
+ init_taskWeight();
1664
1696
  init_codeZones();
1665
1697
  init_effort();
1666
1698
  init_queuePlan();
@@ -3242,7 +3274,7 @@ var init_package = __esm({
3242
3274
  "package.json"() {
3243
3275
  package_default = {
3244
3276
  name: "@taskless-app/swarm",
3245
- version: "0.6.33",
3277
+ version: "0.6.34",
3246
3278
  type: "module",
3247
3279
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3248
3280
  keywords: [
@@ -3293,7 +3325,7 @@ var init_package = __esm({
3293
3325
  execa: "^10.0.0",
3294
3326
  zod: "3.25.76"
3295
3327
  },
3296
- gitHead: "7b17b06b041af953afde46fbd41fc68ebc6dc4e9"
3328
+ gitHead: "4eabd911d4dfd557bfbfcbc27afd36415714b7eb"
3297
3329
  };
3298
3330
  }
3299
3331
  });
@@ -6077,10 +6109,10 @@ function detectPlatformKind(io = {}) {
6077
6109
  function detectSystemPm(kind, io = {}) {
6078
6110
  const env = io.env ?? process.env;
6079
6111
  const platform = io.platform ?? process.platform;
6080
- const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
6081
- if (kind === "macos") return has("brew") ? "brew" : null;
6082
- if (kind === "windows") return has("winget") ? "winget" : null;
6083
- return LINUX_PMS.find((p) => has(p.cmd))?.pm ?? null;
6112
+ const has2 = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
6113
+ if (kind === "macos") return has2("brew") ? "brew" : null;
6114
+ if (kind === "windows") return has2("winget") ? "winget" : null;
6115
+ return LINUX_PMS.find((p) => has2(p.cmd))?.pm ?? null;
6084
6116
  }
6085
6117
  function detectPlatform(io = {}) {
6086
6118
  const kind = detectPlatformKind(io);
@@ -6120,8 +6152,8 @@ function detectGlobalPm(entry, io = {}) {
6120
6152
  if (/\/node_modules\//.test(p)) return "npm";
6121
6153
  const env = io.env ?? process.env;
6122
6154
  const platform = io.platform ?? process.platform;
6123
- const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
6124
- return has("npm") ? "npm" : "unknown";
6155
+ const has2 = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
6156
+ return has2("npm") ? "npm" : "unknown";
6125
6157
  }
6126
6158
  function globalInstallCommand(pm, spec) {
6127
6159
  switch (pm) {
@@ -11939,8 +11971,8 @@ function resolveTsxCli() {
11939
11971
  }
11940
11972
  }
11941
11973
  var MCP_SERVER_NAME = "overmind";
11942
- function launchBudgetUsd(limit, spentUsd, env = process.env) {
11943
- return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
11974
+ function launchBudgetUsd(limit, spentUsd, env = process.env, weightEvents = []) {
11975
+ return remainingRunBudgetUsd(effectiveCostCeilingUsd(limit ?? envCapUsd(env), weightEvents), spentUsd);
11944
11976
  }
11945
11977
  function envCapUsd(env) {
11946
11978
  const raw = env.SWARM_MAX_COST_USD;
@@ -12106,7 +12138,7 @@ function makeLauncher(cfg) {
12106
12138
  // момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
12107
12139
  // стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
12108
12140
  // ни жёг, — отсюда и $74 при лимите $20.
12109
- maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
12141
+ maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd, process.env, cfg.weightEvents?.(run) ?? []),
12110
12142
  // TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
12111
12143
  effort
12112
12144
  });
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();
@@ -10005,6 +10152,7 @@ function loadHostIdentity(deps) {
10005
10152
  }
10006
10153
 
10007
10154
  // src/overmind.ts
10155
+ init_src();
10008
10156
  init_resources();
10009
10157
 
10010
10158
  // src/relief.ts
@@ -10038,9 +10186,15 @@ function pauseDue(reliefSince, lastPauseAt, now, cfg) {
10038
10186
  if (reliefSince == null || now - reliefSince < cfg.sustainMs) return false;
10039
10187
  return lastPauseAt == null || now - lastPauseAt >= cfg.pauseWindowMs;
10040
10188
  }
10041
- function pickPauseCandidate(runs) {
10189
+ function pickPauseCandidate(runs, protectedRunIds) {
10042
10190
  const candidates = runs.filter(
10043
- (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)
10044
10198
  );
10045
10199
  if (!candidates.length) return null;
10046
10200
  return candidates.reduce((youngest, r) => r.created_at > youngest.created_at ? r : youngest);
@@ -10161,7 +10315,7 @@ var ReliefLoopStore = class {
10161
10315
  };
10162
10316
 
10163
10317
  // src/reliefCost.ts
10164
- function median(xs) {
10318
+ function median2(xs) {
10165
10319
  const s = [...xs].sort((a, b) => a - b);
10166
10320
  return s[Math.floor((s.length - 1) / 2)];
10167
10321
  }
@@ -10217,7 +10371,7 @@ function computeReliefCost(state, now, windowMs) {
10217
10371
  pauses: inWindow.length,
10218
10372
  runs: byRun.size,
10219
10373
  lostMs,
10220
- stepsBetween: gaps.length ? median(gaps) : null,
10374
+ stepsBetween: gaps.length ? median2(gaps) : null,
10221
10375
  worst
10222
10376
  };
10223
10377
  }
@@ -10441,8 +10595,8 @@ function resolveTsxCli() {
10441
10595
  }
10442
10596
  }
10443
10597
  var MCP_SERVER_NAME = "overmind";
10444
- function launchBudgetUsd(limit, spentUsd, env = process.env) {
10445
- return remainingRunBudgetUsd(limit ?? envCapUsd(env), spentUsd);
10598
+ function launchBudgetUsd(limit, spentUsd, env = process.env, weightEvents = []) {
10599
+ return remainingRunBudgetUsd(effectiveCostCeilingUsd(limit ?? envCapUsd(env), weightEvents), spentUsd);
10446
10600
  }
10447
10601
  function envCapUsd(env) {
10448
10602
  const raw = env.SWARM_MAX_COST_USD;
@@ -10608,7 +10762,7 @@ function makeLauncher(cfg) {
10608
10762
  // момент её совершения. Прод-сторож остаётся страховкой, но он по устройству видит
10609
10763
  // стоимость только по записанному итогу: живой прогон стоит для него ноль, сколько бы
10610
10764
  // ни жёг, — отсюда и $74 при лимите $20.
10611
- maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd),
10765
+ maxBudgetUsd: launchBudgetUsd(cfg.maxCostUsd?.() ?? null, run.cost_usd, process.env, cfg.weightEvents?.(run) ?? []),
10612
10766
  // TASWM-395: усилие рассуждения — харнессу флагом (claude `--effort`). null = флага нет.
10613
10767
  effort
10614
10768
  });
@@ -11709,7 +11863,7 @@ function parkReasonUnretriable(reason) {
11709
11863
  // package.json
11710
11864
  var package_default = {
11711
11865
  name: "@taskless-app/swarm",
11712
- version: "0.6.33",
11866
+ version: "0.6.34",
11713
11867
  type: "module",
11714
11868
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
11715
11869
  keywords: [
@@ -11760,7 +11914,7 @@ var package_default = {
11760
11914
  execa: "^10.0.0",
11761
11915
  zod: "3.25.76"
11762
11916
  },
11763
- gitHead: "7b17b06b041af953afde46fbd41fc68ebc6dc4e9"
11917
+ gitHead: "4eabd911d4dfd557bfbfcbc27afd36415714b7eb"
11764
11918
  };
11765
11919
 
11766
11920
  // src/version.ts
@@ -12560,6 +12714,20 @@ async function collectRunDiff(worktree, opts = {}) {
12560
12714
  ...truncatedFiles ? { truncated: true } : {}
12561
12715
  };
12562
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
+ }
12563
12731
  async function changedPaths(worktree, opts = {}) {
12564
12732
  if (!worktree) return [];
12565
12733
  try {
@@ -12624,6 +12792,7 @@ function collectZoneNoteConfirmations(report) {
12624
12792
  // src/droneServer.ts
12625
12793
  init_src();
12626
12794
  init_src();
12795
+ init_src();
12627
12796
 
12628
12797
  // src/reviewCycle.ts
12629
12798
  init_src();
@@ -12835,6 +13004,8 @@ ${failText}` : failText }],
12835
13004
  isError: true
12836
13005
  };
12837
13006
  }
13007
+ await store.addEvent(null, "step", `${ACCEPTANCE_CHECK_MARK}: ${id} \u2192 ${state}`, null).catch(() => {
13008
+ });
12838
13009
  return { content: [{ type: "text", text }] };
12839
13010
  }
12840
13011
  );
@@ -12871,6 +13042,29 @@ ${failText}` : failText }],
12871
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." }] };
12872
13043
  })
12873
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
+ );
12874
13068
  server.tool(
12875
13069
  "return_task",
12876
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.",
@@ -14661,8 +14855,8 @@ function detectGlobalPm(entry, io = {}) {
14661
14855
  if (/\/node_modules\//.test(p)) return "npm";
14662
14856
  const env = io.env ?? process.env;
14663
14857
  const platform = io.platform ?? process.platform;
14664
- const has = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
14665
- return has("npm") ? "npm" : "unknown";
14858
+ const has2 = io.hasCommand ?? ((n) => hasCommandInPath(n, env, platform));
14859
+ return has2("npm") ? "npm" : "unknown";
14666
14860
  }
14667
14861
  function globalInstallCommand(pm, spec) {
14668
14862
  switch (pm) {
@@ -17950,7 +18144,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
17950
18144
  // вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
17951
18145
  // ветку заводит spawnDrone из слага) — прежняя формулировка.
17952
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"}.`,
17953
- "\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.",
17954
18148
  // TASWM-412: третий выход из непонятной задачи. Раньше их было два, и оба плохие —
17955
18149
  // угадать постановку (сделает не то) или ask (слот занят, а ответить может только автор
17956
18150
  // задачи, не оператор роя). Граница здесь ОБЯЗАТЕЛЬНА: дрон, легко возвращающий задачи,
@@ -18947,6 +19141,13 @@ async function trySpawnQueuedRun(deps, run) {
18947
19141
  // причина одна на машину — в лог уходит одной строкой, а не по строке на прогон
18948
19142
  });
18949
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
+ }
18950
19151
  const spentAttempts = attemptsFor(deps, run.task_slug);
18951
19152
  if (spentAttempts >= PRE_START_ATTEMPT_LIMIT) {
18952
19153
  const last = lastPreStartAttempt(deps.db.listRuns(), run.task_slug);
@@ -19234,6 +19435,79 @@ async function applyWatchdogAction(deps, action) {
19234
19435
  }
19235
19436
  }
19236
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
+ }
19237
19511
  async function manageRelief(deps, now, droneCap, stormActive) {
19238
19512
  const state = deps.relief ?? (deps.relief = {});
19239
19513
  const cfg = state.config ? resolveReliefConfig(state.config) : DEFAULT_RELIEF_CONFIG;
@@ -19242,7 +19516,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
19242
19516
  state.reliefSince = needsRelief ? state.reliefSince ?? now : null;
19243
19517
  if (!resourcesOk2 || needsRelief) state.overloadedAt = now;
19244
19518
  if (needsRelief && deps.kill && pauseDue(state.reliefSince, state.lastPauseAt ?? null, now, cfg)) {
19245
- const candidate = pickPauseCandidate(deps.db.listRuns("running"));
19519
+ const candidate = pickPauseCandidate(deps.db.listRuns("running"), new Set(heavyRunsInFlight(deps).map((r) => r.id)));
19246
19520
  if (candidate) {
19247
19521
  let stepsSinceResume = null;
19248
19522
  try {
@@ -19749,6 +20023,11 @@ async function tickOnce(deps) {
19749
20023
  }
19750
20024
  const droneCap = deps.maxDrones ? await deps.maxDrones() : Infinity;
19751
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
+ }
19752
20031
  if (deps.resourcesOk) {
19753
20032
  await manageRelief(deps, now, droneCap, stormActive);
19754
20033
  } else if (!stormActive) {
@@ -20343,7 +20622,12 @@ async function tickOnce(deps) {
20343
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);
20344
20623
  }
20345
20624
  }
20346
- 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())) {
20347
20631
  const it = deps.intake;
20348
20632
  const claimCap = deps.maxDrones ? droneCap : it.maxInFlight;
20349
20633
  const sinceLastClaim = it.lastClaimAt == null ? Infinity : now - it.lastClaimAt;
@@ -20928,7 +21212,10 @@ async function runDaemon(opts) {
20928
21212
  recordEffort: (runId, effort) => {
20929
21213
  db.updateRun(runId, { effort });
20930
21214
  },
20931
- maxCostUsd: () => lastMaxCostUsd
21215
+ maxCostUsd: () => lastMaxCostUsd,
21216
+ // TASWM-535: поднятый потолок тяжёлого прогона живёт в его ленте — спавн читает её
21217
+ // оттуда же, откуда потом прочитает прод-сторож.
21218
+ weightEvents: (run) => db.events(run.id)
20932
21219
  });
20933
21220
  let prepareReader;
20934
21221
  const zoneModels = new ZoneModels({ manualAreas: () => new Map(db.listRuns().map((r) => [r.task_slug, r.area])) });
@@ -21498,6 +21785,11 @@ async function runDaemon(opts) {
21498
21785
  // mtime транскрипта claude-сессии как liveness (TASWM-136): точнее коммитов — обновляется
21499
21786
  // на каждое действие дрона (Edit/Read/tool-use), не только на коммит.
21500
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,
21501
21793
  // Ресурс-гейт спавна (TASWM-130): реальные показания хоста (load1 / free RAM).
21502
21794
  resourcesOk: hostResourcesOk,
21503
21795
  // Замер занятости CPU раз в тик (TASWM-397): окно фиксированной длины набирает пульс
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.33",
3
+ "version": "0.6.34",
4
4
  "type": "module",
5
5
  "description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "execa": "^10.0.0",
40
40
  "zod": "3.25.76"
41
41
  },
42
- "gitHead": "7b17b06b041af953afde46fbd41fc68ebc6dc4e9",
42
+ "gitHead": "4eabd911d4dfd557bfbfcbc27afd36415714b7eb",
43
43
  "scripts": {
44
44
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
45
45
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",