@taskless-app/swarm 0.6.2 → 0.6.4

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/README.md CHANGED
@@ -211,6 +211,24 @@ deny-правил на файловые тулы у него нет — дрон
211
211
  (`src/executors/index.ts`). Если для него пришлось тронуть общий код — значит,
212
212
  чинить надо интерфейс, а не общий код; на это и стоит `contract.test.ts`.
213
213
 
214
+ ## Служебные файлы роя в worktree
215
+
216
+ Кое-что рой кладёт прямо в рабочее дерево дрона: `.swarm-mcp.json` (конфиг
217
+ MCP-сервера роя, в нём **токен прогона**) и `.swarm-task/` (вложения задачи).
218
+ Ваш `.gitignore` про них знать не обязан — это файлы роя, а не проекта, и рой
219
+ прячет их сам, на каждом подготовленном worktree (TASWM-372):
220
+
221
+ - паттерны уезжают в `.git/info/exclude` — локальный exclude вашего клона, файлы
222
+ в репозиторий не коммитятся, а сам репозиторий при этом не тронут;
223
+ - `pre-commit`/`pre-push` рвут операцию, если служебный файл всё-таки оказался в
224
+ индексе (`git add -f`, файл приехал закоммиченным с ветки). Хуки ставятся
225
+ строго пер-worktree (`git config --worktree core.hooksPath`, каталог —
226
+ `.git/worktrees/<id>/swarm-hooks`) и в конце **вызывают ваш прежний хук**:
227
+ husky и прочая дисциплина репозитория продолжают работать.
228
+
229
+ Список файлов — один, `src/swarmFiles.ts`; оттуда же берут имена те, кто их
230
+ пишет. Кладёте в worktree что-то новое — впишите туда, и оно спрячется само.
231
+
214
232
  ## Windows
215
233
 
216
234
  Рой работает на Windows, но **рекомендуемый способ — WSL2**: там он ведёт себя ровно
