@taskless-app/swarm 0.6.20 → 0.6.21

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
@@ -2373,7 +2373,7 @@ var init_package = __esm({
2373
2373
  "package.json"() {
2374
2374
  package_default = {
2375
2375
  name: "@taskless-app/swarm",
2376
- version: "0.6.20",
2376
+ version: "0.6.21",
2377
2377
  type: "module",
2378
2378
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2379
2379
  keywords: [
@@ -2423,7 +2423,7 @@ var init_package = __esm({
2423
2423
  execa: "^10.0.0",
2424
2424
  zod: "3.25.76"
2425
2425
  },
2426
- gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
2426
+ gitHead: "584411cbe69583a8727e89caa8ae7e626f8fd8ca"
2427
2427
  };
2428
2428
  }
2429
2429
  });
@@ -8510,10 +8510,24 @@ async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
8510
8510
  }
8511
8511
  }
8512
8512
  var NO_CHECKS_STDERR = /no checks reported/i;
8513
+ var CiAccessDeniedError = class extends Error {
8514
+ detail;
8515
+ constructor(detail) {
8516
+ super(`\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C: ${detail}`);
8517
+ this.name = "CiAccessDeniedError";
8518
+ this.detail = detail;
8519
+ }
8520
+ };
8521
+ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have admin rights|resource not accessible/i;
8522
+ function firstLine(s) {
8523
+ return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
8524
+ }
8513
8525
  var realChecksRunner = async (pr) => {
8514
8526
  const r = await $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
8515
8527
  const stdout = r.stdout ?? "";
8516
- if (!stdout.trim() && NO_CHECKS_STDERR.test(r.stderr ?? "")) return "[]";
8528
+ const stderr = r.stderr ?? "";
8529
+ if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
8530
+ if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
8517
8531
  return stdout;
8518
8532
  };
8519
8533
  async function prCheckRows(pr, run) {
@@ -8522,13 +8536,17 @@ async function prCheckRows(pr, run) {
8522
8536
  const rows = JSON.parse(out);
8523
8537
  if (!Array.isArray(rows)) return null;
8524
8538
  return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
8525
- } catch {
8539
+ } catch (e) {
8540
+ if (e instanceof CiAccessDeniedError) return { denied: e.detail };
8526
8541
  return null;
8527
8542
  }
8528
8543
  }
