@taskless-app/swarm 0.6.20 → 0.6.22

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/overmind.js CHANGED
@@ -1850,10 +1850,24 @@ function asTokenUsage(m) {
1850
1850
  function budgetExhausted(result) {
1851
1851
  return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
1852
1852
  }
1853
+ function sessionAbort(result) {
1854
+ if (budgetExhausted(result)) return null;
1855
+ const reason = result.terminal_reason ?? "";
1856
+ const subtype = result.subtype ?? "";
1857
+ if (reason === "aborted_tools") return { kind: "tools", raw: reason };
1858
+ if (reason === "aborted_streaming" || reason === "api_error") return { kind: "link", raw: reason };
1859
+ if (HONEST_ENDINGS.has(subtype) || HONEST_ENDINGS.has(reason)) return null;
1860
+ const failed = result.is_error === true || subtype === "error_during_execution" || reason.startsWith("aborted");
1861
+ if (!failed) return null;
1862
+ const raw = reason && reason !== "completed" ? reason : subtype || reason || "\u043E\u0448\u0438\u0431\u043A\u0430 \u0431\u0435\u0437 \u043C\u0435\u0442\u043A\u0438";
1863
+ return { kind: "other", raw };
1864
+ }
1865
+ var HONEST_ENDINGS;
1853
1866
  var init_claudeResult = __esm({
1854
1867
  "src/claudeResult.ts"() {
1855
1868
  "use strict";
1856
1869
  init_pricing();
1870
+ HONEST_ENDINGS = /* @__PURE__ */ new Set(["error_max_turns"]);
1857
1871
  }
1858
1872
  });
1859
1873
 
@@ -1928,6 +1942,9 @@ function writeMcpConfig(spec) {
1928
1942
  writeFileSync2(path, JSON.stringify(cfg, null, 2));
1929
1943
  return path;
1930
1944
  }
1945
+ function abortField(abort) {
1946
+ return abort ? { aborted: abort } : {};
1947
+ }
1931
1948
  var ISOLATION_FLAGS, MCP_CONFIG_FILE, claudeCodeExecutor;
1932
1949
  var init_claudeCode = __esm({
1933
1950
  "src/executors/claudeCode.ts"() {
@@ -1966,7 +1983,10 @@ var init_claudeCode = __esm({
1966
1983
  durationMs: spend.durationMs,
1967
1984
  // TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
1968
1985
  // разбор файла: у claude он лежит в тех же полях, что деньги и токены.
1969
- ...budgetExhausted(result) ? { stoppedByBudget: true } : {}
1986
+ ...budgetExhausted(result) ? { stoppedByBudget: true } : {},
1987
+ // TASWM-487: и «оборвалось» — третий факт об этом же итоге. Порядок важен: потолок
1988
+ // трат sessionAbort отсекает сам, поэтому оба ключа одновременно не появятся.
1989
+ ...abortField(sessionAbort(result))
1970
1990
  };
1971
1991
  },
1972
1992
  session: {
@@ -2888,11 +2908,11 @@ var init_heartbeat = __esm({
2888
2908
  });
2889
2909
 
2890
2910
  // src/agentToken.ts
2891
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
2892
- import { join as join9 } from "path";
2911
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
2912
+ import { join as join8 } from "path";
2893
2913
  function loadAgentToken() {
2894
2914
  try {
2895
- const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
2915
+ const s = JSON.parse(readFileSync6(STORE_PATH, "utf8"));
2896
2916
  return s.token ? s : null;
2897
2917
  } catch {
2898
2918
  return null;
@@ -2945,7 +2965,7 @@ var init_agentToken = __esm({
2945
2965
  "src/agentToken.ts"() {
2946
2966
  "use strict";
2947
2967
  init_config();
2948
- STORE_PATH = join9(SWARM_HOME, "agent.json");
2968
+ STORE_PATH = join8(SWARM_HOME, "agent.json");
2949
2969
  base = (api) => api.replace(/\/$/, "");
2950
2970
  TOKEN_PROBE_TIMEOUT_MS = 8e3;
2951
2971
  }
@@ -3533,7 +3553,7 @@ async function remoteMainRef(repo) {
3533
3553
  throw new Error(`\u043D\u0435 \u043D\u0430\u0448\u0451\u043B \u0434\u0435\u0444\u043E\u043B\u0442-\u0432\u0435\u0442\u043A\u0443 origin \u0432 ${repo} (\u043D\u0435\u0442 origin/HEAD, origin/main, origin/master)`);
3534
3554
  }
3535
3555
  async function remoteBranchExists(repo, branch) {
3536
- const res = await $2({ reject: false })`git -C ${repo} ls-remote --heads origin ${`refs/heads/${branch}`}`;
3556
+ const res = await $2({ reject: false, timeout: 5e3 })`git -C ${repo} ls-remote --heads origin ${`refs/heads/${branch}`}`;
3537
3557
  if (res.exitCode !== 0) return null;
3538
3558
  return res.stdout.trim().length > 0;
3539
3559
  }
@@ -4527,6 +4547,78 @@ var init_oauth = __esm({
4527
4547
  }
4528
4548
  });
4529
4549
 
4550
+ // src/toolFailure.ts
4551
+ function trimDetail(s) {
4552
+ const t = s.trim();
4553
+ return t.length <= FAILURE_DETAIL_MAX ? t : `${t.slice(0, FAILURE_DETAIL_MAX)}\u2026`;
4554
+ }
4555
+ function classifyToolFailure(raw) {
4556
+ if (MISMATCH_RE.test(raw)) return TOOL_FAILURE.WORKSPACE_MISMATCH;
4557
+ if (FORBIDDEN_RE.test(raw)) return TOOL_FAILURE.FORBIDDEN;
4558
+ if (NOT_FOUND_RE.test(raw)) return TOOL_FAILURE.NOT_FOUND;
4559
+ return TOOL_FAILURE.TOOL_ERROR;
4560
+ }
4561
+ function classifyThrown(e) {
4562
+ const { kind, detail } = explainLinkFailure(e);
4563
+ const code = kind === "offline" ? TOOL_FAILURE.UNREACHABLE : kind === "unauthorized" ? TOOL_FAILURE.UNAUTHORIZED : (
4564
+ // «ручки нет» и «сервер отказал по существу» разбираем доменно: там может быть
4565
+ // и task_not_found, и отказ по правам — то есть наши коды, а не транспортные.
4566
+ classifyToolFailure(detail)
4567
+ );
4568
+ return { code, detail };
4569
+ }
4570
+ function describeToolFailure(f) {
4571
+ const text = TOOL_FAILURE_TEXT[f.code];
4572
+ const detail = trimDetail(f.detail);
4573
+ return detail ? `${text} (${detail})` : text;
4574
+ }
4575
+ var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE, FAILURE_DETAIL_MAX, NOT_FOUND_RE, FORBIDDEN_RE, MISMATCH_RE;
4576
+ var init_toolFailure = __esm({
4577
+ "src/toolFailure.ts"() {
4578
+ "use strict";
4579
+ init_linkFailure();
4580
+ TOOL_FAILURE = {
4581
+ /** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
4582
+ NOT_FOUND: "not_found",
4583
+ /** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
4584
+ FORBIDDEN: "forbidden",
4585
+ /** Токен роя не состоит в воркспейсе, которым искали. */
4586
+ WORKSPACE_MISMATCH: "workspace_mismatch",
4587
+ /** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
4588
+ UNREACHABLE: "unreachable",
4589
+ /** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
4590
+ UNAUTHORIZED: "unauthorized",
4591
+ /** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
4592
+ TOOL_ERROR: "tool_error",
4593
+ /** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
4594
+ EMPTY: "empty"
4595
+ };
4596
+ TOOL_FAILURE_TEXT = {
4597
+ [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",
4598
+ [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",
4599
+ [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",
4600
+ [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",
4601
+ [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",
4602
+ [TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
4603
+ [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"
4604
+ };
4605
+ TOOL_FAILURE_RETRIABLE = {
4606
+ [TOOL_FAILURE.NOT_FOUND]: false,
4607
+ [TOOL_FAILURE.FORBIDDEN]: false,
4608
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
4609
+ [TOOL_FAILURE.UNREACHABLE]: true,
4610
+ // Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
4611
+ [TOOL_FAILURE.UNAUTHORIZED]: false,
4612
+ [TOOL_FAILURE.TOOL_ERROR]: false,
4613
+ [TOOL_FAILURE.EMPTY]: false
4614
+ };
4615
+ FAILURE_DETAIL_MAX = 300;
4616
+ NOT_FOUND_RE = /task_not_found|not_found|не найдена|не найден/i;
4617
+ FORBIDDEN_RE = /forbidden|not visible|no access|нет доступа|(?:http|status|code)[ =:]*403\b|\(403\)|403 forbidden/i;
4618
+ MISMATCH_RE = /workspace_mismatch|not a member of that workspace/i;
4619
+ }
4620
+ });
4621
+
4530
4622
  // src/authState.ts
4531
4623
  function noteTokenRejected(nowMs = Date.now()) {
4532
4624
  if (!denial) {
@@ -4623,25 +4715,44 @@ function isClosingTaskCategory(category) {
4623
4715
  function taskAuthor(u) {
4624
4716
  return u?.id ? { id: u.id, name: u.displayName || "\u0430\u0432\u0442\u043E\u0440 \u0437\u0430\u0434\u0430\u0447\u0438" } : null;
4625
4717
  }
4718
+ function toolErrorText(r) {
4719
+ const fromContent = (r.content ?? []).map((c) => c.text ?? "").filter(Boolean).join(" ").trim();
4720
+ return fromContent || JSON.stringify(r);
4721
+ }
4626
4722
  function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl = fetch) {
4627
4723
  const ws = workspaceId ? { workspaceId } : {};
4628
4724
  const idCache = /* @__PURE__ */ new Map();
4629
4725
  const byWorkspace = /* @__PURE__ */ new Map();
4630
- async function safeCall(name, args) {
4726
+ async function callTool(name, args) {
4631
4727
  try {
4632
4728
  const r = await call(name, WS_FREE_TOOLS.has(name) ? args : { ...args, ...ws });
4633
4729
  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;
4730
+ const raw = toolErrorText(r);
4731
+ const detail = trimDetail(raw);
4732
+ console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, detail);
4733
+ return { ok: false, failure: { code: classifyToolFailure(raw), detail } };
4636
4734
  }
4637
- return r;
4735
+ return { ok: true, res: r };
4638
4736
  } catch (e) {
4639
- console.error(`[taskless] \u0442\u0443\u043B ${name} \u0443\u043F\u0430\u043B:`, e instanceof Error ? e.message : String(e));
4640
- return null;
4737
+ const failure = classifyThrown(e);
4738
+ console.error(`[taskless] \u0442\u0443\u043B ${name} \u0443\u043F\u0430\u043B:`, failure.detail);
4739
+ return { ok: false, failure };
4641
4740
  }
4642
4741
  }
4643
- async function getTask(idOrSlug) {
4644
- const res = await safeCall("get_task", { id: idOrSlug });
4742
+ async function safeCall(name, args) {
4743
+ const out = await callTool(name, args);
4744
+ return out.ok ? out.res : null;
4745
+ }
4746
+ async function getTaskDetailed(idOrSlug) {
4747
+ const out = await callTool("get_task", { id: idOrSlug });
4748
+ if (!out.ok) return { ok: false, failure: out.failure };
4749
+ const body = taskBodyFrom(out.res, idOrSlug);
4750
+ if (!body) {
4751
+ 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" } };
4752
+ }
4753
+ return { ok: true, task: body };
4754
+ }
4755
+ function taskBodyFrom(res, idOrSlug) {
4645
4756
  const task = res?.structuredContent?.task ?? null;
4646
4757
  if (!task?.id) return null;
4647
4758
  const slug = task.prefix && task.number != null ? `${task.prefix}-${task.number}` : idOrSlug;
@@ -4671,6 +4782,9 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4671
4782
  idCache.set(body.id, body.id);
4672
4783
  return body;
4673
4784
  }
4785
+ async function getTask(idOrSlug) {
4786
+ return taskBodyFrom(await safeCall("get_task", { id: idOrSlug }), idOrSlug);
4787
+ }
4674
4788
  async function getDocument(id) {
4675
4789
  const res = await safeCall("get_document", { id });
4676
4790
  const doc = res?.structuredContent?.document ?? null;
@@ -4762,6 +4876,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4762
4876
  }
4763
4877
  const self = {
4764
4878
  getTask,
4879
+ getTaskDetailed,
4765
4880
  ...fetchFile ? { fetchFile } : {},
4766
4881
  startTask: (idOrSlug) => transition("start_task", idOrSlug),
4767
4882
  // TASWM-294: intent — «человек, посмотри», а не «Готово» (см. докблок в TasklessClient).
@@ -4857,6 +4972,10 @@ function parseWorkspaces(res) {
4857
4972
  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
4973
  }
4859
4974
  function requireTaskBody(task, slug, workspaceId) {
4975
+ if (task && "ok" in task) {
4976
+ if (task.ok) return task.task;
4977
+ throw new TaskBodyUnavailableError(slug, workspaceId, task.failure);
4978
+ }
4860
4979
  if (!task) throw new TaskBodyUnavailableError(slug, workspaceId);
4861
4980
  return task;
4862
4981
  }
@@ -5088,16 +5207,25 @@ var init_taskless = __esm({
5088
5207
  init_oauth();
5089
5208
  init_agentToken();
5090
5209
  init_linkFailure();
5210
+ init_toolFailure();
5091
5211
  init_authState();
5092
5212
  init_workspace();
5093
5213
  SLUG_RE = /^[A-Za-z][A-Za-z0-9]*-\d+$/;
5094
5214
  WS_FREE_TOOLS = /* @__PURE__ */ new Set(["list_workspaces", "who_am_i", "finalize_upload", "report_acceptance_check"]);
5095
5215
  TaskBodyUnavailableError = class extends Error {
5096
- constructor(slug, workspaceId) {
5216
+ /** Код причины — по нему решают, повторять ли (TOOL_FAILURE_RETRIABLE), а не по тексту. */
5217
+ code;
5218
+ // TASWM-484: причина называется, а не перечисляется. Прежний текст («задачи там нет
5219
+ // ЛИБО токену её не видно») заставлял человека проверять обе версии руками — 21.08 на
5220
+ // этом ушло полдня, и обе оказались не теми. Теперь код приходит от классификатора
5221
+ // ответа прода, а человеку достаётся объяснение и то, что прод сказал дословно.
5222
+ constructor(slug, workspaceId, failure) {
5223
+ const f = failure ?? { code: TOOL_FAILURE.EMPTY, detail: "" };
5097
5224
  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).`
5225
+ `\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
5226
  );
5100
5227
  this.name = "TaskBodyUnavailableError";
5228
+ this.code = f.code;
5101
5229
  }
5102
5230
  };
5103
5231
  TASK_CONTEXT_MAX_DOCS = 5;
@@ -8773,6 +8901,80 @@ function rotateDaemonLog(path, opts, io = realIo) {
8773
8901
  return "rotated";
8774
8902
  }
8775
8903
 
8904
+ // src/logStamp.ts
8905
+ import { format } from "util";
8906
+ function logStamp(at) {
8907
+ return at.toISOString();
8908
+ }
8909
+ function stampMessage(text, at) {
8910
+ const ts = logStamp(at);
8911
+ return text.split("\n").map((line) => line.length ? `${ts} ${line}` : ts).join("\n");
8912
+ }
8913
+ function utcOffsetText(at) {
8914
+ const minutes = -at.getTimezoneOffset();
8915
+ const sign2 = minutes < 0 ? "-" : "+";
8916
+ const abs = Math.abs(minutes);
8917
+ const pad = (n) => String(n).padStart(2, "0");
8918
+ return `UTC${sign2}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
8919
+ }
8920
+ function localZoneLine(at, zone) {
8921
+ const tz = zone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
8922
+ return `[overmind] \u0432\u0440\u0435\u043C\u044F \u0432 \u043B\u043E\u0433\u0435 \u2014 UTC, ISO-8601 \u0441 \u043C\u0438\u043B\u043B\u0438\u0441\u0435\u043A\u0443\u043D\u0434\u0430\u043C\u0438 (${logStamp(at)}); \u043C\u0435\u0441\u0442\u043D\u043E\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 ${utcOffsetText(at)}${tz ? ` (${tz})` : ""}`;
8923
+ }
8924
+ var LEVELS = ["log", "info", "warn", "error"];
8925
+ var installed = false;
8926
+ function installLogStamps(now = () => /* @__PURE__ */ new Date()) {
8927
+ if (installed) return () => {
8928
+ };
8929
+ installed = true;
8930
+ const original = LEVELS.map((level) => [level, console[level]]);
8931
+ for (const [level, orig] of original) {
8932
+ const write = orig.bind(console);
8933
+ console[level] = (...args) => {
8934
+ write(stampMessage(format(...args), now()));
8935
+ };
8936
+ }
8937
+ return () => {
8938
+ for (const [level, orig] of original) console[level] = orig;
8939
+ installed = false;
8940
+ };
8941
+ }
8942
+
8943
+ // src/logNoise.ts
8944
+ var DEFAULT_REMIND_MS = 10 * 6e4;
8945
+ function timesWord(n) {
8946
+ const tens = n % 100;
8947
+ if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
8948
+ return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
8949
+ }
8950
+ function makeRepeatFilter(opts = {}) {
8951
+ const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
8952
+ const streaks = /* @__PURE__ */ new Map();
8953
+ return {
8954
+ say(key, message, now) {
8955
+ const prev = streaks.get(key);
8956
+ if (!prev || prev.message !== message) {
8957
+ const tail2 = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
8958
+ streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
8959
+ return `${message}${tail2}`;
8960
+ }
8961
+ prev.hidden += 1;
8962
+ if (now - prev.saidAt < remindAfterMs) return null;
8963
+ const hidden = prev.hidden;
8964
+ const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
8965
+ prev.saidAt = now;
8966
+ prev.hidden = 0;
8967
+ return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
8968
+ },
8969
+ forget(key) {
8970
+ streaks.delete(key);
8971
+ }
8972
+ };
8973
+ }
8974
+
8975
+ // src/overmind.ts
8976
+ init_linkFailure();
8977
+
8776
8978
  // src/repoRegistry.ts
8777
8979
  init_config();
8778
8980
  import { $ } from "execa";
@@ -8811,13 +9013,13 @@ async function buildRepoRegistry(paths) {
8811
9013
  }
8812
9014
 
8813
9015
  // src/repoMemory.ts
8814
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
8815
- import { join as join8 } from "path";
9016
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
9017
+ import { join as join9 } from "path";
8816
9018
  var FILE = "repos.json";
8817
9019
  function loadRememberedRepos(home) {
8818
9020
  let raw;
8819
9021
  try {
8820
- raw = readFileSync6(join8(home, FILE), "utf8");
9022
+ raw = readFileSync7(join9(home, FILE), "utf8");
8821
9023
  } catch {
8822
9024
  return [];
8823
9025
  }
@@ -8832,7 +9034,7 @@ function loadRememberedRepos(home) {
8832
9034
  function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
8833
9035
  const clean = paths.filter((p) => p.trim().length > 0);
8834
9036
  if (clean.length === 0) return;
8835
- writeFileSync6(join8(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
9037
+ writeFileSync7(join9(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
8836
9038
  `);
8837
9039
  }
8838
9040
  function pickRepoPaths(args) {
@@ -9077,9 +9279,13 @@ async function refreshRepoMap(opts) {
9077
9279
  added++;
9078
9280
  }
9079
9281
  scannedAt = stamp();
9080
- log(
9081
- `[overmind] \u043F\u0440\u043E\u0448\u0451\u043B \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C (${SCAN_REASON_TEXT[decision.reason]}): ${added > 0 ? `\u043D\u0430\u0448\u0451\u043B \u0435\u0449\u0451 ${added}` : "\u043D\u043E\u0432\u043E\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u0451\u043B"}, \u0432\u0441\u0435\u0433\u043E \u043A\u043B\u043E\u043D\u043E\u0432 ${clones.size}, \u0437\u0430 ${Date.now() - startedAt}\u043C\u0441. \u0418\u0441\u043A\u0430\u043B \u0432: ${opts.roots.length ? opts.roots.slice(0, 6).join(", ") : "\u043D\u0438\u0433\u0434\u0435 \u2014 \u043F\u043E\u0438\u0441\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)"}`
9082
- );
9282
+ const outcome = `[overmind] \u043F\u0440\u043E\u0448\u0451\u043B \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C (${SCAN_REASON_TEXT[decision.reason]}): ${added > 0 ? `\u043D\u0430\u0448\u0451\u043B \u0435\u0449\u0451 ${added}` : "\u043D\u043E\u0432\u043E\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u0451\u043B"}, \u0432\u0441\u0435\u0433\u043E \u043A\u043B\u043E\u043D\u043E\u0432 ${clones.size}`;
9283
+ if (added > 0 || opts.state.announcedScan !== outcome) {
9284
+ opts.state.announcedScan = outcome;
9285
+ log(
9286
+ `${outcome}, \u0437\u0430 ${Date.now() - startedAt}\u043C\u0441. \u0418\u0441\u043A\u0430\u043B \u0432: ${opts.roots.length ? opts.roots.slice(0, 6).join(", ") : "\u043D\u0438\u0433\u0434\u0435 \u2014 \u043F\u043E\u0438\u0441\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)"}`
9287
+ );
9288
+ }
9083
9289
  }
9084
9290
  const registry = [...clones.values()].map((c) => ({
9085
9291
  repoFullName: c.repoFullName,
@@ -9091,8 +9297,10 @@ async function refreshRepoMap(opts) {
9091
9297
  scannedAt
9092
9298
  );
9093
9299
  if (!answer.ok) {
9094
- log(describeReposFailure(answer.kind, answer.detail));
9095
- log(`[overmind] \u0438\u0442\u043E\u0433: \u043A\u043B\u043E\u043D\u043E\u0432 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E ${clones.size}, \u0441\u043F\u0438\u0441\u043E\u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0440\u043E\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D (\u0441\u043C. \u0441\u0442\u0440\u043E\u043A\u0443 \u0432\u044B\u0448\u0435)`);
9300
+ announceMap(opts.state, log, [
9301
+ describeReposFailure(answer.kind, answer.detail),
9302
+ `[overmind] \u0438\u0442\u043E\u0433: \u043A\u043B\u043E\u043D\u043E\u0432 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E ${clones.size}, \u0441\u043F\u0438\u0441\u043E\u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0440\u043E\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D (\u0441\u043C. \u0441\u0442\u0440\u043E\u043A\u0443 \u0432\u044B\u0448\u0435)`
9303
+ ]);
9096
9304
  return registry;
9097
9305
  }
9098
9306
  opts.state.unknown = false;
@@ -9101,18 +9309,39 @@ async function refreshRepoMap(opts) {
9101
9309
  opts.state.roots = [...opts.roots];
9102
9310
  opts.state.at = stamp();
9103
9311
  if (wantedNames.length === 0) {
9104
- log("[overmind] \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u043C \u0440\u043E\u0435\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435");
9312
+ announceMap(opts.state, log, ["[overmind] \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u043C \u0440\u043E\u0435\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435"]);
9105
9313
  return registry;
9106
9314
  }
9107
- log(`[overmind] ${describeMapSummary(opts.state, opts.roots)}`);
9108
- for (const c of opts.state.clones) log(`[overmind] ${describeClone(c)}`);
9109
9315
  const prefixOf = new Map(answer.repos.map((r) => [r.repoFullName, r.taskPrefix]));
9110
- for (const name of opts.state.missing) {
9111
- const prefix = prefixOf.get(name);
9112
- log(`[overmind] ${describeMissing(prefix ? `${name} (\u043F\u0440\u043E\u0435\u043A\u0442 ${prefix})` : name, opts.roots)}`);
9113
- }
9316
+ announceMap(
9317
+ opts.state,
9318
+ log,
9319
+ [
9320
+ `[overmind] ${describeMapSummary(opts.state, opts.roots)}`,
9321
+ ...opts.state.clones.map((c) => `[overmind] ${describeClone(c)}`),
9322
+ ...opts.state.missing.map((name) => {
9323
+ const prefix = prefixOf.get(name);
9324
+ return `[overmind] ${describeMissing(prefix ? `${name} (\u043F\u0440\u043E\u0435\u043A\u0442 ${prefix})` : name, opts.roots)}`;
9325
+ })
9326
+ ],
9327
+ mapSignature(opts.state, opts.roots)
9328
+ );
9114
9329
  return registry;
9115
9330
  }
9331
+ function announceMap(state, log, lines, signature = lines.join("\n")) {
9332
+ if (state.announced === signature) return;
9333
+ const first = state.announced === void 0;
9334
+ state.announced = signature;
9335
+ if (!first) log("[overmind] \u043A\u0430\u0440\u0442\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C:");
9336
+ for (const line of lines) log(line);
9337
+ }
9338
+ function mapSignature(state, roots) {
9339
+ return [
9340
+ ...state.clones.map((c) => `${c.repoFullName}=${c.path}${c.alternatives.length ? `+${c.alternatives.join("+")}` : ""}`),
9341
+ `missing:${state.missing.join(",")}`,
9342
+ `roots:${roots.join(",")}`
9343
+ ].join("\n");
9344
+ }
9116
9345
  function describeMapSummary(state, roots) {
9117
9346
  const found = state.clones.length;
9118
9347
  const where = roots.length ? roots.slice(0, 6).join(", ") : "\u043F\u043E\u0438\u0441\u043A \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)";
@@ -9809,17 +10038,20 @@ import { readdirSync as readdirSync10, readFileSync as readFileSync12 } from "fs
9809
10038
  import { join as join16 } from "path";
9810
10039
  init_executors();
9811
10040
  function endedItself(outcome) {
9812
- return outcome === "graceful" || outcome === "budget";
10041
+ return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
10042
+ }
10043
+ function wasAborted(outcome) {
10044
+ return outcome === "aborted";
9813
10045
  }
9814
10046
  var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
9815
- function classifyExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
10047
+ function readLatestResultRaw(runLogsDir, runLogKeys) {
9816
10048
  const dir = findRunLogDir(runLogsDir, runLogKeys);
9817
- if (!dir) return "unknown";
10049
+ if (!dir) return null;
9818
10050
  let files;
9819
10051
  try {
9820
10052
  files = readdirSync10(dir);
9821
10053
  } catch {
9822
- return "unknown";
10054
+ return null;
9823
10055
  }
9824
10056
  let latest = null;
9825
10057
  for (const f of files) {
@@ -9828,21 +10060,35 @@ function classifyExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
9828
10060
  const idx = Number(m[1]);
9829
10061
  if (!latest || idx > latest.idx) latest = { idx, name: f };
9830
10062
  }
9831
- if (!latest) return "unknown";
9832
- let raw;
10063
+ if (!latest) return null;
9833
10064
  try {
9834
- raw = readFileSync12(join16(dir, latest.name), "utf8");
10065
+ return readFileSync12(join16(dir, latest.name), "utf8");
9835
10066
  } catch {
9836
- return "unknown";
10067
+ return null;
9837
10068
  }
10069
+ }
10070
+ function classifyExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
10071
+ const raw = readLatestResultRaw(runLogsDir, runLogKeys);
10072
+ if (raw == null) return "unknown";
9838
10073
  const result = executor.parseResult(raw, null);
9839
10074
  if (!result) return "crash";
9840
- return result.stoppedByBudget ? "budget" : "graceful";
10075
+ if (result.stoppedByBudget) return "budget";
10076
+ if (result.aborted) return "aborted";
10077
+ return "graceful";
10078
+ }
10079
+ function abortOfExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
10080
+ const raw = readLatestResultRaw(runLogsDir, runLogKeys);
10081
+ if (raw == null) return null;
10082
+ return executor.parseResult(raw, null)?.aborted ?? null;
9841
10083
  }
9842
10084
  function makeExitClassifier(runLogsDir, runLogKeys, executor) {
9843
10085
  const exec = executor ?? resolveExecutor();
9844
10086
  return (run) => classifyExit(runLogsDir, runLogKeys(run.id), exec);
9845
10087
  }
10088
+ function makeAbortReader(runLogsDir, runLogKeys, executor) {
10089
+ const exec = executor ?? resolveExecutor();
10090
+ return (run) => abortOfExit(runLogsDir, runLogKeys(run.id), exec);
10091
+ }
9846
10092
 
9847
10093
  // src/orchestrator.ts
9848
10094
  init_src();
@@ -10768,10 +11014,24 @@ function fullCommitHeadline(headline, body) {
10768
11014
  return head.slice(0, -1) + tail2;
10769
11015
  }
10770
11016
  var NO_CHECKS_STDERR = /no checks reported/i;
11017
+ var CiAccessDeniedError = class extends Error {
11018
+ detail;
11019
+ constructor(detail) {
11020
+ super(`\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C: ${detail}`);
11021
+ this.name = "CiAccessDeniedError";
11022
+ this.detail = detail;
11023
+ }
11024
+ };
11025
+ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have admin rights|resource not accessible/i;
11026
+ function firstLine(s) {
11027
+ return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
11028
+ }
10771
11029
  var realChecksRunner = async (pr) => {
10772
11030
  const r = await $3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
10773
11031
  const stdout = r.stdout ?? "";
10774
- if (!stdout.trim() && NO_CHECKS_STDERR.test(r.stderr ?? "")) return "[]";
11032
+ const stderr = r.stderr ?? "";
11033
+ if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
11034
+ if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
10775
11035
  return stdout;
10776
11036
  };
10777
11037
  async function prCheckRows(pr, run) {
@@ -10780,13 +11040,17 @@ async function prCheckRows(pr, run) {
10780
11040
  const rows = JSON.parse(out);
10781
11041
  if (!Array.isArray(rows)) return null;
10782
11042
  return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
10783
- } catch {
11043
+ } catch (e) {
11044
+ if (e instanceof CiAccessDeniedError) return { denied: e.detail };
10784
11045
  return null;
10785
11046
  }
10786
11047
  }
11048
+ function isDenied(rows) {
11049
+ return rows != null && !Array.isArray(rows);
11050
+ }
10787
11051
  async function ghChecks(pr, run = realChecksRunner) {
10788
11052
  const rows = await prCheckRows(pr, run);
10789
- if (!rows || rows.length === 0) return null;
11053
+ if (!rows || isDenied(rows) || rows.length === 0) return null;
10790
11054
  let pass = 0, fail = 0, pending2 = 0;
10791
11055
  for (const r of rows) {
10792
11056
  if (r.bucket === "pass") pass++;
@@ -10817,6 +11081,7 @@ function runIdFromLink(link) {
10817
11081
  async function prCiStatus(pr, run = realChecksRunner) {
10818
11082
  const rows = await prCheckRows(pr, run);
10819
11083
  if (!rows) return null;
11084
+ if (isDenied(rows)) return { bucket: "denied", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, detail: rows.denied };
10820
11085
  if (rows.length === 0) return { bucket: "none", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0 };
10821
11086
  const total = rows.length;
10822
11087
  const pass = rows.filter((r) => r.bucket === "pass").length;
@@ -13312,7 +13577,7 @@ async function changedPaths(worktree, opts = {}) {
13312
13577
  // package.json
13313
13578
  var package_default = {
13314
13579
  name: "@taskless-app/swarm",
13315
- version: "0.6.20",
13580
+ version: "0.6.22",
13316
13581
  type: "module",
13317
13582
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13318
13583
  keywords: [
@@ -13362,7 +13627,7 @@ var package_default = {
13362
13627
  execa: "^10.0.0",
13363
13628
  zod: "3.25.76"
13364
13629
  },
13365
- gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
13630
+ gitHead: "b8eaac4a5562ffcf45a02af338267620e0a7843f"
13366
13631
  };
13367
13632
 
13368
13633
  // src/version.ts
@@ -15218,9 +15483,9 @@ async function runSelfUpdate(io) {
15218
15483
  if (!exists(join28(repo, "pnpm-lock.yaml"))) {
15219
15484
  return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
15220
15485
  }
15221
- const installed = await run("pnpm", ["install", "--frozen-lockfile"], repo);
15222
- if (installed.exitCode !== 0) {
15223
- return { ok: false, step: "install", message: `pnpm install \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B: ${tail(installed.stderr || installed.stdout)}` };
15486
+ const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
15487
+ if (installed2.exitCode !== 0) {
15488
+ return { ok: false, step: "install", message: `pnpm install \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B: ${tail(installed2.stderr || installed2.stdout)}` };
15224
15489
  }
15225
15490
  await io.onStep("build", "\u0441\u043E\u0431\u0438\u0440\u0430\u044E \u043F\u0430\u043A\u0435\u0442\u044B \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430");
15226
15491
  const built = await run("pnpm", ["--filter", `${DAEMON_PACKAGE}...`, "build"], repo);
@@ -15843,9 +16108,24 @@ var executorHasSession = (p) => EXECUTOR.session.exists(p);
15843
16108
  var OVERMIND_ENTRY = THIS_FILE;
15844
16109
  var STALL_MS = Number(process.env.SWARM_STALL_MS) || 10 * 60 * 1e3;
15845
16110
  var STALL_FLOOR_MS = Number(process.env.SWARM_STALL_FLOOR_MS) || 30 * 60 * 1e3;
16111
+ var DEAD_HOLDER_SILENCE_MS = Number(process.env.SWARM_DEAD_HOLDER_SILENCE_MS) || 3 * 60 * 60 * 1e3;
15846
16112
  var RESPAWN_LIMIT = Number(process.env.SWARM_RESPAWN_LIMIT) || 2;
15847
16113
  var RESPAWN_YOUNG_MS = Number(process.env.SWARM_RESPAWN_YOUNG_MS) || 60 * 1e3;
15848
- var EXITED_NO_DONE_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D \u2014 worktree \u0446\u0435\u043B, \u0440\u0430\u0431\u043E\u0442\u0430 \u0441\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u0430";
16114
+ var EXITED_NO_DONE_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D";
16115
+ var ABORTED_STOP_REASON = "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D";
16116
+ var EXIT_WORK_PUSHED = "\u0432\u0435\u0442\u043A\u0430 \u0435\u0441\u0442\u044C \u043D\u0430 origin \u2014 \u0435\u0441\u0442\u044C \u0447\u0442\u043E \u0441\u043F\u0430\u0441\u0430\u0442\u044C, PR \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0442";
16117
+ var EXIT_WORK_LOST = "\u0432\u0435\u0442\u043A\u0438 \u043D\u0430 origin \u043D\u0435\u0442 \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u043E";
16118
+ var EXIT_WORK_UNKNOWN = "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C origin \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u2014 \u0446\u0435\u043B\u0430 \u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430, \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E";
16119
+ var ABORT_TEXT = {
16120
+ tools: "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u043D\u0430 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 \u2014 \u043E\u0431\u044B\u0447\u043D\u043E \u044D\u0442\u043E \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0441 \u0433\u0438\u0433\u0430\u043D\u0442\u0441\u043A\u0438\u043C \u0432\u044B\u0432\u043E\u0434\u043E\u043C (\u043F\u043E\u043B\u043D\u044B\u0439 \u0441\u044C\u044E\u0442 \u0442\u0435\u0441\u0442\u043E\u0432, \u043B\u043E\u0433 \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430): \u043E\u0442\u0432\u0435\u0442 \u0442\u0443\u043B\u0430 \u043D\u0435 \u0432\u043B\u0435\u0437\u0430\u0435\u0442 \u0438 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u043F\u0443\u0441\u0442\u044B\u043C",
16121
+ link: "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u043D\u0430 \u0441\u0432\u044F\u0437\u0438 \u0441 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u043C (\u0441\u0442\u0440\u0438\u043C/\u043E\u0448\u0438\u0431\u043A\u0430 API) \u2014 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C",
16122
+ other: "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F"
16123
+ };
16124
+ function abortReason(abort) {
16125
+ if (!abort) return "\u0441\u0435\u0441\u0441\u0438\u044F \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C";
16126
+ return `${ABORT_TEXT[abort.kind]} (${abort.raw})`;
16127
+ }
16128
+ var ABORTED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u041E\u0411\u041E\u0420\u0412\u0410\u041B\u0410\u0421\u042C \u043D\u0430 \u043F\u043E\u043B\u0443\u0441\u043B\u043E\u0432\u0435 (\u043D\u0435 \u0442\u044B \u0435\u0451 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B). \u0422\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log`: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0438 \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439. \u0412\u0410\u0416\u041D\u041E \u043F\u0440\u043E \u043E\u0431\u0440\u044B\u0432\u044B: \u0440\u0432\u0451\u0442\u0441\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0430 \u041E\u0422\u0412\u0415\u0422\u0415 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430, \u0447\u0435\u0439 \u0432\u044B\u0432\u043E\u0434 \u043E\u0433\u0440\u043E\u043C\u0435\u043D (\u043F\u043E\u043B\u043D\u044B\u0439 \u0441\u044C\u044E\u0442 \u0442\u0435\u0441\u0442\u043E\u0432, `git log` \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430, \u0434\u0430\u043C\u043F \u0444\u0430\u0439\u043B\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C). \u0413\u043D\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430\u0434\u043E \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \u2014 \u043D\u043E \u0432\u044B\u0432\u043E\u0434 \u043F\u0438\u0448\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0444\u0430\u0439\u043B (`LOG=$(mktemp -t suite)`) \u0438 \u0431\u0435\u0440\u0438 \u0438\u0437 \u043D\u0435\u0433\u043E \u0445\u0432\u043E\u0441\u0442 \u0438 \u0438\u0442\u043E\u0433\u043E\u0432\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\u044B\u043D\u044E. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).";
15849
16129
  var EXITED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0421\u0410\u041C\u0410, \u043D\u043E \u0442\u044B \u0442\u0430\u043A \u0438 \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043B done(pr_url) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0443\u0432\u0438\u0434\u0435\u043B \u0441\u0434\u0430\u0447\u0438. \u0422\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log`: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0438 \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR (\u0435\u0441\u043B\u0438 \u0435\u0449\u0451 \u043D\u0435\u0442) \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url) \u2014 \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0441\u0434\u0430\u043D\u043D\u043E\u0439. \u0423\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0432 \u043B\u0438\u043C\u0438\u0442/\u043D\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C \u2014 \u043D\u0435 \u043C\u043E\u043B\u043E\u0442\u0438 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0437\u0430\u0434\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0447\u0435\u0440\u0435\u0437 MCP-\u0442\u0443\u043B ask. \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439.";
15850
16130
  var STORM_DEAD_LIMIT = Number(process.env.SWARM_STORM_DEAD_LIMIT) || 3;
15851
16131
  var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS) || 3 * 60 * 1e3;
@@ -15866,6 +16146,9 @@ var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u04
15866
16146
  var CI_NO_CHECKS_MARK = "\u0443 PR \u043D\u0435\u0442 \u0447\u0435\u043A\u043E\u0432 \u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442";
15867
16147
  var CI_WAIT_POLL_MARK = "\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A";
15868
16148
  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";
16149
+ var CI_DENIED_LIMIT = Number(process.env.SWARM_CI_DENIED_LIMIT) || 3;
16150
+ var CI_DENIED_MARK = "\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C (403)";
16151
+ 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
16152
  var CI_WAIT_LIMIT_MS = Number(process.env.SWARM_CI_WAIT_LIMIT_MS) || 45 * 60 * 1e3;
15870
16153
  var CI_POLL_LIMIT = Math.max(
15871
16154
  Number(process.env.SWARM_CI_POLL_LIMIT) || 90,
@@ -15883,7 +16166,7 @@ var CHANGES_WAKE_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430
15883
16166
  var CHANGES_WAKE_ESCALATION_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C";
15884
16167
  var CHANGES_WAKE_FAILED_MARK = "\u043F\u043E\u0431\u0443\u0434\u043A\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C";
15885
16168
  var BUDGET_STOP_REASON = "\u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442: \u0441\u0435\u0441\u0441\u0438\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u0431\u044E\u0434\u0436\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430";
15886
- var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON];
16169
+ var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON, ABORTED_STOP_REASON];
15887
16170
  var REVIEW_BUDGET_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u043E\u0441\u044C \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442";
15888
16171
  function reviewBudgetMark(cycle) {
15889
16172
  return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
@@ -16363,8 +16646,13 @@ function buildCiWakeNudge(summary) {
16363
16646
  ].join("\n");
16364
16647
  }
16365
16648
  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.`;
16649
+ const counts = (over.kind === "pass" || over.kind === "none") && over.ci.total > 0 ? ` (${over.ci.pass}/${over.ci.total})` : "";
16650
+ 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.` : (
16651
+ // TASWM-483: чеки, скорее всего, ЕСТЬ и даже зелёные — их просто не видно рою (403).
16652
+ // Врать «CI не прошёл» тут так же нельзя, как «CI зелёный»: единственная честная
16653
+ // формулировка — «не проверялся», и она обязана доехать до вердикта.
16654
+ 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.`
16655
+ );
16368
16656
  return [
16369
16657
  head,
16370
16658
  "",
@@ -16671,6 +16959,11 @@ function droneLabel(run) {
16671
16959
  function signalTitle(run, what) {
16672
16960
  return `${run.task_slug || droneLabel(run)}: ${what}`;
16673
16961
  }
16962
+ var tickNoise = makeRepeatFilter();
16963
+ function warnRepeating(key, message, now) {
16964
+ const line = tickNoise.say(key, message, now);
16965
+ if (line) console.error(line);
16966
+ }
16674
16967
  function narrate(deps, text) {
16675
16968
  try {
16676
16969
  deps.narrate?.(text);
@@ -16834,6 +17127,27 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
16834
17127
  return;
16835
17128
  }
16836
17129
  const ci = await deps.ci.checks(run.pr_url).catch(() => null);
17130
+ if (ci?.bucket === "denied") {
17131
+ const seen = ciDeniedStreak(deps.db, run.id) + 1;
17132
+ if (seen < CI_DENIED_LIMIT) {
17133
+ deps.db.addEvent(
17134
+ run.id,
17135
+ "ci_checked",
17136
+ `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}]`
17137
+ );
17138
+ return;
17139
+ }
17140
+ closeCiWatch(
17141
+ deps,
17142
+ run,
17143
+ CI_DENIED_STOP_MARK,
17144
+ `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.`,
17145
+ `${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.`
17146
+ );
17147
+ await escalateCiDenied(deps, run, ci.detail ?? null, seen);
17148
+ await wakeReviewerOnCiReady(deps, run, { kind: "denied", ...ci.detail ? { detail: ci.detail } : {} });
17149
+ return;
17150
+ }
16837
17151
  if (ci?.bucket === "none" && !repoHasCi(deps, run)) {
16838
17152
  closeCiWatch(
16839
17153
  deps,
@@ -16877,7 +17191,27 @@ function ciWatchMark(base3, cycle) {
16877
17191
  return `${base3} [\u0446\u0438\u043A\u043B ${cycle}]`;
16878
17192
  }
16879
17193
  function isCiWatchClose(text) {
16880
- return text.includes(CI_NO_CHECKS_MARK) || text.includes(CI_POLL_LIMIT_MARK);
17194
+ return text.includes(CI_NO_CHECKS_MARK) || text.includes(CI_POLL_LIMIT_MARK) || text.includes(CI_DENIED_STOP_MARK);
17195
+ }
17196
+ function ciDeniedStreak(db, runId) {
17197
+ return ciStreak(db, runId, CI_DENIED_MARK).count;
17198
+ }
17199
+ async function escalateCiDenied(deps, run, detail, polls) {
17200
+ 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.`;
17201
+ deps.db.addEvent(run.id, "anomaly", why);
17202
+ if (!deps.watchdog?.notify) return;
17203
+ const signal = {
17204
+ key: `ci_denied:${run.id}:${run.review_cycle}`,
17205
+ title: signalTitle(run, "GitHub \u043D\u0435 \u0434\u0430\u0451\u0442 \u0440\u043E\u044E \u0447\u0438\u0442\u0430\u0442\u044C \u0447\u0435\u043A\u0438 (403)"),
17206
+ 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"}.`,
17207
+ runId: run.id,
17208
+ ...hrefOf(run.task_url)
17209
+ };
17210
+ try {
17211
+ await deps.watchdog.notify(signal);
17212
+ } catch (e) {
17213
+ 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);
17214
+ }
16881
17215
  }
16882
17216
  function ciWatchClosed(db, run) {
16883
17217
  return ciWatchCloseKind(db, run) != null;
@@ -16886,20 +17220,31 @@ var NO_CHECKS_STATUS = { bucket: "none", failing: [], failingRunIds: [], total:
16886
17220
  function ciWatchCloseKind(db, run) {
16887
17221
  const noChecks = ciWatchMark(CI_NO_CHECKS_MARK, run.review_cycle);
16888
17222
  const gaveUp = ciWatchMark(CI_POLL_LIMIT_MARK, run.review_cycle);
17223
+ const denied = ciWatchMark(CI_DENIED_STOP_MARK, run.review_cycle);
16889
17224
  for (const e of db.events(run.id)) {
16890
17225
  const text = e.text ?? "";
16891
17226
  if (text.includes(noChecks)) return { kind: "none", ci: NO_CHECKS_STATUS };
16892
17227
  if (text.includes(gaveUp)) return { kind: "gaveup" };
17228
+ if (text.includes(denied)) {
17229
+ const detail = deniedDetailFrom(text);
17230
+ return { kind: "denied", ...detail ? { detail } : {} };
17231
+ }
16893
17232
  }
16894
17233
  return null;
16895
17234
  }
17235
+ function deniedDetailFrom(text) {
17236
+ return text.match(/на чтение чек-ранов \(([^)]+)\)/)?.[1];
17237
+ }
16896
17238
  function ciPollStreak(db, runId) {
17239
+ return ciStreak(db, runId, CI_WAIT_POLL_MARK);
17240
+ }
17241
+ function ciStreak(db, runId, mark) {
16897
17242
  const evs = db.events(runId).filter((e) => e.kind === "ci_wake" || e.kind === "ci_checked" || isCiWatchClose(e.text ?? ""));
16898
17243
  let count = 0;
16899
17244
  let sinceMs = null;
16900
17245
  for (let i = evs.length - 1; i >= 0; i -= 1) {
16901
17246
  const e = evs[i];
16902
- if (e.kind !== "ci_checked" || !(e.text ?? "").includes(CI_WAIT_POLL_MARK)) break;
17247
+ if (e.kind !== "ci_checked" || !(e.text ?? "").includes(mark)) break;
16903
17248
  count += 1;
16904
17249
  sinceMs = sqliteMs(e.at);
16905
17250
  }
@@ -16954,6 +17299,10 @@ function ciReadyLabel(over) {
16954
17299
  // (isCiWatchClose/ciWatchCloseKind ищут его по подстроке), и событие о том, что
16955
17300
  // побудка ждёт слота, не должно выглядеть вторым решением о закрытии.
16956
17301
  "\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"
17302
+ ) : over.kind === "denied" ? (
17303
+ // По той же причине не дословно CI_DENIED_STOP_MARK (TASWM-483) — но «403» в тексте
17304
+ // остаётся: по журналу должна быть видна причина, а не «проверок нет».
17305
+ "\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
17306
  ) : `CI \u0437\u0435\u043B\u0451\u043D\u044B\u0439 (${over.ci.pass}/${over.ci.total})`;
16958
17307
  }
16959
17308
  async function wakeReviewerOnCiReady(deps, run, over) {
@@ -17386,14 +17735,17 @@ async function trySpawnQueuedRun(deps, run) {
17386
17735
  return await failBeforeStart(deps, run, String(e));
17387
17736
  }
17388
17737
  }
17738
+ function holderStillHolds(deps, holder, now) {
17739
+ if (holder.pid != null) return deps.isAlive(holder.pid);
17740
+ return now - lastActivityMs(deps.db, holder) < DEAD_HOLDER_SILENCE_MS;
17741
+ }
17389
17742
  async function healBusyWorktree(deps, run, e) {
17390
17743
  const path = e.worktreePath;
17391
17744
  if (!deps.releaseWorktree) return false;
17392
17745
  if (deps.liveWorktrees?.().has(path)) return false;
17746
+ const now = deps.now ? deps.now() : Date.now();
17393
17747
  const holder = deps.db.listRuns().find((r) => r.id !== run.id && r.worktree_path === path && !isTerminalStatus(r.status));
17394
- if (holder && (holder.pid == null || deps.isAlive(holder.pid))) {
17395
- return false;
17396
- }
17748
+ if (holder && holderStillHolds(deps, holder, now)) return false;
17397
17749
  if (deps.isDirty && await deps.isDirty(path).catch(() => true)) {
17398
17750
  deps.db.addEvent(
17399
17751
  run.id,
@@ -17403,10 +17755,12 @@ async function healBusyWorktree(deps, run, e) {
17403
17755
  narrate(deps, `${run.task_slug}: worktree \u0437\u0430\u043D\u044F\u0442 \u043C\u0451\u0440\u0442\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439, \u043D\u043E \u0432 \u043D\u0451\u043C \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u2014 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A.`);
17404
17756
  return false;
17405
17757
  }
17758
+ const silence = holder && holder.pid == null ? `\u043C\u043E\u043B\u0447\u0430\u043B ${humanMinutes(now - lastActivityMs(deps.db, holder))}` : null;
17759
+ const why = silence ? `\u0434\u0435\u0440\u0436\u0430\u0442\u0435\u043B\u044C #${holder.id} (${holder.status}) ${silence} \u0438 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u043D\u0435 \u0438\u043C\u0435\u0435\u0442 \u2014 \u043D\u0435 \u0436\u0434\u0451\u0442 \u043C\u0435\u0440\u0436\u0430, \u0430 \u0443\u043C\u0435\u0440` : "\u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0437\u0430 \u043D\u0438\u043C \u043D\u0435\u0442";
17406
17760
  await deps.releaseWorktree(run.repo, path).catch(() => {
17407
17761
  });
17408
- deps.db.addEvent(run.id, "step", `worktree ${path} \u0434\u0435\u0440\u0436\u0430\u043B\u0430 \u043C\u0451\u0440\u0442\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u2014 \u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044E \u0441\u043F\u0430\u0432\u043D (TASWM-476)`);
17409
- if (holder) deps.db.addEvent(holder.id, "step", `worktree ${path} \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D: \u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438 \u0437\u0430 \u043D\u0438\u043C \u043D\u0435\u0442, \u0441\u043B\u0430\u0433 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id}`);
17762
+ deps.db.addEvent(run.id, "step", `worktree ${path} \u0434\u0435\u0440\u0436\u0430\u043B\u0430 \u043C\u0451\u0440\u0442\u0432\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F (${why}) \u2014 \u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044E \u0441\u043F\u0430\u0432\u043D (TASWM-476)`);
17763
+ if (holder) deps.db.addEvent(holder.id, "step", `worktree ${path} \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D: ${why}, \u0441\u043B\u0430\u0433 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id}`);
17410
17764
  return true;
17411
17765
  }
17412
17766
  async function failBeforeStart(deps, run, reason) {
@@ -17717,6 +18071,15 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
17717
18071
  ...hrefOf(worstRun?.task_url)
17718
18072
  }));
17719
18073
  }
18074
+ async function workFate(deps, run) {
18075
+ if (run.pr_url) return `PR \u043E\u0442\u043A\u0440\u044B\u0442: ${run.pr_url}`;
18076
+ if (!deps.branchOnOrigin) return "";
18077
+ const branch = run.branch || run.task_slug;
18078
+ const exists = await deps.branchOnOrigin(run.repo, branch).catch(() => null);
18079
+ if (exists === true) return `${EXIT_WORK_PUSHED} (\u0432\u0435\u0442\u043A\u0430 ${branch})`;
18080
+ if (exists === false) return `${EXIT_WORK_LOST} (\u0432\u0435\u0442\u043A\u0438 ${branch} \u043D\u0430 origin \u043D\u0435\u0442)`;
18081
+ return EXIT_WORK_UNKNOWN;
18082
+ }
17720
18083
  async function deliveredPrUrl(deps, run) {
17721
18084
  if (run.pr_url) return run.pr_url;
17722
18085
  if (!deps.openPrsForBranch) return null;
@@ -18108,7 +18471,11 @@ async function tickOnce(deps) {
18108
18471
  deps.db.addEvent(run.id, "step", `${SELF_REFERENCE_MARK}: PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 ${DAEMON_CODE_PATHS.join(", ")} \u2014 \u044D\u0441\u043A\u0430\u043B\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443, \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E`);
18109
18472
  narrate(deps, `PR ${droneLabel(run)} \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0441\u0430\u043C \u0441\u0435\u0431\u044F \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442, \u044D\u0441\u043A\u0430\u043B\u0438\u0440\u0443\u044E \u0438 \u043D\u0435 \u0433\u043E\u043D\u044F\u044E \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E.`);
18110
18473
  } catch (e) {
18111
- console.error(`[overmind] \u0441\u0442\u043E\u0440\u043E\u0436: self-reference \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \xAB${signal.key}\xBB \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u0430, \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C:`, e);
18474
+ warnRepeating(
18475
+ `self-reference:${signal.key}`,
18476
+ `[overmind] \u0441\u0442\u043E\u0440\u043E\u0436: self-reference \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \xAB${signal.key}\xBB \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u0430, \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C: ${describeError(e)}`,
18477
+ now
18478
+ );
18112
18479
  }
18113
18480
  }
18114
18481
  continue;
@@ -18314,7 +18681,10 @@ async function tickOnce(deps) {
18314
18681
  continue;
18315
18682
  }
18316
18683
  }
18317
- if (!alive && outcome === "graceful" && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
18684
+ if (!alive && (outcome === "graceful" || outcome === "aborted") && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
18685
+ const aborted = wasAborted(outcome);
18686
+ const abort = aborted ? deps.exitAbort?.(run) ?? null : null;
18687
+ const what = aborted ? abortReason(abort) : EXITED_NO_DONE_REASON;
18318
18688
  const wt = run.worktree_path;
18319
18689
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
18320
18690
  if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
@@ -18322,21 +18692,31 @@ async function tickOnce(deps) {
18322
18692
  deps.db.addEvent(
18323
18693
  run.id,
18324
18694
  "respawned",
18325
- `\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430 \u0431\u0435\u0437 done() \u2014 --continue, \u0434\u043E\u0432\u043E\u0436\u0443 \u0434\u043E \u0441\u0434\u0430\u0447\u0438 (\u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u044E \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435)`
18695
+ aborted ? `\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: ${what} \u2014 --continue \u0441 \u043D\u0430\u043A\u0430\u0437\u043E\u043C \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0442\u044C \u0442\u044F\u0436\u0451\u043B\u0443\u044E \u043A\u043E\u043C\u0430\u043D\u0434\u0443` : `\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430 \u0431\u0435\u0437 done() \u2014 --continue, \u0434\u043E\u0432\u043E\u0436\u0443 \u0434\u043E \u0441\u0434\u0430\u0447\u0438 (\u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u044E \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435)`
18696
+ );
18697
+ narrate(
18698
+ deps,
18699
+ aborted ? `${droneLabel(run)}: ${what}. \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue.` : `${droneLabel(run)} \u0432\u044B\u0448\u0435\u043B \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0432\u0451\u043B \u0434\u043E PR/done.`
18326
18700
  );
18327
- narrate(deps, `${droneLabel(run)} \u0432\u044B\u0448\u0435\u043B \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue, \u0447\u0442\u043E\u0431\u044B \u0434\u043E\u0432\u0451\u043B \u0434\u043E PR/done.`);
18328
18701
  try {
18329
- await deps.respawn(run, "continue", EXITED_CONTINUE_NUDGE);
18702
+ await deps.respawn(run, "continue", aborted ? ABORTED_CONTINUE_NUDGE : EXITED_CONTINUE_NUDGE);
18330
18703
  } catch (e) {
18331
- deps.db.updateRun(run.id, { status: "paused", stop_reason: EXITED_NO_DONE_REASON });
18332
- deps.db.addEvent(run.id, "paused", `${EXITED_NO_DONE_REASON}; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C: ${String(e)}`);
18704
+ const failed = `${aborted ? ABORTED_STOP_REASON : EXITED_NO_DONE_REASON}; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C: ${String(e)}`;
18705
+ deps.db.updateRun(run.id, { status: "paused", stop_reason: failed });
18706
+ deps.db.addEvent(run.id, "paused", failed);
18333
18707
  }
18334
18708
  continue;
18335
18709
  }
18336
- const reason2 = run.respawns >= RESPAWN_LIMIT ? `${EXITED_NO_DONE_REASON} \u2014 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0439 (${RESPAWN_LIMIT}), \u043D\u0443\u0436\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440` : EXITED_NO_DONE_REASON;
18710
+ const marker = aborted ? ABORTED_STOP_REASON : EXITED_NO_DONE_REASON;
18711
+ const head = run.respawns >= RESPAWN_LIMIT ? `${marker} \u2014 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0439 (${RESPAWN_LIMIT}), \u043D\u0443\u0436\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440` : marker;
18712
+ const fate = await workFate(deps, run);
18713
+ const reason2 = [head, aborted ? what : null, fate].filter(Boolean).join("; ");
18337
18714
  deps.db.updateRun(run.id, { status: "paused", stop_reason: reason2 });
18338
18715
  deps.db.addEvent(run.id, "paused", reason2);
18339
- narrate(deps, `${droneLabel(run)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432 \u043B\u043E\u0433\u0435 (TASWM-229).`);
18716
+ narrate(
18717
+ deps,
18718
+ aborted ? `${droneLabel(run)}: ${what}. \u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C \u2014 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, ${fate || "\u0441\u0443\u0434\u044C\u0431\u0430 \u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430"}.` : `${droneLabel(run)} \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E \u0441\u0430\u043C, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u0437\u0430\u043C\u0435\u0440\xBB, \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432 \u043B\u043E\u0433\u0435 (TASWM-229).`
18719
+ );
18340
18720
  continue;
18341
18721
  }
18342
18722
  if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
@@ -18384,8 +18764,8 @@ async function tickOnce(deps) {
18384
18764
  const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
18385
18765
  const ci = ciWatchOff ? null : await deps.ci.checks(run.pr_url).catch(() => null);
18386
18766
  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}`;
18767
+ if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied")) {
18768
+ 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
18769
  deps.db.updateRun(run.id, { status: "paused", stop_reason: AWAITING_CI_REASON });
18390
18770
  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
18771
  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.`);
@@ -18512,8 +18892,13 @@ async function tickOnce(deps) {
18512
18892
  const signal = finalCall ? { ...item.signal, body: `${item.signal.body} ${LAST_REMINDER_SUFFIX}` } : item.signal;
18513
18893
  try {
18514
18894
  await az.notify(signal);
18895
+ tickNoise.forget(`notify:${signal.key}`);
18515
18896
  } catch (e) {
18516
- console.error(`[overmind] \u043D\u0430\u0434\u0437\u043E\u0440: \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \xAB${signal.key}\xBB \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u0430, \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C:`, e);
18897
+ warnRepeating(
18898
+ `notify:${signal.key}`,
18899
+ `[overmind] \u043D\u0430\u0434\u0437\u043E\u0440: \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \xAB${signal.key}\xBB \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u0430, \u043F\u043E\u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C: ${describeError(e)}`,
18900
+ now
18901
+ );
18517
18902
  continue;
18518
18903
  }
18519
18904
  for (const key of item.keys) az.tracker.commit(key, now);
@@ -18535,8 +18920,13 @@ async function tickOnce(deps) {
18535
18920
  try {
18536
18921
  await az.resolve(batch);
18537
18922
  az.tracker.forgetResolved(batch);
18923
+ tickNoise.forget("anomaly-resolve");
18538
18924
  } catch (e) {
18539
- console.error("[overmind] \u043D\u0430\u0434\u0437\u043E\u0440: \u0433\u0430\u0448\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0432\u0448\u0438\u0445\u0441\u044F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u0439 \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u043E, \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C:", e);
18925
+ warnRepeating(
18926
+ "anomaly-resolve",
18927
+ `[overmind] \u043D\u0430\u0434\u0437\u043E\u0440: \u0433\u0430\u0448\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0432\u0448\u0438\u0445\u0441\u044F \u0430\u043D\u043E\u043C\u0430\u043B\u0438\u0439 \u043D\u0435 \u0434\u043E\u043B\u0435\u0442\u0435\u043B\u043E, \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C: ${describeError(e)}`,
18928
+ now
18929
+ );
18540
18930
  }
18541
18931
  }
18542
18932
  }
@@ -19009,6 +19399,8 @@ async function reportWorkspaceAccess(tl, db) {
19009
19399
  }
19010
19400
  async function runDaemon(opts) {
19011
19401
  installCrashGuards();
19402
+ installLogStamps();
19403
+ console.log(localZoneLine(/* @__PURE__ */ new Date()));
19012
19404
  const daemonStart = captureDaemonStart();
19013
19405
  const latestRelease = fetchLatestVersion();
19014
19406
  const worktreesDir = await migrateWorktreesAtStartup();
@@ -19466,7 +19858,18 @@ async function runDaemon(opts) {
19466
19858
  // TASWM-472: третья ось — ОЧЕРЕДЬ. Вливаем по одному и только на свежей базе; ридер
19467
19859
  // свежести ходит в gh (`compare/base...head` → behind_by). Ключа нет при
19468
19860
  // SWARM_MERGE_QUEUE=0 — тогда гейт отвечает ровно как до TASWM-472.
19469
- ...SWARM_MERGE_QUEUE ? { baseSync: (url) => prBaseSync(url), checks: (url) => prCiStatus(url) } : {}
19861
+ // TASWM-483: отказ по правам (403) очереди мержа приходит как «спросить не удалось» (null),
19862
+ // а не как отдельная ось: для НЕЁ это одно и то же — цвет проверок неизвестен, значит
19863
+ // вливать нельзя (checks_unknown, см. докблок computeMergeQueue про несимметричную цену).
19864
+ // Отличать 403 от таймаута нужно тому, кто ЖДЁТ и повторяет (handleCi), а очередь не ждёт.
19865
+ ...SWARM_MERGE_QUEUE ? {
19866
+ baseSync: (url) => prBaseSync(url),
19867
+ checks: async (url) => {
19868
+ const ci = await prCiStatus(url);
19869
+ if (ci == null || ci.bucket === "denied") return null;
19870
+ return { bucket: ci.bucket };
19871
+ }
19872
+ } : {}
19470
19873
  });
19471
19874
  const deps = {
19472
19875
  db,
@@ -19478,7 +19881,9 @@ async function runDaemon(opts) {
19478
19881
  const repoOfRun = normalizeRepo(run.repo || opts.repo);
19479
19882
  const taskClient = tlFor(run.workspace_id);
19480
19883
  const [rawTask, verifyMode, projectBase, zoneNotes] = await Promise.all([
19481
- taskClient ? taskClient.getTask(run.task_slug) : Promise.resolve(null),
19884
+ // TASWM-484: с причиной — на этом чтении стоит fail-loud ниже, и именно его
19885
+ // сообщение читает человек, когда дрон не поднялся.
19886
+ taskClient ? taskClient.getTaskDetailed(run.task_slug) : Promise.resolve(null),
19482
19887
  verifyModeReader ? verifyModeReader(repoOfRun) : Promise.resolve(void 0),
19483
19888
  baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve(""),
19484
19889
  // TASWM-466: память зоны прогона — тем же параллельным чтением, что и остальные
@@ -19576,6 +19981,11 @@ async function runDaemon(opts) {
19576
19981
  // Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
19577
19982
  // (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
19578
19983
  exitOutcome: makeExitClassifier(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
19984
+ // TASWM-487: вид обрыва — из того же result-файла и теми же ключами каталога.
19985
+ exitAbort: makeAbortReader(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
19986
+ // TASWM-487: жива ли работа на origin. Тот же фолбэк repo, что у removeWorktree выше:
19987
+ // у прогона в основном репозитории демона run.repo пуст.
19988
+ branchOnOrigin: (repo, branch) => remoteBranchExists(repo || opts.repo, branch),
19579
19989
  // Живой child-процесс (TASWM-89): долгая Bash-команда (тесты/сборка) — не «завис».
19580
19990
  livePpids,
19581
19991
  // Погасить дрона (SIGTERM) — reap внешне-остановленных прогонов (kill из дашборда:
@@ -19735,7 +20145,7 @@ async function runDaemon(opts) {
19735
20145
  // fresh-оживление строит промпт с нуля, и без тела задачи это тот же слепой дрон.
19736
20146
  getTask: async (slug, ws) => {
19737
20147
  const client = tlFor(ws);
19738
- return client ? requireTaskBody(await client.getTask(slug), slug, ws) : null;
20148
+ return client ? requireTaskBody(await client.getTaskDetailed(slug), slug, ws) : null;
19739
20149
  },
19740
20150
  ...verifyModeReader ? { verifyMode: verifyModeReader } : {},
19741
20151
  // TASWM-466: тот же ридер памяти зоны, что на спавне — оживлённый дрон строит
@@ -20177,6 +20587,9 @@ if (isEntrypoint) {
20177
20587
  }
20178
20588
  }
20179
20589
  export {
20590
+ ABORTED_CONTINUE_NUDGE,
20591
+ ABORTED_STOP_REASON,
20592
+ ABORT_TEXT,
20180
20593
  AWAITING_CI_REASON,
20181
20594
  BUDGET_STOP_REASON,
20182
20595
  CHANGES_FRESH_WAKE_AFTER_MS,
@@ -20186,6 +20599,9 @@ export {
20186
20599
  CHANGES_WAKE_LIMIT,
20187
20600
  CHANGES_WAKE_MARK,
20188
20601
  CI_CHECK_COOLDOWN_MS,
20602
+ CI_DENIED_LIMIT,
20603
+ CI_DENIED_MARK,
20604
+ CI_DENIED_STOP_MARK,
20189
20605
  CI_NO_CHECKS_MARK,
20190
20606
  CI_POLL_LIMIT,
20191
20607
  CI_POLL_LIMIT_MARK,
@@ -20198,8 +20614,12 @@ export {
20198
20614
  CI_WAKE_LIMIT,
20199
20615
  CONFLICT_RESOLVE_COOLDOWN_MS,
20200
20616
  CONTINUE_NUDGE,
20617
+ DEAD_HOLDER_SILENCE_MS,
20201
20618
  EXITED_CONTINUE_NUDGE,
20202
20619
  EXITED_NO_DONE_REASON,
20620
+ EXIT_WORK_LOST,
20621
+ EXIT_WORK_PUSHED,
20622
+ EXIT_WORK_UNKNOWN,
20203
20623
  GATE_LOG_HEARTBEAT_TICKS,
20204
20624
  HEADLESS_BLOCK,
20205
20625
  HERO_CONTINUE_NUDGE,
@@ -20222,6 +20642,7 @@ export {
20222
20642
  STORM_DEAD_LIMIT,
20223
20643
  VERIFY_SPAWN_LIMIT,
20224
20644
  WATCHDOG_PARK_REASON,
20645
+ abortReason,
20225
20646
  buildChangesWakeNudge,
20226
20647
  buildCiReadyNudge,
20227
20648
  buildCiWakeNudge,