package/dist/cli.js CHANGED
@@ -104,7 +104,13 @@ var init_types = __esm({
104
104
  // делать нечего». Находки вроде «в дизайн-системе нет иконки микрофона» раньше либо
105
105
  // превращались в такой дорогой вопрос, либо тонули в отчёте о сдаче (дрон LAMPYRIS сдал
106
106
  // заглушку «Мик» текстом на кнопке — уехало в прод). note уезжает оператору, дрон работает дальше.
107
- "note"
107
+ "note",
108
+ // Зависимость прогона снята НЕуспешным финалом блокера (TASWM-361): тот остановлен/упал/
109
+ // переигран, успешного финала не будет — ждать больше нечего. Пишется в ленту ЖДАВШЕГО
110
+ // прогона перед самым спавном: «поехал молча» означало бы, что причина запуска исчезла из
111
+ // истории, и следующий разбор «почему он стартовал поверх незакрытого блокера» упёрся бы в
112
+ // пустоту. Успешное снятие (блокер смержен) события не порождает — это штатный ход.
113
+ "dep_cleared"
108
114
  ];
109
115
  MILESTONE_KINDS = [
110
116
  "spawned",
@@ -125,8 +131,10 @@ var init_types = __esm({
125
131
  "storm_dead",
126
132
  "anomaly",
127
133
  // надзор overmind (TASWM-194) — та же нотификация, что улетела в инбокс, вехой в Истории
128
- "note"
134
+ "note",
129
135
  // заметка дрона «замечено по пути» (TASWM-279) — веха, иначе находка тонет в ленте step'ов
136
+ "dep_cleared"
137
+ // зависимость снята неуспешным финалом блокера (TASWM-361) — решение роя, веха как conflict
130
138
  ];
131
139
  MILESTONE_KIND_SET = new Set(MILESTONE_KINDS);
132
140
  }
@@ -355,6 +363,32 @@ var init_status = __esm({
355
363
  }
356
364
  });
357
365
 
366
+ // ../swarm-core/src/deps.ts
367
+ function judgeDep(runs) {
368
+ const real = runs.filter((r) => !r.service);
369
+ if (real.length === 0) return { ...PENDING };
370
+ if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
371
+ if (real.some((r) => r.status === "done")) {
372
+ return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
373
+ }
374
+ const last = real[real.length - 1];
375
+ return {
376
+ outcome: "exhausted",
377
+ met: true,
378
+ status: last.status,
379
+ name: last.name ?? null,
380
+ stopReason: last.stopReason ?? null
381
+ };
382
+ }
383
+ var PENDING;
384
+ var init_deps = __esm({
385
+ "../swarm-core/src/deps.ts"() {
386
+ "use strict";
387
+ init_status();
388
+ PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
389
+ }
390
+ });
391
+
358
392
  // ../swarm-core/src/time.ts
359
393
  function stamp(d = /* @__PURE__ */ new Date()) {
360
394
  return d.toISOString().slice(0, 19).replace("T", " ");
@@ -1061,6 +1095,7 @@ var init_src = __esm({
1061
1095
  init_timeline();
1062
1096
  init_reviewPhase();
1063
1097
  init_status();
1098
+ init_deps();
1064
1099
  init_time();
1065
1100
  init_windows();
1066
1101
  init_slug();
@@ -1156,6 +1191,17 @@ var init_claudeBin = __esm({
1156
1191
  }
1157
1192
  });
1158
1193
 
1194
+ // src/swarmFiles.ts
1195
+ var SWARM_MCP_CONFIG_FILE, SWARM_TASK_DIR, SWARM_EXCLUDE_PATTERNS;
1196
+ var init_swarmFiles = __esm({
1197
+ "src/swarmFiles.ts"() {
1198
+ "use strict";
1199
+ SWARM_MCP_CONFIG_FILE = ".swarm-mcp.json";
1200
+ SWARM_TASK_DIR = ".swarm-task";
1201
+ SWARM_EXCLUDE_PATTERNS = [`/${SWARM_MCP_CONFIG_FILE}`, `/${SWARM_TASK_DIR}/`];
1202
+ }
1203
+ });
1204
+
1159
1205
  // src/pricing.ts
1160
1206
  function costFromUsage(usage2, model, table = MODEL_PRICING) {
1161
1207
  const pricing = model ? table[model] : void 0;
@@ -1294,9 +1340,10 @@ var init_claudeCode = __esm({
1294
1340
  "src/executors/claudeCode.ts"() {
1295
1341
  "use strict";
1296
1342
  init_claudeBin();
1343
+ init_swarmFiles();
1297
1344
  init_claudeResult();
1298
1345
  ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
1299
- MCP_CONFIG_FILE = ".swarm-mcp.json";
1346
+ MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
1300
1347
  claudeCodeExecutor = {
1301
1348
  id: "claude-code",
1302
1349
  title: "Claude Code (claude)",
@@ -1837,7 +1884,7 @@ var init_package = __esm({
1837
1884
  "package.json"() {
1838
1885
  package_default = {
1839
1886
  name: "@taskless-app/swarm",
1840
- version: "0.6.2",
1887
+ version: "0.6.4",
1841
1888
  type: "module",
1842
1889
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
1843
1890
  keywords: [
@@ -1887,7 +1934,7 @@ var init_package = __esm({
1887
1934
  execa: "^10.0.0",
1888
1935
  zod: "3.25.76"
1889
1936
  },
1890
- gitHead: "e812f35a996b00111f3f535b6b0a45b379e7afb2"
1937
+ gitHead: "12ecebcf1e60baab07e68cae3e78a29d78cb7fde"
1891
1938
  };
1892
1939
  }
1893
1940
  });
@@ -2676,18 +2723,40 @@ CREATE TABLE IF NOT EXISTS imports (
2676
2723
  return rows.map((r) => r.area);
2677
2724
  }
2678
2725
  /**
2679
- * Из depends_on прогона — слаги зависимостей, ещё НЕ закрытых (нет прогона со
2680
- * статусом done). Пусто → все выполнены, дрона можно спавнить. Явная зависимость
2681
- * (spawn --after) отдельна от area-lock: area освобождается на review (до мержа),
2682
- * а тут ждём именно done (смерж), чтобы следующий дрон стартовал от кода предыдущего.
2683
- * Несуществующий/зациклённый слаг просто никогда не станет done → прогон вечно ждёт,
2684
- * но очередь не виснет (гейт — пропуск в цикле, не блокировка).
2726
+ * Из depends_on прогона — слаги зависимостей, ещё НЕ исчерпанных. Пусто → все выполнены,
2727
+ * дрона можно спавнить. Явная зависимость (spawn --after) отдельна от area-lock: area
2728
+ * освобождается на review (до мержа), а тут ждём финала блокера, чтобы следующий дрон
2729
+ * стартовал от его кода.
2730
+ *
2731
+ * TASWM-361: критерий — «живых прогонов по слагу не осталось», а не «есть done». Раньше
2732
+ * зависимость снимал ТОЛЬКО успех, и остановленный оператором блокер держал ждущих вечно
2733
+ * (VESPA и TYPHON — сутки очереди при пустом рое). Само правило и его обоснование —
2734
+ * `judgeDep` в swarm-core/deps.ts (его же читают зеркало прода и прод-снапшот).
2735
+ *
2736
+ * Несуществующий/зациклённый слаг прогонов не имеет вовсе → `pending` → прогон ждёт, как и
2737
+ * раньше, но очередь не виснет (гейт — пропуск в цикле, не блокировка).
2685
2738
  */
2686
2739
  unmetDeps(depends_on) {
2740
+ return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
2741
+ }
2742
+ /**
2743
+ * TASWM-361: вердикт по каждой зависимости — не только «держит ли», но и КАК закрылась
2744
+ * (успехом блокера или его неуспешным финалом). Разница нужна overmind: снятие по
2745
+ * неуспеху оставляет след в ленге ждавшего прогона, штатное — нет.
2746
+ */
2747
+ depVerdicts(depends_on) {
2687
2748
  if (!depends_on || depends_on.length === 0) return [];
2688
- const rows = this.db.prepare("SELECT DISTINCT task_slug FROM runs WHERE status = 'done' AND resolves_run_id IS NULL").all();
2689
- const done = new Set(rows.map((r) => r.task_slug));
2690
- return depends_on.filter((slug) => !done.has(slug));
2749
+ const slugs = [...new Set(depends_on)];
2750
+ const rows = this.db.prepare(
2751
+ `SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL ORDER BY id`
2752
+ ).all(...slugs);
2753
+ const bySlug = /* @__PURE__ */ new Map();
2754
+ for (const r of rows) {
2755
+ const list = bySlug.get(r.task_slug) ?? [];
2756
+ list.push({ status: r.status, name: r.name, stopReason: r.stop_reason });
2757
+ bySlug.set(r.task_slug, list);
2758
+ }
2759
+ return depends_on.map((slug) => ({ slug, ...judgeDep(bySlug.get(slug) ?? []) }));
2691
2760
  }
2692
2761
  /** Все репо, засветившиеся в прогонах — whitelist для валидации spawn из веба. */
2693
2762
  distinctRepos() {
@@ -4172,10 +4241,13 @@ var SwarmClient = class _SwarmClient {
4172
4241
  // редко — без этого лог был бы простынёй из одной и той же строки, и в ней потонуло бы
4173
4242
  // всё остальное. Меняется набор (эпик закрыли / блокер прошли) → печатаем снова.
4174
4243
  lastClaimSkipLog = null;
4175
- // Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг → «прогон этой задачи уже done».
4176
- // Монотонно (done не откатывается) → положительный ответ не протухает; отрицательный
4177
- // переспрашивается по TTL. Живёт в памяти: рестарт демона просто переспросит прод.
4178
- depsDoneInProd = /* @__PURE__ */ new Set();
4244
+ // Зависимости, подтверждённые ПРОДОМ (TASWM-300): слаг → вердикт по ВСЕМ его прогонам.
4245
+ // `done` монотонен (не откатывается) → положительный ответ не протухает и больше не
4246
+ // спрашивается; прочие вердикты переспрашиваются по TTL — «исчерпана неуспешно»
4247
+ // (TASWM-361) монотонностью НЕ обладает: оператор вправе переигрть остановленный блокер
4248
+ // новым прогоном того же слага, и тогда зависимость снова становится ожиданием.
4249
+ // Живёт в памяти: рестарт демона просто переспросит прод.
4250
+ depsProd = /* @__PURE__ */ new Map();
4179
4251
  depsAskedAt = /* @__PURE__ */ new Map();
4180
4252
  static DEPS_TTL_MS = 6e4;
4181
4253
  // TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
@@ -4602,12 +4674,36 @@ var SwarmClient = class _SwarmClient {
4602
4674
  return [...seen];
4603
4675
  }
4604
4676
  unmetDeps(depends_on) {
4677
+ return this.depVerdicts(depends_on).filter((d) => !d.met).map((d) => d.slug);
4678
+ }
4679
+ /**
4680
+ * TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
4681
+ *
4682
+ * АСИММЕТРИЯ ЗЕРКАЛА И ПРОДА, и почему она здесь намеренная. Зеркало — ЧАСТИЧНАЯ выборка
4683
+ * (только прогоны этой машины, только окно гидрации), и по частичной выборке два вывода
4684
+ * стоят разного:
4685
+ * • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он не
4686
+ * портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
4687
+ * • «живых прогонов слага не осталось» — вывод от ОТСУТСТВИЯ: невидимый зеркалу прогон
4688
+ * на соседней машине как раз и может быть живым. Поэтому «исчерпана неуспешно»
4689
+ * принимаем ТОЛЬКО из ответа прода (refreshDeps) — он видит всех.
4690
+ * Прод недоступен → вердикта нет → прогон честно ждёт следующего окна: лишняя минута
4691
+ * очереди дешевле дрона, поднятого поверх работающего блокера.
4692
+ */
4693
+ depVerdicts(depends_on) {
4605
4694
  if (!depends_on || depends_on.length === 0) return [];
4606
- const done = new Set(this.depsDoneInProd);
4695
+ const doneInMirror = /* @__PURE__ */ new Set();
4607
4696
  for (const r of this.runs.values()) {
4608
- if (r.status === "done" && r.resolves_run_id == null) done.add(r.task_slug);
4697
+ if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null) doneInMirror.add(r.task_slug);
4609
4698
  }
4610
- return depends_on.filter((slug) => !done.has(slug));
4699
+ return depends_on.map((slug) => {
4700
+ if (doneInMirror.has(slug)) {
4701
+ return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
4702
+ }
4703
+ const fromProd = this.depsProd.get(slug);
4704
+ if (fromProd) return { slug, ...fromProd };
4705
+ return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
4706
+ });
4611
4707
  }
4612
4708
  /**
4613
4709
  * TASWM-300: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
@@ -4618,14 +4714,15 @@ var SwarmClient = class _SwarmClient {
4618
4714
  * просто позавчера, в зеркало не попадала — и `--after` держал прогон в очереди вечно
4619
4715
  * (04.08: снимали руками).
4620
4716
  *
4621
- * Кеш. «Выполнено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
4622
- * положительный ответ помним навсегда и больше не спрашиваем. Отрицательный переспрашиваем
4717
+ * Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
4718
+ * положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
4719
+ * «исчерпана неуспешно», TASWM-361 — блокер могут переиграть новым прогоном) переспрашиваем
4623
4720
  * не чаще DEPS_TTL_MS: тик идёт раз в ~4с, а ждущих зависимость прогонов может быть
4624
4721
  * много — без TTL это был бы поход в прод на каждый слаг каждый тик.
4625
4722
  */
4626
4723
  async refreshDeps(slugs, nowMs = Date.now()) {
4627
4724
  for (const slug of new Set(slugs)) {
4628
- if (!slug || this.depsDoneInProd.has(slug)) continue;
4725
+ if (!slug || this.depsProd.get(slug)?.outcome === "done") continue;
4629
4726
  const asked = this.depsAskedAt.get(slug);
4630
4727
  if (asked !== void 0 && nowMs - asked < _SwarmClient.DEPS_TTL_MS) continue;
4631
4728
  this.depsAskedAt.set(slug, nowMs);
@@ -4638,7 +4735,18 @@ var SwarmClient = class _SwarmClient {
4638
4735
  }
4639
4736
  if (res?.isError) continue;
4640
4737
  const runs = res.structuredContent?.runs ?? [];
4641
- if (runs.some((r) => r.status === "done" && !r.resolvesRunId)) this.depsDoneInProd.add(slug);
4738
+ const facts = runs.slice().sort((a, b) => String(a.createdAt ?? "").localeCompare(String(b.createdAt ?? ""))).map((r) => ({
4739
+ status: String(r.status ?? ""),
4740
+ // Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
4741
+ // не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
4742
+ // с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
4743
+ service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
4744
+ // В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
4745
+ // запасной вариант ради фикстур и старых ответов.
4746
+ name: r.droneName ?? r.name ?? null,
4747
+ stopReason: r.stopReason ?? null
4748
+ }));
4749
+ this.depsProd.set(slug, judgeDep(facts));
4642
4750
  }
4643
4751
  }
4644
4752
  /**
@@ -5293,6 +5401,26 @@ var SwarmClient = class _SwarmClient {
5293
5401
  const v = sc.config?.exclusive;
5294
5402
  return typeof v === "boolean" ? v : null;
5295
5403
  }
5404
+ /**
5405
+ * Live КВОТА «дронов на проект» из Taskless (TASWM-365): тот же swarm_repo_config по
5406
+ * repoFullName — Project.swarmConfig.maxDrones. Сколько прогонов этого проекта могут идти
5407
+ * одновременно; 0 — без ограничения (дефолт проекта). Отдаём ЧИСЛО КАК ЕСТЬ, включая ноль:
5408
+ * превращать его в «без ограничения» — дело ридера (makeDroneCapReader), клиент здесь только
5409
+ * достаёт настройку. null (прод недоступен / repo не смэплен / поля нет / мусор) → ридер
5410
+ * возьмёт «без ограничения», то есть прежнее поведение.
5411
+ */
5412
+ async repoDroneCap(repoFullName) {
5413
+ let res;
5414
+ try {
5415
+ res = await this.call("swarm_repo_config", { repoFullName });
5416
+ } catch {
5417
+ return null;
5418
+ }
5419
+ if (res?.isError) return null;
5420
+ const sc = res.structuredContent ?? {};
5421
+ const v = sc.config?.maxDrones;
5422
+ return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
5423
+ }
5296
5424
  /**
5297
5425
  * TASWM-291: МАШИНЫ роя владельца (swarm_list_hosts) — чтобы `swarm spawn --host
5298
5426
  * <имя>` резолвил человеческое имя в hostId, а `swarm hosts` показывал, какие
@@ -6404,7 +6532,7 @@ function hostIdentityPath(home) {
6404
6532
  function loadHostIdentity(deps) {
6405
6533
  const exists = deps.exists ?? existsSync6;
6406
6534
  const readFile2 = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
6407
- const writeFile2 = deps.writeFile ?? ((p, data) => {
6535
+ const writeFile3 = deps.writeFile ?? ((p, data) => {
6408
6536
  writeFileSync5(`${p}.tmp`, data);
6409
6537
  renameSync4(`${p}.tmp`, p);
6410
6538
  });
@@ -6424,7 +6552,7 @@ function loadHostIdentity(deps) {
6424
6552
  }
6425
6553
  const hostId = newId();
6426
6554
  try {
6427
- writeFile2(path, JSON.stringify({ hostId, hostName, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6555
+ writeFile3(path, JSON.stringify({ hostId, hostName, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6428
6556
  } catch {
6429
6557
  console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C ${path} \u2014 hostId \u0436\u0438\u0432\u0451\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043E \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430`);
6430
6558
  }
@@ -7610,8 +7738,8 @@ var GK = ['claw','spore','eye','hex'];
7610
7738
  var ST = ${jsonForScript(statusTuples())};
7611
7739
  var ORDER = { running:0, blocked:1, paused:2, storm_dead:3, review:4, stalled:5, queued:6, stopped:7, done:8, failed:9, superseded:10 };
7612
7740
  var TERM = ${jsonForScript(Object.fromEntries(TERMINAL_STATUSES.map((s) => [s, 1])))};
7613
- var EVL = { spawned:'\u0434\u0440\u043E\u043D \u0437\u0430\u043F\u0443\u0449\u0435\u043D', step:'', blocked:'\u0432\u043E\u043F\u0440\u043E\u0441 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443', answered:'\u043F\u043E\u043B\u0443\u0447\u0435\u043D \u043E\u0442\u0432\u0435\u0442', pr_opened:'PR \u043E\u0442\u043A\u0440\u044B\u0442', merged:'PR \u0441\u043C\u0435\u0440\u0436\u0451\u043D, \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u0430', done:'\u0433\u043E\u0442\u043E\u0432\u043E', failed:'\u0434\u0440\u043E\u043D \u0443\u043F\u0430\u043B', stalled:'\u0434\u0440\u043E\u043D \u0437\u0430\u0432\u0438\u0441/\u043F\u043E\u0433\u0438\u0431', respawned:'\u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0435', paused:'\u0437\u0430\u043C\u0435\u0440 \u2014 overmind \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D', resumed:'overmind \u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u2014 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B', rescued:'worktree \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u2014 \u0437\u0430\u0431\u0435\u0440\u0438 \u0440\u0443\u043A\u0430\u043C\u0438', conflict:'\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 main (merge-main-in)', died:'\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440', storm_start:'\u26A1 \u043F\u0441\u0438\u043E\u043D\u043D\u0430\u044F \u0441\u0432\u044F\u0437\u044C \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430 \u2014 Anthropic \u0448\u0442\u043E\u0440\u043C\u0438\u0442', storm_end:'\u0441\u0432\u044F\u0437\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E\u0431\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044F', storm_dead:'\u0434\u0440\u043E\u043D \u0432 \u043A\u043E\u043A\u043E\u043D\u0435 (\u0448\u0442\u043E\u0440\u043C)', stopped:'\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D', superseded:'\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D', anomaly:'\u26A0\uFE0F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u044F \u0440\u043E\u044F' };
7614
- var MDOT = { merged:'--success', done:'--success', pr_opened:'--info', spawned:'--muted-foreground', stalled:'--destructive', failed:'--destructive', respawned:'--warning', paused:'--warning', resumed:'--success', rescued:'--destructive', conflict:'--warning', died:'--destructive', storm_start:'--destructive', storm_end:'--success', storm_dead:'--warning', stopped:'--muted-foreground', superseded:'--muted-foreground', anomaly:'--warning' };
7741
+ var EVL = { spawned:'\u0434\u0440\u043E\u043D \u0437\u0430\u043F\u0443\u0449\u0435\u043D', step:'', blocked:'\u0432\u043E\u043F\u0440\u043E\u0441 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0443', answered:'\u043F\u043E\u043B\u0443\u0447\u0435\u043D \u043E\u0442\u0432\u0435\u0442', pr_opened:'PR \u043E\u0442\u043A\u0440\u044B\u0442', merged:'PR \u0441\u043C\u0435\u0440\u0436\u0451\u043D, \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u043A\u0440\u044B\u0442\u0430', done:'\u0433\u043E\u0442\u043E\u0432\u043E', failed:'\u0434\u0440\u043E\u043D \u0443\u043F\u0430\u043B', stalled:'\u0434\u0440\u043E\u043D \u0437\u0430\u0432\u0438\u0441/\u043F\u043E\u0433\u0438\u0431', respawned:'\u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0435', paused:'\u0437\u0430\u043C\u0435\u0440 \u2014 overmind \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D', resumed:'overmind \u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u2014 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B', rescued:'worktree \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u2014 \u0437\u0430\u0431\u0435\u0440\u0438 \u0440\u0443\u043A\u0430\u043C\u0438', conflict:'\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0441 main (merge-main-in)', died:'\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440', storm_start:'\u26A1 \u043F\u0441\u0438\u043E\u043D\u043D\u0430\u044F \u0441\u0432\u044F\u0437\u044C \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430 \u2014 Anthropic \u0448\u0442\u043E\u0440\u043C\u0438\u0442', storm_end:'\u0441\u0432\u044F\u0437\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E\u0431\u0443\u0436\u0434\u0430\u0435\u0442\u0441\u044F', storm_dead:'\u0434\u0440\u043E\u043D \u0432 \u043A\u043E\u043A\u043E\u043D\u0435 (\u0448\u0442\u043E\u0440\u043C)', stopped:'\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D', superseded:'\u043F\u0435\u0440\u0435\u0438\u0433\u0440\u0430\u043D', anomaly:'\u26A0\uFE0F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u044F \u0440\u043E\u044F', dep_cleared:'\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u0441\u043D\u044F\u0442\u0430 \u2014 \u0431\u043B\u043E\u043A\u0435\u0440 \u043D\u0435 \u0434\u043E\u0435\u0434\u0435\u0442' };
7742
+ var MDOT = { merged:'--success', done:'--success', pr_opened:'--info', spawned:'--muted-foreground', stalled:'--destructive', failed:'--destructive', respawned:'--warning', paused:'--warning', resumed:'--success', rescued:'--destructive', conflict:'--warning', died:'--destructive', storm_start:'--destructive', storm_end:'--success', storm_dead:'--warning', stopped:'--muted-foreground', superseded:'--muted-foreground', anomaly:'--warning', dep_cleared:'--warning' };
7615
7743
 
7616
7744
  function E(s){ return String(s==null?"":s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]}); }
7617
7745
  function color(st){ return 'var('+(ST[st]||ST.queued)[2]+')'; }
@@ -8772,9 +8900,10 @@ init_droneChannel();
8772
8900
 
8773
8901
  // src/git.ts
8774
8902
  import { existsSync as existsSync8, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
8775
- import { appendFile, mkdir, readFile, rm } from "fs/promises";
8776
- import { isAbsolute as isAbsolute2, join as join13 } from "path";
8903
+ import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
8904
+ import { isAbsolute as isAbsolute2, join as join13, sep as sep2 } from "path";
8777
8905
  import { $ as $5 } from "execa";
8906
+ init_swarmFiles();
8778
8907
  function isLinkedWorktree(dir) {
8779
8908
  try {
8780
8909
  return statSync8(join13(dir, ".git")).isFile();
@@ -8825,8 +8954,10 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8825
8954
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8826
8955
 
8827
8956
  // src/attachments.ts
8828
- import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
8957
+ import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
8829
8958
  import { join as join15 } from "path";
8959
+ init_swarmFiles();
8960
+ var ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
8830
8961
  var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
8831
8962
  var ATTACH_MAX_FILE_BYTES = 8 * 1024 * 1024;
8832
8963
  var IMAGE_EXT = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "heic"]);
@@ -9239,11 +9370,11 @@ function savePauseState(home, state, io = {}) {
9239
9370
  remove(path);
9240
9371
  return;
9241
9372
  }
9242
- const writeFile2 = io.writeFile ?? ((p, data) => {
9373
+ const writeFile3 = io.writeFile ?? ((p, data) => {
9243
9374
  writeFileSync10(`${p}.tmp`, data);
9244
9375
  renameSync5(`${p}.tmp`, p);
9245
9376
  });
9246
- writeFile2(path, JSON.stringify(state, null, 2));
9377
+ writeFile3(path, JSON.stringify(state, null, 2));
9247
9378
  }
9248
9379
  function makePauseState(source, reason, now = Date.now()) {
9249
9380
  return {