@taskless-app/swarm 0.6.48 → 0.6.49

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
@@ -672,7 +672,7 @@ var init_modelPolicy = __esm({
672
672
  label: "Anthropic",
673
673
  executorId: "claude-code",
674
674
  selectable: true,
675
- roles: { executor: "claude-opus-5", review: "claude-sonnet-5" }
675
+ roles: { executor: "claude-opus-5-5", review: "claude-sonnet-5" }
676
676
  },
677
677
  // TASWM-565 · шаг 1 из четырёх: провайдер в реестре ЕСТЬ, выбрать его ещё нельзя.
678
678
  // Имён моделей здесь нет СОЗНАТЕЛЬНО: у владельца подписка, состав моделей в ней нам
@@ -1169,6 +1169,9 @@ var init_cost = __esm({
1169
1169
  });
1170
1170
 
1171
1171
  // ../swarm-core/src/modelPricing.ts
1172
+ function cacheReadFactorOf(pricing) {
1173
+ return pricing.cacheReadFactor ?? CACHE_READ_FACTOR;
1174
+ }
1172
1175
  function findModelPricing(model, table = MODEL_PRICING) {
1173
1176
  if (!model) return void 0;
1174
1177
  const exact = table[model];
@@ -1190,6 +1193,10 @@ var init_modelPricing = __esm({
1190
1193
  // и «почему прогон вчера стоил иначе» приходится выяснять по коду, а не по таблице.
1191
1194
  // Меняется цена — меняется строка, одним видимым коммитом.
1192
1195
  "claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
1196
+ // Opus 5.5 — 4/20, и чтение из кеша у него 5 % входа ($0.20), а не обычные 10 %.
1197
+ // У дронов чтение из кеша — основная часть токенов: с общим множителем прогон на
1198
+ // Opus 5.5 стоил бы в расчёте почти вдвое больше настоящего.
1199
+ "claude-opus-5-5": { inputPerMTok: 4, outputPerMTok: 20, cacheReadFactor: 0.05 },
1193
1200
  "claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
1194
1201
  "claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
1195
1202
  // Прогоны и вызовы на 4.x остаются в истории — строки держим ради их стоимости.
@@ -1964,7 +1971,7 @@ function costFromUsage(usage2, model, table = MODEL_PRICING) {
1964
1971
  const output = usage2.output_tokens ?? 0;
1965
1972
  const cacheWrite = usage2.cache_creation_input_tokens ?? 0;
1966
1973
  const cacheRead = usage2.cache_read_input_tokens ?? 0;
1967
- const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * CACHE_READ_FACTOR) / 1e6;
1974
+ const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * cacheReadFactorOf(pricing)) / 1e6;
1968
1975
  return { costUsd };
1969
1976
  }
1970
1977
  var init_pricing = __esm({
@@ -2943,6 +2950,18 @@ var init_zoneNotes = __esm({
2943
2950
  }
2944
2951
  });
2945
2952
 
2953
+ // src/store.ts
2954
+ function isCleanResolve(run) {
2955
+ return (run.stop_reason ?? "").includes(RESOLVE_CLEAN_MARK);
2956
+ }
2957
+ var RESOLVE_CLEAN_MARK;
2958
+ var init_store = __esm({
2959
+ "src/store.ts"() {
2960
+ "use strict";
2961
+ RESOLVE_CLEAN_MARK = "\u0447\u0438\u0441\u0442\u043E\u0435 \u0432\u043B\u0438\u0432\u0430\u043D\u0438\u0435 \u0431\u0430\u0437\u044B";
2962
+ }
2963
+ });
2964
+
2946
2965
  // src/agentToken.ts
2947
2966
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync } from "fs";
2948
2967
  import { join as join7 } from "path";
@@ -3412,7 +3431,7 @@ var init_package = __esm({
3412
3431
  "package.json"() {
3413
3432
  package_default = {
3414
3433
  name: "@taskless-app/swarm",
3415
- version: "0.6.48",
3434
+ version: "0.6.49",
3416
3435
  type: "module",
3417
3436
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3418
3437
  keywords: [
@@ -3463,7 +3482,7 @@ var init_package = __esm({
3463
3482
  execa: "^10.0.0",
3464
3483
  zod: "3.25.76"
3465
3484
  },
3466
- gitHead: "62a77b0a48d5a07ae71b45c5f0f6212dc6069829"
3485
+ gitHead: "27600667531d4f65761c74793e8c4ee4859dc8ae"
3467
3486
  };
3468
3487
  }
3469
3488
  });
@@ -4835,6 +4854,7 @@ var init_db = __esm({
4835
4854
  init_names2();
4836
4855
  init_zoneNotes();
4837
4856
  init_storm();
4857
+ init_store();
4838
4858
  init_src();
4839
4859
  TERMINAL_SQL = TERMINAL_STATUSES.map((s) => `'${s}'`).join(",");
4840
4860
  RESOLVER_TERMINAL_SQL = [...TERMINAL_STATUSES, "stalled"].map((s) => `'${s}'`).join(",");
@@ -5188,9 +5208,19 @@ CREATE TABLE IF NOT EXISTS imports (
5188
5208
  ).get(reviewRunId);
5189
5209
  return row ? rowToRun(row) : null;
5190
5210
  }
5191
- /** Сколько всего резолверов уже поднимали на этот review-PR — потолок авто-попыток. */
5211
+ /**
5212
+ * Сколько резолверов С ДРОНОМ уже поднимали на этот review-PR — потолок авто-попыток.
5213
+ *
5214
+ * TASWM-575: чистые вливания базы (RESOLVE_CLEAN_MARK в stop_reason — git свёл сам, дрон
5215
+ * не поднимался, стоимость ноль) в счёт НЕ идут. Потолок защищает от карусели ДРОНОВ на
5216
+ * нетривиальном конфликте (TASWM-127); бесплатное вливание не карусель, а на оживлённом
5217
+ * main оно случается на каждый чужой мерж — три сдвига main выбирали потолок, и PR с
5218
+ * отставшей базой застревал молча (гейт мержа не пускает base_stale, а вливать уже
5219
+ * некому). Всё остальное (failed/stalled резолверы, в т.ч. транзиентные сбои ОС) считается
5220
+ * как раньше — иначе потолок потерял бы смысл на органических смертях.
5221
+ */
5192
5222
  resolverCountFor(reviewRunId) {
5193
- const row = this.db.prepare("SELECT COUNT(*) n FROM runs WHERE resolves_run_id = ?").get(reviewRunId);
5223
+ const row = this.db.prepare("SELECT COUNT(*) n FROM runs WHERE resolves_run_id = ? AND (stop_reason IS NULL OR stop_reason NOT LIKE ?)").get(reviewRunId, `%${RESOLVE_CLEAN_MARK}%`);
5194
5224
  return row.n;
5195
5225
  }
5196
5226
  /**
@@ -7232,6 +7262,7 @@ function parsePendingRepoRules(raw) {
7232
7262
 
7233
7263
  // src/swarmClient.ts
7234
7264
  init_zoneNotes();
7265
+ init_store();
7235
7266
  init_toolFailure();
7236
7267
 
7237
7268
  // src/logTail.ts
@@ -7823,8 +7854,10 @@ var SwarmClient = class _SwarmClient {
7823
7854
  const cands = [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId && !RESOLVER_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
7824
7855
  return cands.length ? this.mat(cands[0]) : null;
7825
7856
  }
7857
+ // Потолок авто-попыток: чистые вливания базы (дрон не поднимался, стоимость ноль) не
7858
+ // считаются — см. db.ts:resolverCountFor и store.ts:RESOLVE_CLEAN_MARK (TASWM-575).
7826
7859
  resolverCountFor(reviewRunId) {
7827
- return [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId).length;
7860
+ return [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId && !isCleanResolve(r)).length;
7828
7861
  }
7829
7862
  // failed/stalled ревьюеры (сорвался спавн ДО вердикта) в счёт НЕ идут; stopped/superseded
7830
7863
  // тоже (TASWM-253: оператор закрыл зомби-ревьюера/сбросил попытки ревью — POST /swarm/
package/dist/overmind.js CHANGED
@@ -666,7 +666,7 @@ var init_modelPolicy = __esm({
666
666
  label: "Anthropic",
667
667
  executorId: "claude-code",
668
668
  selectable: true,
669
- roles: { executor: "claude-opus-5", review: "claude-sonnet-5" }
669
+ roles: { executor: "claude-opus-5-5", review: "claude-sonnet-5" }
670
670
  },
671
671
  // TASWM-565 · шаг 1 из четырёх: провайдер в реестре ЕСТЬ, выбрать его ещё нельзя.
672
672
  // Имён моделей здесь нет СОЗНАТЕЛЬНО: у владельца подписка, состав моделей в ней нам
@@ -1277,6 +1277,9 @@ var init_cost = __esm({
1277
1277
  });
1278
1278
 
1279
1279
  // ../swarm-core/src/modelPricing.ts
1280
+ function cacheReadFactorOf(pricing) {
1281
+ return pricing.cacheReadFactor ?? CACHE_READ_FACTOR;
1282
+ }
1280
1283
  function findModelPricing(model, table = MODEL_PRICING) {
1281
1284
  if (!model) return void 0;
1282
1285
  const exact = table[model];
@@ -1298,6 +1301,10 @@ var init_modelPricing = __esm({
1298
1301
  // и «почему прогон вчера стоил иначе» приходится выяснять по коду, а не по таблице.
1299
1302
  // Меняется цена — меняется строка, одним видимым коммитом.
1300
1303
  "claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
1304
+ // Opus 5.5 — 4/20, и чтение из кеша у него 5 % входа ($0.20), а не обычные 10 %.
1305
+ // У дронов чтение из кеша — основная часть токенов: с общим множителем прогон на
1306
+ // Opus 5.5 стоил бы в расчёте почти вдвое больше настоящего.
1307
+ "claude-opus-5-5": { inputPerMTok: 4, outputPerMTok: 20, cacheReadFactor: 0.05 },
1301
1308
  "claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
1302
1309
  "claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
1303
1310
  // Прогоны и вызовы на 4.x остаются в истории — строки держим ради их стоимости.
@@ -2168,7 +2175,7 @@ function costFromUsage(usage, model, table = MODEL_PRICING) {
2168
2175
  const output = usage.output_tokens ?? 0;
2169
2176
  const cacheWrite = usage.cache_creation_input_tokens ?? 0;
2170
2177
  const cacheRead = usage.cache_read_input_tokens ?? 0;
2171
- const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * CACHE_READ_FACTOR) / 1e6;
2178
+ const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * cacheReadFactorOf(pricing)) / 1e6;
2172
2179
  return { costUsd };
2173
2180
  }
2174
2181
  var init_pricing = __esm({
@@ -3480,6 +3487,18 @@ var init_zoneNotes = __esm({
3480
3487
  }
3481
3488
  });
3482
3489
 
3490
+ // src/store.ts
3491
+ function isCleanResolve(run) {
3492
+ return (run.stop_reason ?? "").includes(RESOLVE_CLEAN_MARK);
3493
+ }
3494
+ var RESOLVE_CLEAN_MARK;
3495
+ var init_store = __esm({
3496
+ "src/store.ts"() {
3497
+ "use strict";
3498
+ RESOLVE_CLEAN_MARK = "\u0447\u0438\u0441\u0442\u043E\u0435 \u0432\u043B\u0438\u0432\u0430\u043D\u0438\u0435 \u0431\u0430\u0437\u044B";
3499
+ }
3500
+ });
3501
+
3483
3502
  // src/agentToken.ts
3484
3503
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
3485
3504
  import { join as join8 } from "path";
@@ -6271,6 +6290,7 @@ var init_db = __esm({
6271
6290
  init_names2();
6272
6291
  init_zoneNotes();
6273
6292
  init_storm();
6293
+ init_store();
6274
6294
  init_src();
6275
6295
  TERMINAL_SQL = TERMINAL_STATUSES.map((s) => `'${s}'`).join(",");
6276
6296
  RESOLVER_TERMINAL_SQL = [...TERMINAL_STATUSES, "stalled"].map((s) => `'${s}'`).join(",");
@@ -6624,9 +6644,19 @@ CREATE TABLE IF NOT EXISTS imports (
6624
6644
  ).get(reviewRunId);
6625
6645
  return row ? rowToRun(row) : null;
6626
6646
  }
6627
- /** Сколько всего резолверов уже поднимали на этот review-PR — потолок авто-попыток. */
6647
+ /**
6648
+ * Сколько резолверов С ДРОНОМ уже поднимали на этот review-PR — потолок авто-попыток.
6649
+ *
6650
+ * TASWM-575: чистые вливания базы (RESOLVE_CLEAN_MARK в stop_reason — git свёл сам, дрон
6651
+ * не поднимался, стоимость ноль) в счёт НЕ идут. Потолок защищает от карусели ДРОНОВ на
6652
+ * нетривиальном конфликте (TASWM-127); бесплатное вливание не карусель, а на оживлённом
6653
+ * main оно случается на каждый чужой мерж — три сдвига main выбирали потолок, и PR с
6654
+ * отставшей базой застревал молча (гейт мержа не пускает base_stale, а вливать уже
6655
+ * некому). Всё остальное (failed/stalled резолверы, в т.ч. транзиентные сбои ОС) считается
6656
+ * как раньше — иначе потолок потерял бы смысл на органических смертях.
6657
+ */
6628
6658
  resolverCountFor(reviewRunId) {
6629
- const row = this.db.prepare("SELECT COUNT(*) n FROM runs WHERE resolves_run_id = ?").get(reviewRunId);
6659
+ const row = this.db.prepare("SELECT COUNT(*) n FROM runs WHERE resolves_run_id = ? AND (stop_reason IS NULL OR stop_reason NOT LIKE ?)").get(reviewRunId, `%${RESOLVE_CLEAN_MARK}%`);
6630
6660
  return row.n;
6631
6661
  }
6632
6662
  /**
@@ -7312,6 +7342,7 @@ function renderPendingRulesBlock(rules) {
7312
7342
 
7313
7343
  // src/swarmClient.ts
7314
7344
  init_zoneNotes();
7345
+ init_store();
7315
7346
  init_toolFailure();
7316
7347
 
7317
7348
  // src/logTail.ts
@@ -8061,8 +8092,10 @@ var SwarmClient = class _SwarmClient {
8061
8092
  const cands = [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId && !RESOLVER_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
8062
8093
  return cands.length ? this.mat(cands[0]) : null;
8063
8094
  }
8095
+ // Потолок авто-попыток: чистые вливания базы (дрон не поднимался, стоимость ноль) не
8096
+ // считаются — см. db.ts:resolverCountFor и store.ts:RESOLVE_CLEAN_MARK (TASWM-575).
8064
8097
  resolverCountFor(reviewRunId) {
8065
- return [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId).length;
8098
+ return [...this.runs.values()].filter((r) => r.resolves_run_id === reviewRunId && !isCleanResolve(r)).length;
8066
8099
  }
8067
8100
  // failed/stalled ревьюеры (сорвался спавн ДО вердикта) в счёт НЕ идут; stopped/superseded
8068
8101
  // тоже (TASWM-253: оператор закрыл зомби-ревьюера/сбросил попытки ревью — POST /swarm/
@@ -9927,6 +9960,7 @@ function parseDecision(v) {
9927
9960
  }
9928
9961
 
9929
9962
  // src/overmind.ts
9963
+ init_store();
9930
9964
  init_config();
9931
9965
  init_heartbeat();
9932
9966
 
@@ -12359,7 +12393,7 @@ function parkReasonUnretriable(reason) {
12359
12393
  // package.json
12360
12394
  var package_default = {
12361
12395
  name: "@taskless-app/swarm",
12362
- version: "0.6.48",
12396
+ version: "0.6.49",
12363
12397
  type: "module",
12364
12398
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
12365
12399
  keywords: [
@@ -12410,7 +12444,7 @@ var package_default = {
12410
12444
  execa: "^10.0.0",
12411
12445
  zod: "3.25.76"
12412
12446
  },
12413
- gitHead: "62a77b0a48d5a07ae71b45c5f0f6212dc6069829"
12447
+ gitHead: "27600667531d4f65761c74793e8c4ee4859dc8ae"
12414
12448
  };
12415
12449
 
12416
12450
  // src/version.ts
@@ -18150,6 +18184,8 @@ var EXITED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u
18150
18184
  var STORM_DEAD_LIMIT = Number(process.env.SWARM_STORM_DEAD_LIMIT) || 3;
18151
18185
  var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS) || 3 * 60 * 1e3;
18152
18186
  var RESOLVE_LIMIT = Number(process.env.SWARM_RESOLVE_LIMIT) || 3;
18187
+ var BASE_MERGED_MARK = "\u0431\u0430\u0437\u0430 \u0432\u043B\u0438\u0442\u0430 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438";
18188
+ var BASE_NOOP_MARK = "\u0432\u043B\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E";
18153
18189
  var REVIEW_SPAWN_LIMIT = Number(process.env.SWARM_REVIEW_SPAWN_LIMIT) || 3;
18154
18190
  var REVIEW_CYCLE_LIMIT = Number(process.env.SWARM_REVIEW_CYCLE_LIMIT) || 3;
18155
18191
  var CI_WAKE_LIMIT = Number(process.env.SWARM_CI_WAKE_LIMIT) || 3;
@@ -18231,10 +18267,17 @@ function lastConflictAttemptMs(db, runId) {
18231
18267
  if (!evs.length) return null;
18232
18268
  return Date.parse(evs[evs.length - 1].at.replace(" ", "T") + "Z");
18233
18269
  }
18270
+ function lastBaseMergeWasNoop(db, runId) {
18271
+ const outcomes = db.events(runId).filter((e) => e.kind === "conflict" && ((e.text ?? "").includes(BASE_NOOP_MARK) || (e.text ?? "").includes(BASE_MERGED_MARK)));
18272
+ return (outcomes[outcomes.length - 1]?.text ?? "").includes(BASE_NOOP_MARK);
18273
+ }
18274
+ function resolveEscalated(db, runId) {
18275
+ return db.events(runId).some((e) => e.kind === "conflict" && (e.text ?? "").includes(RESOLVE_ESCALATION_MARK));
18276
+ }
18234
18277
  async function needsBaseRefreshNow(deps, run, stormActive, now) {
18235
18278
  if (!deps.mergeQueue || !deps.resolveConflict || stormActive) return false;
18236
18279
  if (deps.db.activeResolverFor(run.id)) return false;
18237
- if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) return false;
18280
+ if (resolveEscalated(deps.db, run.id)) return false;
18238
18281
  const last = lastConflictAttemptMs(deps.db, run.id);
18239
18282
  if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) return false;
18240
18283
  return needsBaseRefresh(await deps.mergeQueue(run).catch(() => null));
@@ -20899,16 +20942,30 @@ async function tickOnce(deps) {
20899
20942
  await retireOutdatedReviewers(deps, run);
20900
20943
  const baseStale = !st.conflicting && await needsBaseRefreshNow(deps, run, stormActive, now);
20901
20944
  if ((st.conflicting || baseStale) && deps.resolveConflict && !stormActive) {
20902
- if (deps.resourcesOk && !deps.resourcesOk()) continue;
20903
- if (countActiveDrones(deps.db) >= droneCap) continue;
20904
- if (!await projectQuotaGateAllows(deps, run)) continue;
20905
20945
  if (deps.db.activeResolverFor(run.id)) continue;
20906
20946
  if (deps.db.resolverCountFor(run.id) >= RESOLVE_LIMIT) {
20907
- if (!deps.db.events(run.id).some((e) => e.kind === "conflict" && (e.text ?? "").includes(RESOLVE_ESCALATION_MARK))) {
20908
- deps.db.addEvent(run.id, "conflict", `${RESOLVE_ESCALATION_MARK}: \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0441\u044F \u0437\u0430 ${RESOLVE_LIMIT} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
20947
+ if (!resolveEscalated(deps.db, run.id)) {
20948
+ deps.db.addEvent(
20949
+ run.id,
20950
+ "conflict",
20951
+ st.conflicting ? `${RESOLVE_ESCALATION_MARK}: \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0441\u044F \u0437\u0430 ${RESOLVE_LIMIT} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A` : `${RESOLVE_ESCALATION_MARK}: \u0431\u0430\u0437\u0430 \u043F\u043E\u0434 PR \u0443\u0435\u0445\u0430\u043B\u0430 \u0432\u043F\u0435\u0440\u0451\u0434, \u0430 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440\u043E\u0432 (${RESOLVE_LIMIT}) \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0432\u043B\u0438\u0442\u044C \u0431\u0430\u0437\u0443 \u0434\u043E\u043B\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
20952
+ );
20909
20953
  }
20910
20954
  continue;
20911
20955
  }
20956
+ if (baseStale && lastBaseMergeWasNoop(deps.db, run.id)) {
20957
+ if (!resolveEscalated(deps.db, run.id)) {
20958
+ deps.db.addEvent(
20959
+ run.id,
20960
+ "conflict",
20961
+ `${RESOLVE_ESCALATION_MARK}: \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043C\u0435\u0440\u0436\u0430 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u0431\u0430\u0437\u0443 \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0435\u0439, \u0430 \u0432\u043B\u0438\u0432\u0430\u0442\u044C \u0432 \u0432\u0435\u0442\u043A\u0443 \u043D\u0435\u0447\u0435\u0433\u043E \u2014 \u0441\u0430\u043C\u0430 \u0441\u043E\u0431\u043E\u0439 \u0442\u0430\u043A\u0430\u044F \u0431\u0430\u0437\u0430 \u043D\u0435 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
20962
+ );
20963
+ }
20964
+ continue;
20965
+ }
20966
+ if (deps.resourcesOk && !deps.resourcesOk()) continue;
20967
+ if (countActiveDrones(deps.db) >= droneCap) continue;
20968
+ if (!await projectQuotaGateAllows(deps, run)) continue;
20912
20969
  const last = lastConflictAttemptMs(deps.db, run.id);
20913
20970
  if (last != null && now - last < CONFLICT_RESOLVE_COOLDOWN_MS) continue;
20914
20971
  deps.db.addEvent(
@@ -21514,13 +21571,14 @@ async function resolveConflict(deps, reviewRun) {
21514
21571
  if (m.changed) await deps.git.pushHeadTo(wt, branch);
21515
21572
  await deps.git.removeWorktree(repo, wt).catch(() => {
21516
21573
  });
21517
- deps.db.updateRun(resolver.id, { status: "done" });
21574
+ const cleanWhy = m.changed ? `\u0447\u0438\u0441\u0442\u044B\u0439 merge ${mainRef} \u2014 \u0434\u0440\u043E\u043D \u043D\u0435 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F` : `${mainRef} \u0443\u0436\u0435 \u0432\u043B\u0438\u0442 \u2014 \u043D\u0435\u0447\u0435\u0433\u043E \u0434\u0435\u043B\u0430\u0442\u044C`;
21575
+ deps.db.updateRun(resolver.id, { status: "done", stop_reason: `${RESOLVE_CLEAN_MARK}: ${cleanWhy}` });
21576
+ deps.db.addEvent(resolver.id, "done", cleanWhy);
21518
21577
  deps.db.addEvent(
21519
- resolver.id,
21520
- "done",
21521
- m.changed ? `\u0447\u0438\u0441\u0442\u044B\u0439 merge ${mainRef} \u2014 \u0434\u0440\u043E\u043D \u043D\u0435 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F` : `${mainRef} \u0443\u0436\u0435 \u0432\u043B\u0438\u0442 \u2014 \u043D\u0435\u0447\u0435\u0433\u043E \u0434\u0435\u043B\u0430\u0442\u044C`
21578
+ reviewRun.id,
21579
+ "conflict",
21580
+ m.changed ? `${BASE_MERGED_MARK} (${mainRef}, \u0431\u0435\u0437 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432) \u2014 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D` : `${mainRef} \u0443\u0436\u0435 \u0432 \u0432\u0435\u0442\u043A\u0435 \u2014 ${BASE_NOOP_MARK}, \u0431\u0430\u0437\u0430 \u043E\u0442 \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0441\u044F`
21522
21581
  );
21523
- deps.db.addEvent(reviewRun.id, "conflict", `\u0431\u0430\u0437\u0430 \u0432\u043B\u0438\u0442\u0430 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 (${mainRef}, \u0431\u0435\u0437 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432) \u2014 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D`);
21524
21582
  } else {
21525
21583
  const prompt = buildResolverPrompt(reviewRun, resolver, m.conflicts, branch, mainRef);
21526
21584
  const pid = deps.launch({ worktree: wt, run: resolver, prompt });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.48",
3
+ "version": "0.6.49",
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": "62a77b0a48d5a07ae71b45c5f0f6212dc6069829",
42
+ "gitHead": "27600667531d4f65761c74793e8c4ee4859dc8ae",
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",