8544
+ function isDenied(rows) {
8545
+ return rows != null && !Array.isArray(rows);
8546
+ }
8529
8547
  async function ghChecks(pr, run = realChecksRunner) {
8530
8548
  const rows = await prCheckRows(pr, run);
8531
- if (!rows || rows.length === 0) return null;
8549
+ if (!rows || isDenied(rows) || rows.length === 0) return null;
8532
8550
  let pass = 0, fail = 0, pending2 = 0;
8533
8551
  for (const r of rows) {
8534
8552
  if (r.bucket === "pass") pass++;
@@ -11084,6 +11102,44 @@ init_config();
11084
11102
  init_agentToken();
11085
11103
  init_linkFailure();
11086
11104
 
11105
+ // src/toolFailure.ts
11106
+ init_linkFailure();
11107
+ var TOOL_FAILURE = {
11108
+ /** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
11109
+ NOT_FOUND: "not_found",
11110
+ /** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
11111
+ FORBIDDEN: "forbidden",
11112
+ /** Токен роя не состоит в воркспейсе, которым искали. */
11113
+ WORKSPACE_MISMATCH: "workspace_mismatch",
11114
+ /** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
11115
+ UNREACHABLE: "unreachable",
11116
+ /** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
11117
+ UNAUTHORIZED: "unauthorized",
11118
+ /** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
11119
+ TOOL_ERROR: "tool_error",
11120
+ /** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
11121
+ EMPTY: "empty"
11122
+ };
11123
+ var TOOL_FAILURE_TEXT = {
11124
+ [TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
11125
+ [TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
11126
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
11127
+ [TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
11128
+ [TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
11129
+ [TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
11130
+ [TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
11131
+ };
11132
+ var TOOL_FAILURE_RETRIABLE = {
11133
+ [TOOL_FAILURE.NOT_FOUND]: false,
11134
+ [TOOL_FAILURE.FORBIDDEN]: false,
11135
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
11136
+ [TOOL_FAILURE.UNREACHABLE]: true,
11137
+ // Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
11138
+ [TOOL_FAILURE.UNAUTHORIZED]: false,
11139
+ [TOOL_FAILURE.TOOL_ERROR]: false,
11140
+ [TOOL_FAILURE.EMPTY]: false
11141
+ };
11142
+
11087
11143
  // src/workspace.ts
11088
11144
  init_config();
11089
11145
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
package/dist/overmind.js CHANGED
@@ -4527,6 +4527,78 @@ var init_oauth = __esm({
4527
4527
  }
4528
4528
  });
4529
4529
 
4530
+ // src/toolFailure.ts
4531
+ function trimDetail(s) {
4532
+ const t = s.trim();
4533
+ return t.length <= FAILURE_DETAIL_MAX ? t : `${t.slice(0, FAILURE_DETAIL_MAX)}\u2026`;
4534
+ }
4535
+ function classifyToolFailure(raw) {
4536
+ if (MISMATCH_RE.test(raw)) return TOOL_FAILURE.WORKSPACE_MISMATCH;
4537
+ if (FORBIDDEN_RE.test(raw)) return TOOL_FAILURE.FORBIDDEN;
4538
+ if (NOT_FOUND_RE.test(raw)) return TOOL_FAILURE.NOT_FOUND;
4539
+ return TOOL_FAILURE.TOOL_ERROR;
4540
+ }
4541
+ function classifyThrown(e) {
4542
+ const { kind, detail } = explainLinkFailure(e);
4543
+ const code = kind === "offline" ? TOOL_FAILURE.UNREACHABLE : kind === "unauthorized" ? TOOL_FAILURE.UNAUTHORIZED : (
4544
+ // «ручки нет» и «сервер отказал по существу» разбираем доменно: там может быть
4545
+ // и task_not_found, и отказ по правам — то есть наши коды, а не транспортные.
4546
+ classifyToolFailure(detail)
4547
+ );
4548
+ return { code, detail };
4549
+ }
4550
+ function describeToolFailure(f) {
4551
+ const text = TOOL_FAILURE_TEXT[f.code];
4552
+ const detail = trimDetail(f.detail);
4553
+ return detail ? `${text} (${detail})` : text;
4554
+ }
4555
+ var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE, FAILURE_DETAIL_MAX, NOT_FOUND_RE, FORBIDDEN_RE, MISMATCH_RE;
4556
+ var init_toolFailure = __esm({
4557
+ "src/toolFailure.ts"() {
4558
+ "use strict";
4559
+ init_linkFailure();
4560
+ TOOL_FAILURE = {
4561
+ /** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
4562
+ NOT_FOUND: "not_found",
4563
+ /** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
4564
+ FORBIDDEN: "forbidden",
4565
+ /** Токен роя не состоит в воркспейсе, которым искали. */
4566
+ WORKSPACE_MISMATCH: "workspace_mismatch",
4567
+ /** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
4568
+ UNREACHABLE: "unreachable",
4569
+ /** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
4570
+ UNAUTHORIZED: "unauthorized",
4571
+ /** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
4572
+ TOOL_ERROR: "tool_error",
4573
+ /** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
4574
+ EMPTY: "empty"
4575
+ };
4576
+ TOOL_FAILURE_TEXT = {
4577
+ [TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
4578
+ [TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
4579
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
4580
+ [TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
4581
+ [TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
4582
+ [TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
4583
+ [TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
4584
+ };
4585
+ TOOL_FAILURE_RETRIABLE = {
4586
+ [TOOL_FAILURE.NOT_FOUND]: false,
4587
+ [TOOL_FAILURE.FORBIDDEN]: false,
4588
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
4589
+ [TOOL_FAILURE.UNREACHABLE]: true,
4590
+ // Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
4591
+ [TOOL_FAILURE.UNAUTHORIZED]: false,
4592
+ [TOOL_FAILURE.TOOL_ERROR]: false,
4593
+ [TOOL_FAILURE.EMPTY]: false
4594
+ };
4595
+ FAILURE_DETAIL_MAX = 300;
4596
+ NOT_FOUND_RE = /task_not_found|not_found|не найдена|не найден/i;
4597
+ FORBIDDEN_RE = /forbidden|not visible|no access|нет доступа|(?:http|status|code)[ =:]*403\b|\(403\)|403 forbidden/i;
4598
+ MISMATCH_RE = /workspace_mismatch|not a member of that workspace/i;
4599
+ }
4600
+ });
4601
+
4530
4602
  // src/authState.ts
4531
4603
  function noteTokenRejected(nowMs = Date.now()) {
4532
4604
  if (!denial) {
@@ -4623,25 +4695,44 @@ function isClosingTaskCategory(category) {
4623
4695
  function taskAuthor(u) {
4624
4696
  return u?.id ? { id: u.id, name: u.displayName || "\u0430\u0432\u0442\u043E\u0440 \u0437\u0430\u0434\u0430\u0447\u0438" } : null;
4625
4697
  }
4698
+ function toolErrorText(r) {
4699
+ const fromContent = (r.content ?? []).map((c) => c.text ?? "").filter(Boolean).join(" ").trim();
4700
+ return fromContent || JSON.stringify(r);
4701
+ }
4626
4702
  function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl = fetch) {
4627
4703
  const ws = workspaceId ? { workspaceId } : {};
4628
4704
  const idCache = /* @__PURE__ */ new Map();
4629
4705
  const byWorkspace = /* @__PURE__ */ new Map();
4630
- async function safeCall(name, args) {
4706
+ async function callTool(name, args) {
4631
4707
  try {
4632
4708
  const r = await call(name, WS_FREE_TOOLS.has(name) ? args : { ...args, ...ws });
4633
4709
  if (r?.isError) {
4634
- console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, JSON.stringify(r).slice(0, 400));
4635
- return null;
4710
+ const raw = toolErrorText(r);
4711
+ const detail = trimDetail(raw);
4712
+ console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, detail);
4713
+ return { ok: false, failure: { code: classifyToolFailure(raw), detail } };
4636
4714
  }
4637
- return r;
4715
+ return { ok: true, res: r };
4638
4716
  } catch (e) {
4639
- console.error(`[taskless] \u0442\u0443\u043B ${name} \u0443\u043F\u0430\u043B:`, e instanceof Error ? e.message : String(e));
4640
- return null;
4717
+ const failure = classifyThrown(e);
4718
+ console.error(`[taskless] \u0442\u0443\u043B ${name} \u0443\u043F\u0430\u043B:`, failure.detail);
4719
+ return { ok: false, failure };
4641
4720
  }
4642
4721
  }
4643
- async function getTask(idOrSlug) {
4644
- const res = await safeCall("get_task", { id: idOrSlug });
4722
+ async function safeCall(name, args) {
4723
+ const out = await callTool(name, args);
4724
+ return out.ok ? out.res : null;
4725
+ }
4726
+ async function getTaskDetailed(idOrSlug) {
4727
+ const out = await callTool("get_task", { id: idOrSlug });
4728
+ if (!out.ok) return { ok: false, failure: out.failure };
4729
+ const body = taskBodyFrom(out.res, idOrSlug);
4730
+ if (!body) {
4731
+ return { ok: false, failure: { code: TOOL_FAILURE.EMPTY, detail: "get_task: task \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435" } };
4732
+ }
4733
+ return { ok: true, task: body };
4734
+ }
4735
+ function taskBodyFrom(res, idOrSlug) {
4645
4736
  const task = res?.structuredContent?.task ?? null;
4646
4737
  if (!task?.id) return null;
4647
4738
  const slug = task.prefix && task.number != null ? `${task.prefix}-${task.number}` : idOrSlug;
@@ -4671,6 +4762,9 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4671
4762
  idCache.set(body.id, body.id);
4672
4763
  return body;
4673
4764
  }
4765
+ async function getTask(idOrSlug) {
4766
+ return taskBodyFrom(await safeCall("get_task", { id: idOrSlug }), idOrSlug);
4767
+ }
4674
4768
  async function getDocument(id) {
4675
4769
  const res = await safeCall("get_document", { id });
4676
4770
  const doc = res?.structuredContent?.document ?? null;
@@ -4762,6 +4856,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4762
4856
  }
4763
4857
  const self = {
4764
4858
  getTask,
4859
+ getTaskDetailed,
4765
4860
  ...fetchFile ? { fetchFile } : {},
4766
4861
  startTask: (idOrSlug) => transition("start_task", idOrSlug),
4767
4862
  // TASWM-294: intent — «человек, посмотри», а не «Готово» (см. докблок в TasklessClient).
@@ -4857,6 +4952,10 @@ function parseWorkspaces(res) {
4857
4952
  return arr.filter((w) => !!w && typeof w.id === "string" && typeof w.slug === "string").map((w) => ({ id: w.id, slug: w.slug, name: typeof w.name === "string" ? w.name : w.slug, role: w.role }));
4858
4953
  }
4859
4954
  function requireTaskBody(task, slug, workspaceId) {
4955
+ if (task && "ok" in task) {
4956
+ if (task.ok) return task.task;
4957
+ throw new TaskBodyUnavailableError(slug, workspaceId, task.failure);
4958
+ }
4860
4959
  if (!task) throw new TaskBodyUnavailableError(slug, workspaceId);
4861
4960
  return task;
4862
4961
  }
@@ -5088,16 +5187,25 @@ var init_taskless = __esm({
5088
5187
  init_oauth();
5089
5188
  init_agentToken();
5090
5189
  init_linkFailure();
5190
+ init_toolFailure();
5091
5191
  init_authState();
5092
5192
  init_workspace();
5093
5193
  SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
5094
5194
  WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
5095
5195
  TaskBodyUnavailableError = class extends Error {
5096
- constructor(slug, workspaceId) {
5196
+ /** Код причины — по нему решают, повторять ли (TOOL_FAILURE_RETRIABLE), а не по тексту. */
5197
+ code;
5198
+ // TASWM-484: причина называется, а не перечисляется. Прежний текст («задачи там нет
5199
+ // ЛИБО токену её не видно») заставлял человека проверять обе версии руками — 21.08 на
5200
+ // этом ушло полдня, и обе оказались не теми. Теперь код приходит от классификатора
5201
+ // ответа прода, а человеку достаётся объяснение и то, что прод сказал дословно.
5202
+ constructor(slug, workspaceId, failure) {
5203
+ const f = failure ?? { code: TOOL_FAILURE.EMPTY, detail: "" };
5097
5204
  super(
5098
- `\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: ${slug} \u0432 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 ${workspaceId || "(\u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0438\u043D \u0434\u0435\u043C\u043E\u043D\u0430)"} \u2014 Taskless \u043D\u0430 get_task \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0443\u0441\u0442\u043E (\u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0430\u043C \u043D\u0435\u0442 \u043B\u0438\u0431\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0435\u0451 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E, \u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430). \u0414\u0440\u043E\u043D \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u0442: \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0441\u043B\u0430\u0433\u0443, \u0431\u0435\u0437 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u044F \u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u0432, \u0434\u043E\u0440\u043E\u0436\u0435 \u0438 \u0432\u0440\u0435\u0434\u043D\u0435\u0435, \u0447\u0435\u043C \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0442\u044C (TASWM-288).`
5205
+ `\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F: ${slug} \u0432 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 ${workspaceId || "(\u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0438\u043D \u0434\u0435\u043C\u043E\u043D\u0430)"} \u2014 ${describeToolFailure(f)}. \u0414\u0440\u043E\u043D \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u0442: \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0441\u043B\u0430\u0433\u0443, \u0431\u0435\u0437 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u044F \u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u0432, \u0434\u043E\u0440\u043E\u0436\u0435 \u0438 \u0432\u0440\u0435\u0434\u043D\u0435\u0435, \u0447\u0435\u043C \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0442\u044C (TASWM-288).`
5099
5206
  );
5100
5207
  this.name = "TaskBodyUnavailableError";
5208
+ this.code = f.code;
5101
5209
  }
5102
5210
  };
5103
5211
  TASK_CONTEXT_MAX_DOCS = 5;
@@ -10768,10 +10876,24 @@ function fullCommitHeadline(headline, body) {
10768
10876
  return head.slice(0, -1) + tail2;
10769
10877
  }
10770
10878
  var NO_CHECKS_STDERR = /no checks reported/i;
10879
+ var CiAccessDeniedError = class extends Error {
10880
+ detail;
10881
+ constructor(detail) {
10882
+ super(`\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C: ${detail}`);
10883
+ this.name = "CiAccessDeniedError";
10884
+ this.detail = detail;
10885
+ }
10886
+ };
10887
+ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have admin rights|resource not accessible/i;
10888
+ function firstLine(s) {
10889
+ return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
10890
+ }
10771
10891
  var realChecksRunner = async (pr) => {
10772
10892
  const r = await $3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
10773
10893
  const stdout = r.stdout ?? "";
10774
- if (!stdout.trim() && NO_CHECKS_STDERR.test(r.stderr ?? "")) return "[]";
10894
+ const stderr = r.stderr ?? "";
10895
+ if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
10896
+ if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
10775
10897
  return stdout;
10776
10898
  };
10777
10899
  async function prCheckRows(pr, run) {
@@ -10780,13 +10902,17 @@ async function prCheckRows(pr, run) {
10780
10902
  const rows = JSON.parse(out);
10781
10903
  if (!Array.isArray(rows)) return null;
10782
10904
  return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
10783
- } catch {
10905
+ } catch (e) {
10906
+ if (e instanceof CiAccessDeniedError) return { denied: e.detail };
10784
10907
  return null;
10785
10908
  }
10786
10909
  }
10910
+ function isDenied(rows) {
10911
+ return rows != null && !Array.isArray(rows);
10912
+ }
10787
10913
  async function ghChecks(pr, run = realChecksRunner) {
10788
10914
  const rows = await prCheckRows(pr, run);
10789
- if (!rows || rows.length === 0) return null;
10915
+ if (!rows || isDenied(rows) || rows.length === 0) return null;
10790
10916
  let pass = 0, fail = 0, pending2 = 0;
10791
10917
  for (const r of rows) {
10792
10918
  if (r.bucket === "pass") pass++;
@@ -10817,6 +10943,7 @@ function runIdFromLink(link) {
10817
10943
  async function prCiStatus(pr, run = realChecksRunner) {
10818
10944
  const rows = await prCheckRows(pr, run);
10819
10945
  if (!rows) return null;
10946
+ if (isDenied(rows)) return { bucket: "denied", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, detail: rows.denied };
10820
10947
  if (rows.length === 0) return { bucket: "none", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0 };
10821
10948
  const total = rows.length;
10822
10949
  const pass = rows.filter((r) => r.bucket === "pass").length;
@@ -13312,7 +13439,7 @@ async function changedPaths(worktree, opts = {}) {
13312
13439
  // package.json
13313
13440
  var package_default = {
13314
13441
  name: "@taskless-app/swarm",
13315
- version: "0.6.20",
13442
+ version: "0.6.21",
13316
13443
  type: "module",
13317
13444
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13318
13445
  keywords: [
@@ -13362,7 +13489,7 @@ var package_default = {
13362
13489
  execa: "^10.0.0",
13363
13490
  zod: "3.25.76"
13364
13491
  },
13365
- gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
13492
+ gitHead: "584411cbe69583a8727e89caa8ae7e626f8fd8ca"
13366
13493
  };
13367
13494
 
13368
13495
  // src/version.ts
@@ -15866,6 +15993,9 @@ var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u04
15866
15993
  var CI_NO_CHECKS_MARK = "\u0443 PR \u043D\u0435\u0442 \u0447\u0435\u043A\u043E\u0432 \u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442";
15867
15994
  var CI_WAIT_POLL_MARK = "\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A";
15868
15995
  var CI_POLL_LIMIT_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u043E \u043F\u043E\u0442\u043E\u043B\u043A\u0443";
15996
+ var CI_DENIED_LIMIT = Number(process.env.SWARM_CI_DENIED_LIMIT) || 3;
15997
+ var CI_DENIED_MARK = "\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C (403)";
15998
+ var CI_DENIED_STOP_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: \u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 (403)";
15869
15999
  var CI_WAIT_LIMIT_MS = Number(process.env.SWARM_CI_WAIT_LIMIT_MS) || 45 * 60 * 1e3;
15870
16000
  var CI_POLL_LIMIT = Math.max(
15871
16001
  Number(process.env.SWARM_CI_POLL_LIMIT) || 90,
@@ -16363,8 +16493,13 @@ function buildCiWakeNudge(summary) {
16363
16493
  ].join("\n");
16364
16494
  }
16365
16495
  function buildCiReadyNudge(prUrl, over) {
16366
- const counts = over.kind !== "gaveup" && over.ci.total > 0 ? ` (${over.ci.pass}/${over.ci.total})` : "";
16367
- const head = over.kind === "none" ? `\u2139\uFE0F \u0416\u0434\u0430\u0442\u044C CI \u043D\u0430 PR ${prUrl} \u041D\u0415\u0427\u0415\u0413\u041E: \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0443 PR \u043D\u0435\u0442, \u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E workflow \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0442\u0443\u0442 \u043D\u0435 \u0431\u044B\u0432\u0430\u0435\u0442. \u0420\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D), \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.` : over.kind === "gaveup" ? `\u26A0\uFE0F \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0442\u0430\u043A \u0438 \u041D\u0415 \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u2014 CI \u043E\u0442\u0432\u0430\u043B\u0438\u043B\u0441\u044F \u0438\u043B\u0438 \u043D\u0430\u043C\u0435\u0440\u0442\u0432\u043E \u0432\u0438\u0441\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438, \u0438 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C (\u0441\u0440\u043E\u043A\u0438 \u0438 \u0441\u0447\u0451\u0442\u0447\u0438\u043A \u2014 \u0432 \u0441\u043E\u0431\u044B\u0442\u0438\u044F\u0445 \u043F\u0440\u043E\u0433\u043E\u043D\u0430). \u0420\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0432\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D). \u041D\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0439 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u043F\u0440\u043E \u044D\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C.` : `\u2705 \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u0438 \u043E\u043D\u0438 \u0437\u0435\u043B\u0451\u043D\u044B\u0435${counts} \u2014 \u0440\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E, \u0436\u0434\u0430\u0442\u044C \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0447\u0435\u0433\u043E.`;
16496
+ const counts = (over.kind === "pass" || over.kind === "none") && over.ci.total > 0 ? ` (${over.ci.pass}/${over.ci.total})` : "";
16497
+ const head = over.kind === "none" ? `\u2139\uFE0F \u0416\u0434\u0430\u0442\u044C CI \u043D\u0430 PR ${prUrl} \u041D\u0415\u0427\u0415\u0413\u041E: \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0443 PR \u043D\u0435\u0442, \u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E workflow \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0442\u0443\u0442 \u043D\u0435 \u0431\u044B\u0432\u0430\u0435\u0442. \u0420\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D), \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.` : over.kind === "gaveup" ? `\u26A0\uFE0F \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0442\u0430\u043A \u0438 \u041D\u0415 \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u2014 CI \u043E\u0442\u0432\u0430\u043B\u0438\u043B\u0441\u044F \u0438\u043B\u0438 \u043D\u0430\u043C\u0435\u0440\u0442\u0432\u043E \u0432\u0438\u0441\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438, \u0438 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C (\u0441\u0440\u043E\u043A\u0438 \u0438 \u0441\u0447\u0451\u0442\u0447\u0438\u043A \u2014 \u0432 \u0441\u043E\u0431\u044B\u0442\u0438\u044F\u0445 \u043F\u0440\u043E\u0433\u043E\u043D\u0430). \u0420\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0432\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D). \u041D\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0439 CI \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0438\u043D\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043D\u0435 \u043F\u0440\u043E\u0441\u0438 \u2014 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u043F\u0440\u043E \u044D\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C.` : (
16498
+ // TASWM-483: чеки, скорее всего, ЕСТЬ и даже зелёные — их просто не видно рою (403).
16499
+ // Врать «CI не прошёл» тут так же нельзя, как «CI зелёный»: единственная честная
16500
+ // формулировка — «не проверялся», и она обязана доехать до вердикта.
16501
+ over.kind === "denied" ? `\u26A0\uFE0F \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0440\u043E\u044E \u041D\u0415 \u0412\u0418\u0414\u041D\u042B: GitHub \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0442\u043A\u0430\u0437\u043E\u043C \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C \u2014 403${over.detail ? ` (${over.detail})` : ""}. \u0427\u0435\u043A\u0438 \u043D\u0430 PR, \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E, \u0435\u0441\u0442\u044C \u0438 \u0434\u0430\u0436\u0435 \u0437\u0435\u043B\u0451\u043D\u044B\u0435, \u043D\u043E \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0438\u0445 \u0440\u043E\u0439 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442, \u043F\u043E\u043A\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043D\u0435 \u0432\u044B\u0434\u0430\u0441\u0442 \u0434\u043E\u0441\u0442\u0443\u043F; \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0440\u043E\u0439 \u0443\u0436\u0435 \u043F\u043E\u0437\u0432\u0430\u043B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C. \u0420\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0432\u043E\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E: \u0432\u044B\u043D\u043E\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0441\u0430\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D), \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439 \xABCI \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0441\u044F: \u0440\u043E\u044E \u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C (403)\xBB. \u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u043C PR \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0439 \u0438 \u0447\u0438\u043D\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0441\u0438.` : `\u2705 \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 CI \u043D\u0430 PR ${prUrl} \u0417\u0410\u0412\u0415\u0420\u0428\u0418\u041B\u0418\u0421\u042C \u0438 \u043E\u043D\u0438 \u0437\u0435\u043B\u0451\u043D\u044B\u0435${counts} \u2014 \u0440\u043E\u0439 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u043B \u0442\u0435\u0431\u044F \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E, \u0436\u0434\u0430\u0442\u044C \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0447\u0435\u0433\u043E.`
16502
+ );
16368
16503
  return [
16369
16504
  head,
16370
16505
  "",
@@ -16834,6 +16969,27 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
16834
16969
  return;
16835
16970
  }
16836
16971
  const ci = await deps.ci.checks(run.pr_url).catch(() => null);
16972
+ if (ci?.bucket === "denied") {
16973
+ const seen = ciDeniedStreak(deps.db, run.id) + 1;
16974
+ if (seen < CI_DENIED_LIMIT) {
16975
+ deps.db.addEvent(
16976
+ run.id,
16977
+ "ci_checked",
16978
+ `CI \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${CI_DENIED_MARK}${ci.detail ? ` \u2014 ${ci.detail}` : ""}; \u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${seen}/${CI_DENIED_LIMIT} [${CI_WAIT_POLL_MARK}]`
16979
+ );
16980
+ return;
16981
+ }
16982
+ closeCiWatch(
16983
+ deps,
16984
+ run,
16985
+ CI_DENIED_STOP_MARK,
16986
+ `GitHub \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 403 \u043D\u0430 \u0447\u0442\u0435\u043D\u0438\u0435 \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432${ci.detail ? ` (${ci.detail})` : ""} \u2014 ${seen} \u043E\u043F\u0440\u043E\u0441(\u043E\u0432) \u043F\u043E\u0434\u0440\u044F\u0434. \u042D\u0442\u043E \u043D\u0435 \u0441\u0431\u043E\u0439 \u0441\u0435\u0442\u0438 \u0438 \u043D\u0435 \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u044B\u0439 CI: \u0443 \u0440\u043E\u044F \u043D\u0435\u0442 \u043F\u0440\u0430\u0432 \u043D\u0430 \u0447\u0435\u043A\u0438 \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0442\u044C \u0431\u0435\u0441\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u043E. \u041F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u044F \u043D\u0435 \u0432\u0438\u0436\u0443 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u044E; \u0440\u0435\u0432\u044C\u044E \u043F\u043E\u0439\u0434\u0451\u0442 \u0411\u0415\u0417 CI (\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 \u044D\u0442\u043E \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435), \u0434\u043E\u0441\u0442\u0443\u043F \u0432\u044B\u0434\u0430\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A: \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C GitHub App \u0440\u043E\u044F \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439/\u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u044E.`,
16987
+ `${droneLabel(run)}: GitHub \u043D\u0435 \u0434\u0430\u0451\u0442 \u0440\u043E\u044E \u0447\u0438\u0442\u0430\u0442\u044C \u0447\u0435\u043A\u0438 (403) \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043D\u0435 \u0432\u0438\u0436\u0443, \u0437\u043E\u0432\u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0438 \u0432\u0435\u0434\u0443 \u0440\u0435\u0432\u044C\u044E \u0431\u0435\u0437 CI.`
16988
+ );
16989
+ await escalateCiDenied(deps, run, ci.detail ?? null, seen);
16990
+ await wakeReviewerOnCiReady(deps, run, { kind: "denied", ...ci.detail ? { detail: ci.detail } : {} });
16991
+ return;
16992
+ }
16837
16993
  if (ci?.bucket === "none" && !repoHasCi(deps, run)) {
16838
16994
  closeCiWatch(
16839
16995
  deps,
@@ -16877,7 +17033,27 @@ function ciWatchMark(base3, cycle) {
16877
17033
  return `${base3} [\u0446\u0438\u043A\u043B ${cycle}]`;
16878
17034
  }
16879
17035
  function isCiWatchClose(text) {
16880
- return text.includes(CI_NO_CHECKS_MARK) || text.includes(CI_POLL_LIMIT_MARK);
17036
+ return text.includes(CI_NO_CHECKS_MARK) || text.includes(CI_POLL_LIMIT_MARK) || text.includes(CI_DENIED_STOP_MARK);
17037
+ }
17038
+ function ciDeniedStreak(db, runId) {
17039
+ return ciStreak(db, runId, CI_DENIED_MARK).count;
17040
+ }
17041
+ async function escalateCiDenied(deps, run, detail, polls) {
17042
+ const why = `${CI_DENIED_MARK}: GitHub \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 403 \u043D\u0430 \u0447\u0442\u0435\u043D\u0438\u0435 \u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 PR${detail ? ` (${detail})` : ""} \u2014 ${polls} \u043E\u043F\u0440\u043E\u0441(\u043E\u0432) \u043F\u043E\u0434\u0440\u044F\u0434. \u0420\u043E\u0439 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0442\u044C PR \u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443\u043C\u0435\u0435\u0442, \u0430 \u0447\u0438\u0442\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u2014 \u043D\u0435\u0442: \u043F\u0440\u0430\u0432\u0430 \u0432\u044B\u0434\u0430\u043D\u044B \u0447\u0430\u0441\u0442\u0438\u0447\u043D\u043E. \u041E\u043F\u0440\u043E\u0441 CI \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u043F\u0440\u0435\u043A\u0440\u0430\u0449\u0451\u043D, \u0440\u0435\u0432\u044C\u044E \u0438\u0434\u0451\u0442 \u0431\u0435\u0437 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A: \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C GitHub App \u0440\u043E\u044F \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439/\u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u044E.`;
17043
+ deps.db.addEvent(run.id, "anomaly", why);
17044
+ if (!deps.watchdog?.notify) return;
17045
+ const signal = {
17046
+ key: `ci_denied:${run.id}:${run.review_cycle}`,
17047
+ title: signalTitle(run, "GitHub \u043D\u0435 \u0434\u0430\u0451\u0442 \u0440\u043E\u044E \u0447\u0438\u0442\u0430\u0442\u044C \u0447\u0435\u043A\u0438 (403)"),
17048
+ body: `${run.task_slug}: ${polls} \u043E\u043F\u0440\u043E\u0441(\u043E\u0432) \u043F\u043E\u0434\u0440\u044F\u0434 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0438\u0441\u044C 403${detail ? ` (${detail})` : ""} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0440\u043E\u0439 \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u0438 \u0436\u0434\u0430\u0442\u044C \u0438\u0445 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442. \u0420\u0435\u0432\u044C\u044E \u043F\u043E\u0439\u0434\u0451\u0442 \u0431\u0435\u0437 CI (\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 \u044D\u0442\u043E \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435). \u0412\u044B\u0434\u0430\u0439 \u0434\u043E\u0441\u0442\u0443\u043F: GitHub App \u0440\u043E\u044F \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439/\u043E\u0440\u0433\u0430\u043D\u0438\u0437\u0430\u0446\u0438\u044E. PR: ${run.pr_url ?? "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D"}.`,
17049
+ runId: run.id,
17050
+ ...hrefOf(run.task_url)
17051
+ };
17052
+ try {
17053
+ await deps.watchdog.notify(signal);
17054
+ } catch (e) {
17055
+ console.error(`[overmind] \u0441\u0438\u0433\u043D\u0430\u043B \u043E\u0431 \u043E\u0442\u043A\u0430\u0437\u0435 GitHub \u043F\u043E \u0447\u0435\u043A\u0430\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id} \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B:`, e);
17056
+ }
16881
17057
  }
16882
17058
  function ciWatchClosed(db, run) {
16883
17059
  return ciWatchCloseKind(db, run) != null;
@@ -16886,20 +17062,31 @@ var NO_CHECKS_STATUS = { bucket: "none", failing: [], failingRunIds: [], total:
16886
17062
  function ciWatchCloseKind(db, run) {
16887
17063
  const noChecks = ciWatchMark(CI_NO_CHECKS_MARK, run.review_cycle);
16888
17064
  const gaveUp = ciWatchMark(CI_POLL_LIMIT_MARK, run.review_cycle);
17065
+ const denied = ciWatchMark(CI_DENIED_STOP_MARK, run.review_cycle);
16889
17066
  for (const e of db.events(run.id)) {
16890
17067
  const text = e.text ?? "";
16891
17068
  if (text.includes(noChecks)) return { kind: "none", ci: NO_CHECKS_STATUS };
16892
17069
  if (text.includes(gaveUp)) return { kind: "gaveup" };
17070
+ if (text.includes(denied)) {
17071
+ const detail = deniedDetailFrom(text);
17072
+ return { kind: "denied", ...detail ? { detail } : {} };
17073
+ }
16893
17074
  }
16894
17075
  return null;
16895
17076
  }
17077
+ function deniedDetailFrom(text) {
17078
+ return text.match(/на чтение чек-ранов \(([^)]+)\)/)?.[1];
17079
+ }
16896
17080
  function ciPollStreak(db, runId) {
17081
+ return ciStreak(db, runId, CI_WAIT_POLL_MARK);
17082
+ }
17083
+ function ciStreak(db, runId, mark) {
16897
17084
  const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked" || isCiWatchClose(e.text ?? ""));
16898
17085
  let count = 0;
16899
17086
  let sinceMs = null;
16900
17087
  for (let i = evs.length - 1; i >= 0; i -= 1) {
16901
17088
  const e = evs[i];
16902
- if (e.kind !== "ci_checked" || !(e.text ?? "").includes(CI_WAIT_POLL_MARK)) break;
17089
+ if (e.kind !== "ci_checked" || !(e.text ?? "").includes(mark)) break;
16903
17090
  count += 1;
16904
17091
  sinceMs = sqliteMs(e.at);
16905
17092
  }
@@ -16954,6 +17141,10 @@ function ciReadyLabel(over) {
16954
17141
  // (isCiWatchClose/ciWatchCloseKind ищут его по подстроке), и событие о том, что
16955
17142
  // побудка ждёт слота, не должно выглядеть вторым решением о закрытии.
16956
17143
  "\u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0442\u0430\u043A \u0438 \u043D\u0435 \u0434\u043E\u0436\u0434\u0430\u043B\u0438\u0441\u044C, \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C CI"
17144
+ ) : over.kind === "denied" ? (
17145
+ // По той же причине не дословно CI_DENIED_STOP_MARK (TASWM-483) — но «403» в тексте
17146
+ // остаётся: по журналу должна быть видна причина, а не «проверок нет».
17147
+ "\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)"
16957
17148
  ) : `CI \u0437\u0435\u043B\u0451\u043D\u044B\u0439 (${over.ci.pass}/${over.ci.total})`;
16958
17149
  }
16959
17150
  async function wakeReviewerOnCiReady(deps, run, over) {
@@ -18384,8 +18575,8 @@ async function tickOnce(deps) {
18384
18575
  const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
18385
18576
  const ci = ciWatchOff ? null : await deps.ci.checks(run.pr_url).catch(() => null);
18386
18577
  const noCiRepo = ci?.bucket === "none" && !repoHasCi(deps, run);
18387
- if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none")) {
18388
- const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : `CI ${ci.pass}/${ci.total}`;
18578
+ if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied")) {
18579
+ const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : ci.bucket === "denied" ? `\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : `CI ${ci.pass}/${ci.total}`;
18389
18580
  deps.db.updateRun(run.id, { status: "paused", stop_reason: AWAITING_CI_REASON });
18390
18581
  deps.db.addEvent(run.id, "paused", `${AWAITING_CI_REASON} (${what}) \u2014 \u043D\u0435 \u0441\u043C\u0435\u0440\u0442\u044C: \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043A\u0438 \u0434\u043E\u0439\u0434\u0443\u0442 \u0434\u043E \u043A\u043E\u043D\u0446\u0430`);
18391
18582
  narrate(deps, `${droneLabel(run)} \u0443\u0448\u0451\u043B \u0436\u0434\u0430\u0442\u044C CI (${what}) \u2014 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u044E, \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A.`);
@@ -19466,7 +19657,18 @@ async function runDaemon(opts) {
19466
19657
  // TASWM-472: третья ось — ОЧЕРЕДЬ. Вливаем по одному и только на свежей базе; ридер
19467
19658
  // свежести ходит в gh (`compare/base...head` → behind_by). Ключа нет при
19468
19659
  // SWARM_MERGE_QUEUE=0 — тогда гейт отвечает ровно как до TASWM-472.
19469
- ...SWARM_MERGE_QUEUE ? { baseSync: (url) => prBaseSync(url), checks: (url) => prCiStatus(url) } : {}
19660
+ // TASWM-483: отказ по правам (403) очереди мержа приходит как «спросить не удалось» (null),
19661
+ // а не как отдельная ось: для НЕЁ это одно и то же — цвет проверок неизвестен, значит
19662
+ // вливать нельзя (checks_unknown, см. докблок computeMergeQueue про несимметричную цену).
19663
+ // Отличать 403 от таймаута нужно тому, кто ЖДЁТ и повторяет (handleCi), а очередь не ждёт.
19664
+ ...SWARM_MERGE_QUEUE ? {
19665
+ baseSync: (url) => prBaseSync(url),
19666
+ checks: async (url) => {
19667
+ const ci = await prCiStatus(url);
19668
+ if (ci == null || ci.bucket === "denied") return null;
19669
+ return { bucket: ci.bucket };
19670
+ }
19671
+ } : {}
19470
19672
  });
19471
19673
  const deps = {
19472
19674
  db,
@@ -19478,7 +19680,9 @@ async function runDaemon(opts) {
19478
19680
  const repoOfRun = normalizeRepo(run.repo || opts.repo);
19479
19681
  const taskClient = tlFor(run.workspace_id);
19480
19682
  const [rawTask, verifyMode, projectBase, zoneNotes] = await Promise.all([
19481
- taskClient ? taskClient.getTask(run.task_slug) : Promise.resolve(null),
19683
+ // TASWM-484: с причиной — на этом чтении стоит fail-loud ниже, и именно его
19684
+ // сообщение читает человек, когда дрон не поднялся.
19685
+ taskClient ? taskClient.getTaskDetailed(run.task_slug) : Promise.resolve(null),
19482
19686
  verifyModeReader ? verifyModeReader(repoOfRun) : Promise.resolve(void 0),
19483
19687
  baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve(""),
19484
19688
  // TASWM-466: память зоны прогона — тем же параллельным чтением, что и остальные
@@ -19735,7 +19939,7 @@ async function runDaemon(opts) {
19735
19939
  // fresh-оживление строит промпт с нуля, и без тела задачи это тот же слепой дрон.
19736
19940
  getTask: async (slug, ws) => {
19737
19941
  const client = tlFor(ws);
19738
- return client ? requireTaskBody(await client.getTask(slug), slug, ws) : null;
19942
+ return client ? requireTaskBody(await client.getTaskDetailed(slug), slug, ws) : null;
19739
19943
  },
19740
19944
  ...verifyModeReader ? { verifyMode: verifyModeReader } : {},
19741
19945
  // TASWM-466: тот же ридер памяти зоны, что на спавне — оживлённый дрон строит
@@ -20186,6 +20390,9 @@ export {
20186
20390
  CHANGES_WAKE_LIMIT,
20187
20391
  CHANGES_WAKE_MARK,
20188
20392
  CI_CHECK_COOLDOWN_MS,
20393
+ CI_DENIED_LIMIT,
20394
+ CI_DENIED_MARK,
20395
+ CI_DENIED_STOP_MARK,
20189
20396
  CI_NO_CHECKS_MARK,
20190
20397
  CI_POLL_LIMIT,
20191
20398
  CI_POLL_LIMIT_MARK,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.20",
3
+ "version": "0.6.21",
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": [
@@ -38,7 +38,7 @@
38
38
  "execa": "^10.0.0",
39
39
  "zod": "3.25.76"
40
40
  },
41
- "gitHead": "2d05a2e2b7c197ab916f7ff756e3a829b138df16",
41
+ "gitHead": "584411cbe69583a8727e89caa8ae7e626f8fd8ca",
42
42
  "scripts": {
43
43
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
44
44
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",