@taskless-app/swarm 0.6.15 → 0.6.17

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.
Files changed (3) hide show
  1. package/dist/cli.js +558 -176
  2. package/dist/overmind.js +311 -58
  3. package/package.json +2 -2
package/dist/overmind.js CHANGED
@@ -58,6 +58,13 @@ var init_types = __esm({
58
58
  // вопрос дрона оператору (прод: SwarmEvent kind=question, TASWM-74)
59
59
  "answer",
60
60
  // ответ оператора на вопрос дрона (прод: SwarmEvent kind=answer)
61
+ // Черновик ответа, собранный машиной (TASWM-430): контекст задачи/репозитория + один
62
+ // вызов модели, реплика в ленте прогона рядом с вопросом. НЕ "answer" с флагом — и это
63
+ // главное в этом kind: вопрос закрывают (и дрона будят) все, кто сравнивает kind со
64
+ // строкой "answer"/"answered" — прод (state/taskRuns/timeline/fleetMetrics), веб-чат и
65
+ // синхронизация демона (droneBridge тянет вниз только ответ). Отдельный kind не знает
66
+ // никто из них, поэтому черновик физически не может разбудить дрона: он ждёт человека.
67
+ "answer_draft",
61
68
  "ci_wake",
62
69
  // review-PR покраснел (gh pr checks) — рой разбудил дрона со сжатым summary фейла (TASWM-102)
63
70
  "ci_checked",
@@ -914,9 +921,17 @@ var init_baseBranch = __esm({
914
921
  });
915
922
 
916
923
  // ../swarm-core/src/hosts.ts
924
+ function hostTickStallMs(intervalMs) {
925
+ const iv = intervalMs != null && Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 0;
926
+ return Math.max(iv * HOST_TICK_STALL_TICKS, HOST_TICK_STALL_FLOOR_MS);
927
+ }
928
+ var HOST_TICK_STALL_TICKS, HOST_TICK_STALL_FLOOR_MS, HOST_TICK_STALLED_ACTION;
917
929
  var init_hosts = __esm({
918
930
  "../swarm-core/src/hosts.ts"() {
919
931
  "use strict";
932
+ HOST_TICK_STALL_TICKS = 10;
933
+ HOST_TICK_STALL_FLOOR_MS = 12e4;
934
+ HOST_TICK_STALLED_ACTION = '\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0434\u0435\u043C\u043E\u043D \u043C\u0430\u0448\u0438\u043D\u044B \u2014 swarm_host_command{kind:"restart"}';
920
935
  }
921
936
  });
922
937
 
@@ -947,10 +962,27 @@ var init_hostResources = __esm({
947
962
  });
948
963
 
949
964
  // ../swarm-core/src/hostPool.ts
965
+ var HOST_REJECTION_TEXT, HOST_REPO_BLOCK_TEXT;
950
966
  var init_hostPool = __esm({
951
967
  "../swarm-core/src/hostPool.ts"() {
952
968
  "use strict";
953
969
  init_hosts();
970
+ HOST_REJECTION_TEXT = {
971
+ offline: "\u043D\u0435 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438",
972
+ tick_stalled: `\u0446\u0438\u043A\u043B \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B \u2014 \u043F\u0443\u043B\u044C\u0441 \u0438\u0434\u0451\u0442, \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442; ${HOST_TICK_STALLED_ACTION}`,
973
+ not_signed_in: "\u043D\u0435 \u0432\u043E\u0448\u043B\u0430 \u0432 Taskless \u2014 \u043D\u0430 \u0435\u0451 \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0435 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u0432\u0445\u043E\u0434",
974
+ paused: "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435",
975
+ draining: "\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435",
976
+ repos_off: "\u0432\u0441\u0435 \u0435\u0451 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u044B",
977
+ full: "\u0437\u0430\u043D\u044F\u0442\u0430 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E"
978
+ };
979
+ HOST_REPO_BLOCK_TEXT = {
980
+ offline: HOST_REJECTION_TEXT.offline,
981
+ paused: HOST_REJECTION_TEXT.paused,
982
+ draining: HOST_REJECTION_TEXT.draining,
983
+ no_clone: "\u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442",
984
+ repo_off: "\u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u0430"
985
+ };
954
986
  }
955
987
  });
956
988
 
@@ -4552,6 +4584,28 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4552
4584
  const text = res.content?.find((c) => c.type === "text")?.text;
4553
4585
  return typeof text === "string" && text.trim() ? text : null;
4554
4586
  }
4587
+ async function proposeRepoRules(input) {
4588
+ const res = await safeCall("swarm_repo_rule_propose", {
4589
+ repoFullName: input.repoFullName,
4590
+ rules: input.rules,
4591
+ ...input.by ? { by: input.by } : {},
4592
+ ...input.taskSlug ? { taskSlug: input.taskSlug } : {}
4593
+ });
4594
+ if (!res || res.isError) return null;
4595
+ const text = res.content?.find((c) => c.type === "text")?.text;
4596
+ return typeof text === "string" && text.trim() ? text : null;
4597
+ }
4598
+ async function decideRepoRules(input) {
4599
+ const res = await safeCall("swarm_repo_rule_decide", {
4600
+ repoFullName: input.repoFullName,
4601
+ decisions: input.decisions,
4602
+ ...input.by ? { by: input.by } : {},
4603
+ ...input.taskSlug ? { taskSlug: input.taskSlug } : {}
4604
+ });
4605
+ if (!res || res.isError) return null;
4606
+ const text = res.content?.find((c) => c.type === "text")?.text;
4607
+ return typeof text === "string" && text.trim() ? text : null;
4608
+ }
4555
4609
  async function resolveId(idOrSlug) {
4556
4610
  const cached = idCache.get(idOrSlug);
4557
4611
  if (cached) return cached;
@@ -4632,6 +4686,8 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4632
4686
  getDocument,
4633
4687
  getTaskContext,
4634
4688
  reportAcceptanceCheck,
4689
+ proposeRepoRules,
4690
+ decideRepoRules,
4635
4691
  createTask: async (input) => {
4636
4692
  if (!input.projectId) {
4637
4693
  throw new Error("createTask: projectId \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u2014 \u0430\u0432\u0442\u043E-\u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u043E\u0435\u043C \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E (TASWM-204)");
@@ -6004,6 +6060,54 @@ import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync5, rea
6004
6060
  import { dirname } from "path";
6005
6061
  import { randomUUID } from "crypto";
6006
6062
 
6063
+ // src/repoRules.ts
6064
+ var RULE_FIELDS = ["brief", "zones", "env", "verify", "conventions", "spine"];
6065
+ var DRONE_RULES_PER_RUN_MAX = 2;
6066
+ function parsePendingRepoRules(raw) {
6067
+ if (!Array.isArray(raw)) return [];
6068
+ const out = [];
6069
+ for (const item of raw) {
6070
+ if (!item || typeof item !== "object") continue;
6071
+ const r = item;
6072
+ const field = RULE_FIELDS.find((f) => f === r.field);
6073
+ if (!field) continue;
6074
+ out.push({
6075
+ field,
6076
+ value: r.value,
6077
+ by: typeof r.by === "string" ? r.by : null,
6078
+ task: typeof r.task === "string" ? r.task : null,
6079
+ why: typeof r.why === "string" ? r.why : null,
6080
+ cheap: r.cheap === true,
6081
+ overwritesHuman: r.overwritesHuman === true
6082
+ });
6083
+ }
6084
+ return out;
6085
+ }
6086
+ function ruleValueLine(value) {
6087
+ const json = JSON.stringify(value ?? null);
6088
+ return json.length > 400 ? `${json.slice(0, 400)}\u2026 (\u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043E)` : json;
6089
+ }
6090
+ function renderPendingRulesBlock(rules) {
6091
+ if (rules.length === 0) return "";
6092
+ const lines = rules.map((r) => {
6093
+ const who = r.by ? `\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438\u043B ${r.by}` : "\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438\u043B \u0434\u0440\u043E\u043D";
6094
+ const on = r.task ? ` \u043D\u0430 ${r.task}` : "";
6095
+ const fate = r.cheap && !r.overwritesHuman ? "\u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u0441\u044F \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0441\u0440\u0430\u0437\u0443" : r.overwritesHuman ? "\u043F\u0435\u0440\u0435\u043F\u0438\u0441\u0430\u043B\u043E \u0431\u044B \u043F\u0440\u0430\u0432\u043A\u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \u0443\u0439\u0434\u0451\u0442 \u0435\u043C\u0443 \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0443, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043F\u0440\u0438\u043C\u0435\u0448\u044C" : "\u0443\u0439\u0434\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0443, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043F\u0440\u0438\u043C\u0435\u0448\u044C (env/zones/spine \u0440\u043E\u0439 \u0441\u0430\u043C \u043D\u0435 \u043F\u0438\u0448\u0435\u0442)";
6096
+ return [
6097
+ `\u2022 ${r.field} \u2014 ${who}${on}. ${fate}.`,
6098
+ ` \u043F\u043E\u0447\u0435\u043C\u0443: ${r.why?.trim() || "(\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0430 \u2014 \u044D\u0442\u043E \u0441\u0430\u043C\u043E \u043F\u043E \u0441\u0435\u0431\u0435 \u043F\u043E\u0432\u043E\u0434 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C)"}`,
6099
+ ` \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435: ${ruleValueLine(r.value)}`
6100
+ ].join("\n");
6101
+ });
6102
+ return [
6103
+ "\u{1F4CC} \u041F\u0420\u0410\u0412\u0418\u041B\u0410 \u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0418, \u041F\u0420\u0418\u041D\u0415\u0421\u0401\u041D\u041D\u042B\u0415 \u0414\u0420\u041E\u041D\u041E\u041C (TASWM-431) \u2014 \u0440\u0435\u0448\u0430\u0435\u0448\u044C \u0442\u044B, \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C.",
6104
+ "\u0414\u0440\u043E\u043D \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0435\u0442 \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0442\u043E \u0432 \u043A\u043E\u0434\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u0442\u0430\u043A\u043E\u0435 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u0438\u0442). \u0422\u044B \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043B \u0434\u0438\u0444\u0444 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0432\u0438\u0434\u0438\u0448\u044C \u0447\u0438\u0441\u043B\u043E \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0438 \u0447\u0442\u043E \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u043B \u2014 \u0442\u043E \u0435\u0441\u0442\u044C \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439, \u043A\u0442\u043E \u043E\u0442\u043B\u0438\u0447\u0438\u0442 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E\u0442 \u043E\u0442\u043F\u0438\u0441\u043A\u0438.",
6105
+ ...lines,
6106
+ '\u0420\u0435\u0448\u0438 \u043F\u043E \u041A\u0410\u0416\u0414\u041E\u041C\u0423 \u0438 \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u0432 done: `rule_verdicts: [{ field: "verify", apply: true }, \u2026]`. apply=true \u2014 \xAB\u043F\u0440\u0430\u0432\u0434\u0430, \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043E\u0431\u044F\u0437\u0430\u043D \u044D\u0442\u043E \u0437\u043D\u0430\u0442\u044C\xBB; apply=false \u2014 \u043E\u0442\u043F\u0438\u0441\u043A\u0430/\u043E\u0431\u0449\u0435\u0435 \u043C\u0435\u0441\u0442\u043E (\xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0438\u0437 README\xBB), \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u0441\u043D\u0438\u043C\u0435\u0442\u0441\u044F. \u041D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D \u2014 apply=false: \u043F\u0440\u0430\u0432\u0438\u043B\u043E, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435\u0442, \u0441\u0442\u043E\u0438\u0442 \u0434\u0435\u0448\u0435\u0432\u043B\u0435 \u043D\u0435\u0432\u0435\u0440\u043D\u043E\u0433\u043E.',
6107
+ "\u041C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u2014 \u0442\u043E\u0436\u0435 \u0438\u0441\u0445\u043E\u0434: \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043B \u0440\u0435\u0448\u0435\u043D\u0438\u0435, \u0438 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u0432\u0438\u0441\u0435\u0442\u044C \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0435 \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u042D\u0442\u043E \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430, \u043D\u043E \u0438 \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430."
6108
+ ].join("\n");
6109
+ }
6110
+
6007
6111
  // src/logTail.ts
6008
6112
  import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
6009
6113
  import { join as join7 } from "path";
@@ -7518,6 +7622,25 @@ var SwarmClient = class _SwarmClient {
7518
7622
  const sc = res.structuredContent ?? {};
7519
7623
  return normalizeBaseBranch(sc.config?.baseBranch) ?? null;
7520
7624
  }
7625
+ /**
7626
+ * ПРАВИЛА ОТ ДРОНОВ, ждущие вердикта ревьюера (TASWM-431): их демон кладёт в промпт
7627
+ * ревьюера — тот решает по ним в своём же вердикте. `taskSlug` — правила ЭТОГО
7628
+ * прогона: ревьюер отвечает за находки своего дрона, а не за чужие.
7629
+ *
7630
+ * Прод недоступен / старый прод (тула нет) / ответ не той формы → пустой список:
7631
+ * ревью обязано подниматься и без этого блока — правила тут не главное.
7632
+ */
7633
+ async repoPendingRules(repoFullName, taskSlug) {
7634
+ let res;
7635
+ try {
7636
+ res = await this.call("swarm_repo_rule_pending", { repoFullName, ...taskSlug ? { taskSlug } : {} });
7637
+ } catch {
7638
+ return [];
7639
+ }
7640
+ if (res?.isError) return [];
7641
+ const sc = res.structuredContent ?? {};
7642
+ return parsePendingRepoRules(sc.rules);
7643
+ }
7521
7644
  /**
7522
7645
  * Live КОМАНДЫ ПОДГОТОВКИ worktree из Taskless (TASWM-312): тот же swarm_repo_config по
7523
7646
  * repoFullName — Project.swarmConfig.prepare. Настройка сильнее детекта демона (deps.ts):
@@ -7561,29 +7684,11 @@ var SwarmClient = class _SwarmClient {
7561
7684
  const p = sc.profile;
7562
7685
  return p && typeof p === "object" && !Array.isArray(p) ? p : null;
7563
7686
  }
7564
- /**
7565
- * РАЗВЕДКА репозитория (TASWM-382): отправить в прод то, что разведчик вычитал в коде.
7566
- * Живую разметку это НЕ меняет — предложение ложится рядом и ждёт человека; поэтому
7567
- * ошибка здесь дорога ровно на стоимость прогона разведки, и мы её возвращаем, а не
7568
- * глотаем, как чтения выше.
7569
- *
7570
- * Мимо outbox (в отличие от мутаций прогонов): предложение имеет смысл только вместе
7571
- * со свежим состоянием репозитория, и доехавшее через сутки после разведки — хуже,
7572
- * чем не доехавшее вовсе. Прод недоступен — человек перезапустит разведку.
7573
- */
7574
- async reportRepoScout(repoFullName, report, by) {
7575
- let res;
7576
- try {
7577
- res = await this.call("swarm_repo_scout_report", { repoFullName, report, ...by ? { by } : {} });
7578
- } catch (e) {
7579
- return { ok: false, error: `\u043F\u0440\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${e.message}` };
7580
- }
7581
- const text = res?.content?.map((c) => "text" in c ? c.text : "").join(" ") ?? "";
7582
- if (res?.isError) return { ok: false, error: text || "\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0438" };
7583
- const sc = res.structuredContent ?? {};
7584
- const diff = Array.isArray(sc.diff) ? sc.diff : [];
7585
- return { ok: true, diff, display: text };
7586
- }
7687
+ // TASWM-441: отправка предложения разведки в прод (`swarm_repo_scout_report`) убрана —
7688
+ // разведчик больше не предлагает разметку в базу, а ведёт файл В САМОМ репозитории
7689
+ // (docs/AGENT_BRIEF.md, см. repoScout.ts). Прод-сторона тула и экран приёмки уезжают
7690
+ // отдельной задачей TASWM-442; `repoProfile` выше — её же остаток, читателей у него
7691
+ // в демоне не осталось.
7587
7692
  /**
7588
7693
  * Live ТРЕБОВАНИЯ К ОКРУЖЕНИЮ машины из Taskless (TASWM-313): тот же swarm_repo_config по
7589
7694
  * repoFullName — Project.swarmConfig.requires. Демон проверяет их ПЕРЕД спавном дрона и не
@@ -7799,7 +7904,7 @@ var SwarmClient = class _SwarmClient {
7799
7904
  *
7800
7905
  * Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
7801
7906
  */
7802
- async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn) {
7907
+ async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick) {
7803
7908
  try {
7804
7909
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
7805
7910
  if (daemonVersion) args.daemonVersion = daemonVersion;
@@ -7826,6 +7931,9 @@ var SwarmClient = class _SwarmClient {
7826
7931
  args.hostId = this.host.hostId;
7827
7932
  args.hostName = this.host.hostName;
7828
7933
  }
7934
+ if (tick && Number.isFinite(tick.seq) && Number.isFinite(tick.ageMs) && tick.intervalMs > 0) {
7935
+ args.tick = { seq: Math.max(0, Math.trunc(tick.seq)), ageMs: Math.max(0, Math.trunc(tick.ageMs)), intervalMs: Math.trunc(tick.intervalMs) };
7936
+ }
7829
7937
  const res = await this.call("swarm_heartbeat", args);
7830
7938
  if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
7831
7939
  this.heartbeatOkAt = this.now();
@@ -8423,6 +8531,34 @@ function parseDecision(v) {
8423
8531
  init_config();
8424
8532
  init_heartbeat();
8425
8533
 
8534
+ // src/tickHealth.ts
8535
+ init_src();
8536
+ function initialTickHealth(now) {
8537
+ return { seq: 0, lastAt: null, startedAt: now, lastError: null };
8538
+ }
8539
+ function markTickDone(health, now, error) {
8540
+ health.seq += 1;
8541
+ health.lastAt = now;
8542
+ health.lastError = error === void 0 ? null : describeTickError(error);
8543
+ }
8544
+ function describeTickError(error) {
8545
+ if (error instanceof Error) return error.message || error.name;
8546
+ return String(error);
8547
+ }
8548
+ function tickAgeMs(health, now) {
8549
+ return Math.max(0, now - (health.lastAt ?? health.startedAt));
8550
+ }
8551
+ var TICK_SELF_HEAL_FLOOR_MS = 6e5;
8552
+ function tickSelfHealMs(intervalMs) {
8553
+ return Math.max(hostTickStallMs(intervalMs) * 2, TICK_SELF_HEAL_FLOOR_MS);
8554
+ }
8555
+ function tickSelfHealDue(health, now, intervalMs) {
8556
+ return tickAgeMs(health, now) > tickSelfHealMs(intervalMs);
8557
+ }
8558
+ function tickPulse(health, now, intervalMs) {
8559
+ return { seq: health.seq, ageMs: tickAgeMs(health, now), intervalMs };
8560
+ }
8561
+
8426
8562
  // src/logRotate.ts
8427
8563
  import { copyFileSync, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
8428
8564
  var realIo = {
@@ -10488,6 +10624,7 @@ init_attachments();
10488
10624
  init_shotUpload();
10489
10625
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10490
10626
  import { z } from "zod";
10627
+ init_git();
10491
10628
  init_src();
10492
10629
 
10493
10630
  // src/reviewCycle.ts
@@ -10864,10 +11001,57 @@ ${notFoundText}` : notFoundText }],
10864
11001
  ),
10865
11002
  screenshots: z.array(z.string()).optional().describe(
10866
11003
  `\u041F\u0443\u0442\u0438 \u043A \u0441\u043D\u044F\u0442\u044B\u043C \u0441\u043A\u0440\u0438\u043D\u0430\u043C (png/jpg/webp/gif) \u2014 \u0438\u0445 \u043F\u0440\u0438\u043B\u043E\u0436\u0430\u0442 \u043A \u0442\u0432\u043E\u0435\u043C\u0443 \u043E\u0442\u0447\u0451\u0442\u0443 \u0412 \u0417\u0410\u0414\u0410\u0427\u0415, \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u0442\u0440\u0435\u043A\u0435\u0440, \u0430 \u043D\u0435 \u0433\u0438\u0442\u0445\u0430\u0431. \u041F\u0443\u0442\u044C \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 (\u043E\u0442 worktree) \u0438\u043B\u0438 \u0430\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u044B\u0439, \u0434\u043E ${SHOT_MAX_FILES} \u0444\u0430\u0439\u043B\u043E\u0432. \u041C\u0435\u043D\u044F\u043B \u0432\u0438\u0434\u0438\u043C\u044B\u0439 UI \u2014 \u043F\u0440\u0438\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0439 \u043E\u0431\u0430 \u0432\u044C\u044E\u043F\u043E\u0440\u0442\u0430 (desktop+mobile), \u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0440\u0430\u0431\u043E\u0442\u044B.`
11004
+ ),
11005
+ // TASWM-431: правило о репозитории — то, обо что дрон СПОТКНУЛСЯ. Форму значения
11006
+ // проверяет прод (там живёт схема разметки), здесь она намеренно свободная: гейт
11007
+ // на форму в двух местах разъехался бы, а отказ прода дрон и так увидит в ответе.
11008
+ rules: z.array(
11009
+ z.object({
11010
+ field: z.enum([...RULE_FIELDS]).describe("\u043A\u0430\u043A\u043E\u0435 \u043F\u043E\u043B\u0435 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u044D\u0442\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u043E: verify | conventions | brief | env | zones | spine"),
11011
+ value: z.unknown().describe("\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \u2014 \u0442\u043E\u0439 \u0436\u0435 \u0444\u043E\u0440\u043C\u044B, \u0447\u0442\u043E \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0435 (verify: {commands,green}, env: {database,migrations,vars}, conventions: {commitLanguage,slugInCommit,baseBranch}, brief: \u0441\u0442\u0440\u043E\u043A\u0430, zones: \u043C\u0430\u0441\u0441\u0438\u0432 \u0437\u043E\u043D, spine: \u043C\u0430\u0441\u0441\u0438\u0432 \u043F\u0443\u0442\u0435\u0439)"),
11012
+ why: z.string().describe("\u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0435\u0433\u043E \u044D\u0442\u043E \u0441\u0442\u043E\u0438\u043B\u043E \u2014 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0438 \u043E\u0442\u043B\u0438\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E\u0442 \u043E\u0442\u043F\u0438\u0441\u043A\u0438")
11013
+ })
11014
+ ).optional().describe(
11015
+ `\u041F\u0420\u0410\u0412\u0418\u041B\u041E \u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0418, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u0432 \u043D\u0451\u043C \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E (TASWM-431) \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${DRONE_RULES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0442\u044B \u0420\u0415\u0410\u041B\u042C\u041D\u041E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u043F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u0432\u0440\u0435\u043C\u044F (\xAB\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u0436\u0434\u0443\u0442 \u043F\u043E\u0441\u0442\u0433\u0440\u0435\u0441 \u043D\u0430 5433\xBB). \u041E\u0431\u0449\u0435\u0435 \u043C\u0435\u0441\u0442\u043E (\xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 pnpm test\xBB) \u043F\u0440\u0430\u0432\u0438\u043B\u043E\u043C \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F. \u0423\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432 \u0441\u0432\u043E\u0451\u043C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435.`
11016
+ ),
11017
+ rule_verdicts: z.array(z.object({ field: z.enum([...RULE_FIELDS]), apply: z.boolean() })).optional().describe(
11018
+ "\u0422\u041E\u041B\u042C\u041A\u041E \u0414\u041B\u042F \u0420\u0415\u0412\u042C\u042E\u0415\u0420\u0410 (TASWM-431): \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u0440\u0438\u043D\u0451\u0441 \u0434\u0440\u043E\u043D (\u043E\u043D\u0438 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u044B \u0432 \u0442\u0432\u043E\u0451\u043C \u043F\u0440\u043E\u043C\u043F\u0442\u0435). apply=true \u2014 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0435, apply=false \u2014 \u043E\u0442\u043F\u0438\u0441\u043A\u0430, \u0441\u043D\u044F\u0442\u044C. verify/conventions/brief \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u0441\u044F \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, env/zones/spine \u0443\u0439\u0434\u0443\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0432 \u043B\u044E\u0431\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435."
10867
11019
  )
10868
11020
  },
10869
- guard(async ({ pr_url, verdict, summary, screenshots }) => {
11021
+ guard(async ({ pr_url, verdict, summary, screenshots, rules, rule_verdicts }) => {
10870
11022
  const run = await store.myRun();
11023
+ let repoFullNameCache = null;
11024
+ const repoFullNameOnce = () => repoFullNameCache ??= remoteRepoFullName(opts.worktree ?? process.cwd()).catch(() => null);
11025
+ const offerRules = async (taskSlug) => {
11026
+ if (!rules?.length) return "";
11027
+ if (!opts.taskless?.proposeRepoRules) return "\n\n\u26A0\uFE0F \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D.";
11028
+ const repoFullName = await repoFullNameOnce();
11029
+ if (!repoFullName) {
11030
+ await store.addEvent(null, "step", "\u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u043F\u043E origin");
11031
+ return "\n\n\u26A0\uFE0F \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u2014 \u0441\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u0432 PR.";
11032
+ }
11033
+ const text = await opts.taskless.proposeRepoRules({ repoFullName, rules, ...run?.name ? { by: run.name } : {}, ...taskSlug ? { taskSlug } : {} }).catch((e) => `\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: ${e instanceof Error ? e.message : String(e)}`);
11034
+ const line = text ?? "\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E (\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0438\u043B\u0438 \u0442\u0443\u043B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D)";
11035
+ await store.addEvent(null, "step", `\u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043E\u0442 \u0434\u0440\u043E\u043D\u0430: ${line}`);
11036
+ return `
11037
+
11038
+ \u{1F4CC} ${line}`;
11039
+ };
11040
+ const decideRules = async (taskSlug) => {
11041
+ if (!rule_verdicts?.length) return "";
11042
+ if (!opts.taskless?.decideRepoRules) return "";
11043
+ const repoFullName = await repoFullNameOnce();
11044
+ if (!repoFullName) {
11045
+ await store.addEvent(null, "step", "\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u043F\u043E origin");
11046
+ return "\n\n\u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u041D\u0415 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo.";
11047
+ }
11048
+ const text = await opts.taskless.decideRepoRules({ repoFullName, decisions: rule_verdicts, ...run?.name ? { by: run.name } : {}, ...taskSlug ? { taskSlug } : {} }).catch((e) => `\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${e instanceof Error ? e.message : String(e)}`);
11049
+ const line = text ?? "\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D (\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0438\u043B\u0438 \u0442\u0443\u043B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D)";
11050
+ await store.addEvent(null, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F: ${line}`);
11051
+ return `
11052
+
11053
+ \u{1F4CC} ${line}`;
11054
+ };
10871
11055
  const reportSlug = (slug) => opts.expectedTaskSlug ?? slug;
10872
11056
  const uploadShots = async () => {
10873
11057
  if (!screenshots?.length) return { attachments: [], warning: "" };
@@ -10953,13 +11137,14 @@ ${notFoundText}` : notFoundText }],
10953
11137
  commentWarning = opts.taskless ? "\u0437\u0430\u0434\u0430\u0447\u0430 \u0440\u0435\u0432\u044C\u044E \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430" : "\u043A\u043B\u0438\u0435\u043D\u0442 Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D";
10954
11138
  await store.addEvent(run.reviews_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
10955
11139
  }
11140
+ const rulesNote = await decideRules(reviewedSlug);
10956
11141
  const trustedPrUrl = reviewed?.pr_url ?? pr_url;
10957
11142
  await store.updateRun(null, { status: "review", pr_url: trustedPrUrl });
10958
11143
  await store.addEvent(null, "pr_opened", trustedPrUrl);
10959
11144
  const commentNote = commentWarning ? `
10960
11145
 
10961
11146
  \u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u041D\u0415 \u0443\u0435\u0445\u0430\u043B (${commentWarning}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0435. \u041F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0435\u0433\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR: \`gh pr comment ${trustedPrUrl} --body "${verdictText} \u2026"\`.` : "";
10962
- return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E${shotsNote(shots)}${commentNote}` }] };
11147
+ return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E${shotsNote(shots)}${commentNote}${rulesNote}` }] };
10963
11148
  }
10964
11149
  if (iAmVerifier(run) && run?.verifies_run_id == null) {
10965
11150
  return {
@@ -11066,22 +11251,24 @@ ${report}${shots.warning}`,
11066
11251
  await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {} });
11067
11252
  await store.addEvent(null, "pr_opened", pr_url);
11068
11253
  await postReport(reportSlug(run.task_slug), t.cycle ?? run.review_cycle ?? 0);
11254
+ const rulesNote = await offerRules(reportSlug(run.task_slug));
11069
11255
  if (t.isFixIteration) {
11070
11256
  await store.addEvent(
11071
11257
  null,
11072
11258
  "step",
11073
11259
  `\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F: \u043D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043F\u043E\u0441\u043B\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB \u2014 \u043E\u0442\u043A\u0440\u044B\u0442 \u0446\u0438\u043A\u043B \u0440\u0435-\u0440\u0435\u0432\u044C\u044E #${t.cycle}`
11074
11260
  );
11075
- return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F)${shotsNote(shots)}` }] };
11261
+ return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F)${shotsNote(shots)}${rulesNote}` }] };
11076
11262
  }
11077
- return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}` }] };
11263
+ return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${rulesNote}` }] };
11078
11264
  }
11079
11265
  await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {} });
11080
11266
  await store.addEvent(null, "pr_opened", pr_url);
11081
11267
  const fallbackSlug = reportSlug(void 0);
11082
11268
  if (fallbackSlug) await postReport(fallbackSlug, void 0);
11083
11269
  else await store.addEvent(null, "step", `\u043E\u0442\u0447\u0451\u0442 \u0434\u0440\u043E\u043D\u0430 (\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432 \u0441\u0442\u043E\u0440\u0435): ${report}`);
11084
- return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}` }] };
11270
+ const fallbackRulesNote = await offerRules(fallbackSlug);
11271
+ return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${fallbackRulesNote}` }] };
11085
11272
  }
11086
11273
  await store.updateRun(null, { status: "done" });
11087
11274
  await store.addEvent(null, "done", pr_url ? `\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0431\u0435\u0437 GitHub-PR (${pr_url})` : "\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0431\u0435\u0437 GitHub-PR");
@@ -12112,13 +12299,35 @@ function makeExclusiveReader(opts) {
12112
12299
  });
12113
12300
  }
12114
12301
  function makeAutoReviewReader(opts) {
12115
- return makeRepoValueReader({
12116
- fetch: opts.fetch,
12117
- fallback: opts.fallback,
12118
- valid: (v) => typeof v === "boolean",
12119
- ...opts.ttlMs !== void 0 ? { ttlMs: opts.ttlMs } : {},
12120
- ...opts.now !== void 0 ? { now: opts.now } : {}
12121
- });
12302
+ const ttl = opts.ttlMs ?? 6e4;
12303
+ const now = opts.now ?? Date.now;
12304
+ const log = opts.log ?? ((msg) => console.warn(msg));
12305
+ const cache = /* @__PURE__ */ new Map();
12306
+ const lastSource = /* @__PURE__ */ new Map();
12307
+ const say = (repo, source, msg) => {
12308
+ if (lastSource.get(repo) === source) return;
12309
+ lastSource.set(repo, source);
12310
+ log(msg);
12311
+ };
12312
+ return async (repo) => {
12313
+ const t = now();
12314
+ const hit = cache.get(repo);
12315
+ if (hit && t - hit.at < ttl) return hit.value;
12316
+ const live = await opts.fetch(repo).catch(() => null);
12317
+ if (typeof live === "boolean") {
12318
+ const changed = hit?.value !== live;
12319
+ cache.set(repo, { value: live, at: t });
12320
+ if (changed) lastSource.delete(repo);
12321
+ say(repo, "live", `[autoReview] ${repo}: \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u0430 \u2014 autoReview=${live}`);
12322
+ return live;
12323
+ }
12324
+ if (hit) {
12325
+ say(repo, "stale", `[autoReview] ${repo}: \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E \u2014 \u0434\u0435\u0440\u0436\u0443 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 autoReview=${hit.value}`);
12326
+ return hit.value;
12327
+ }
12328
+ say(repo, "env", `[autoReview] ${repo}: \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u0430 \u043D\u0438 \u0440\u0430\u0437\u0443 \u2014 \u0431\u0435\u0440\u0443 env SWARM_AUTO_REVIEW=${opts.fallback}`);
12329
+ return opts.fallback;
12330
+ };
12122
12331
  }
12123
12332
  function makeDroneCapReader(opts) {
12124
12333
  const read = makeRepoValueReader({
@@ -12619,7 +12828,7 @@ async function changedPaths(worktree, opts = {}) {
12619
12828
  // package.json
12620
12829
  var package_default = {
12621
12830
  name: "@taskless-app/swarm",
12622
- version: "0.6.15",
12831
+ version: "0.6.17",
12623
12832
  type: "module",
12624
12833
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
12625
12834
  keywords: [
@@ -12669,7 +12878,7 @@ var package_default = {
12669
12878
  execa: "^10.0.0",
12670
12879
  zod: "3.25.76"
12671
12880
  },
12672
- gitHead: "5bc8e0c067de58fd72b0eb434e80ad29c7a00223"
12881
+ gitHead: "a4ed164a36295bd7ebc27e7f6fe73809e09b142a"
12673
12882
  };
12674
12883
 
12675
12884
  // src/version.ts
@@ -15553,6 +15762,7 @@ async function spawnVerifiersForOpenPrs(deps, stormActive) {
15553
15762
  }
15554
15763
  if (deps.resourcesOk && !deps.resourcesOk()) continue;
15555
15764
  if (deps.reviewCap !== void 0 && countActiveVerifiers(deps.db) >= deps.reviewCap) continue;
15765
+ if (!await projectWantsChecks(deps, run)) continue;
15556
15766
  const st = await deps.prStatus(run.pr_url).catch(() => null);
15557
15767
  if (prLiveness(st) !== "open") continue;
15558
15768
  if (st?.isDraft || st?.conflicting) continue;
@@ -15670,6 +15880,12 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
15670
15880
  "4b. \u0421\u041D\u0418\u041C\u0410\u0419 \u041F\u0420\u041E\u0414\u0423\u041A\u0422, \u0410 \u041D\u0415 \u041F\u0420\u0415\u0412\u042C\u042E-\u0421\u0422\u0420\u0410\u041D\u0418\u0426\u0423 (TASWM-404). \u0414\u0435\u043C\u043E-\u0440\u043E\u0443\u0442 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u0430 (`/dev/*`, \u043F\u0435\u0441\u043E\u0447\u043D\u0438\u0446\u0430, \u0441\u0442\u043E\u0440\u0438 \u0432 \u043C\u043E\u0431\u0438\u043B\u044C\u043D\u043E\u043C \u0444\u0440\u0435\u0439\u043C\u0435) \u2014 \u041D\u0415 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u043C\u043E\u0431\u0438\u043B\u044C\u043D\u043E\u0439 \u0432\u0451\u0440\u0441\u0442\u043A\u0438: \u0443 \u0442\u0430\u043A\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0441\u0432\u043E\u044F \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0430, \u0435\u0451 \u043C\u0435\u043D\u044E \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u0443\u0437\u043A\u043E\u043C \u044D\u043A\u0440\u0430\u043D\u0435, \u0438 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u043C\u0443 \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043F\u043E\u043B\u043E\u0441\u0430 \u0432 \u043F\u0430\u0440\u0443 \u0441\u043E\u0442\u0435\u043D \u043F\u0438\u043A\u0441\u0435\u043B\u0435\u0439 \u0432\u043C\u0435\u0441\u0442\u043E \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0439 \u0448\u0438\u0440\u0438\u043D\u044B. \u041F\u043E \u0442\u0430\u043A\u043E\u043C\u0443 \u0441\u043D\u0438\u043C\u043A\u0443 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u043D\u0438 \u0448\u0438\u0440\u0438\u043D\u044B \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u0438 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u044F \u0448\u0430\u043F\u043A\u0438, \u043D\u0438 \u043D\u0430\u043F\u043E\u043B\u0437\u0430\u043D\u0438\u044F \u043B\u0438\u043F\u043A\u0438\u0445 \u043F\u043E\u0434\u043F\u0438\u0441\u0435\u0439 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u0412\u0421\u0415\u0413\u0414\u0410 \u0438 \u043D\u0435 \u043B\u043E\u0432\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E (\u0442\u0430\u043A \u0441\u0434\u0430\u043B\u0438 \u0441\u043B\u043E\u043C\u0430\u043D\u043D\u0443\u044E \u043C\u043E\u0431\u0438\u043B\u0443 \u0432 TASWM-403, \u0441\u043A\u0440\u0438\u043D \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u0431\u044B\u043B \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D). \u0421\u043D\u0438\u043C\u0430\u0439 \u0420\u0415\u0410\u041B\u042C\u041D\u042B\u0419 \u0440\u043E\u0443\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043F\u043E\u0434 \u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439; \u043A\u0430\u043A \u044D\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0432 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u0432 \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u0430\u0445. \u041F\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u043D\u043E \u0440\u043E\u0432\u043D\u043E \u0442\u043E\u0433\u0434\u0430, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u0430\u0432\u0438\u0448\u044C \u0441\u0430\u043C \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442 \u0432\u043D\u0435 \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F.",
15671
15881
  "\u0427\u0438\u0441\u0442\u043E \u0431\u044D\u043A\u0435\u043D\u0434/\u043B\u043E\u0433\u0438\u043A\u0443/API/\u0434\u0435\u043C\u043E\u043D/CI/\u0442\u0438\u043F\u044B \u2014 \u0448\u0430\u0433\u0438 3\u20134 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438 (\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u041D\u0415 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 \u0438 \u0441\u043A\u0440\u0438\u043D\u044B \u043D\u0435 \u0441\u043D\u0438\u043C\u0430\u0439): \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B (\u0448\u0430\u0433 1) + e2e \u0432 CI \u044D\u0442\u043E \u0443\u0436\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u044E\u0442; \u044D\u043A\u043E\u043D\u043E\u043C\u0438\u043C \u0432\u0440\u0435\u043C\u044F \u0438 \u0442\u043E\u043A\u0435\u043D\u044B.",
15672
15882
  "\u0422\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E \u0437\u043E\u0432\u0438 done(pr_url), \u0438 \u0432 \u0441\u0434\u0430\u0447\u0435 \u043F\u043E\u043A\u0430\u0436\u0438 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E (\u0437\u0435\u043B\u0451\u043D\u044B\u0439 \u0432\u044B\u0432\u043E\u0434/\u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435), \u0430 \u043D\u0435 \xAB\u0434\u043E\u043B\u0436\u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C\xBB.",
15883
+ // TASWM-431: половина правил репозитория в коде не написана, и выясняет их ДРОН —
15884
+ // а закрытая задача уносила это знание с собой. Планка намеренно высокая: «не
15885
+ // больше двух» и «обо что споткнулся» — единственное, что отделяет правило от
15886
+ // пересказа README, а лишнее правило дороже отсутствующего (его читает каждый
15887
+ // следующий дрон).
15888
+ `\u{1F4CC} \u0421\u041F\u041E\u0422\u041A\u041D\u0423\u041B\u0421\u042F \u041E \u041D\u0415\u0417\u0410\u041F\u0418\u0421\u0410\u041D\u041D\u041E\u0415 \u2014 \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418 \u041F\u0420\u0410\u0412\u0418\u041B\u041E. \u041F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u0432\u0440\u0435\u043C\u044F \u043D\u0430 \u0442\u043E, \u0447\u0435\u0433\u043E \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0438\u0433\u0434\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u0436\u0434\u0443\u0442 \u043F\u043E\u0441\u0442\u0433\u0440\u0435\u0441 \u043D\u0430 5433; \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F; \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0437\u0435\u043B\u0451\u043D\u0430\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0441 \u0442\u0430\u043A\u0438\u043C \u0444\u043B\u0430\u0433\u043E\u043C) \u2014 \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u044D\u0442\u043E \u0432 done: \`rules: [{ field: "verify"|"conventions"|"brief"|"env"|"zones"|"spine", value: <\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438>, why: "\u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0435\u0433\u043E \u044D\u0442\u043E \u0441\u0442\u043E\u0438\u043B\u043E" }]\`. \u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${DRONE_RULES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0422\u041E\u041B\u042C\u041A\u041E \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F: \xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439, \u043E\u043D\u0430 \u0432 README\xBB \u2014 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E, \u0430 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 README. \u0423\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E; \u0440\u0435\u0448\u0438\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C. \u041D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`,
15673
15889
  "\u0421\u043A\u0438\u043B\u043B\u044B \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u043A \u0442\u0435\u0431\u0435 \u0414\u0412\u0423\u041C\u042F \u043D\u0430\u0431\u043E\u0440\u0430\u043C\u0438, \u0438 \u043E\u0431\u0430 \u0443\u0436\u0435 \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D\u044B (\u0441\u043F\u0430\u0432\u043D \u0438\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u043D \u043E\u0442 \u044E\u0437\u0435\u0440\u0441\u043A\u043E\u0433\u043E ~/.claude, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u041B\u0418\u0427\u041D\u042B\u0415 \u0441\u043A\u0438\u043B\u043B\u044B \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0442\u0435\u0431\u0435 \u043D\u0435 \u0432\u0438\u0434\u043D\u044B). \u2460 \u0420\u043E\u0439\u043D\u044B\u0435 `swarm-drone:*` \u0435\u0434\u0443\u0442 \u0441 \u0420\u041E\u0415\u041C \u0432 \u043B\u044E\u0431\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u2014 \u0434\u0438\u0441\u0446\u0438\u043F\u043B\u0438\u043D\u0430, \u0432\u0435\u0440\u043D\u0430\u044F \u0432\u0435\u0437\u0434\u0435: swarm-save-often (\u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0438\u0440\u0430\u0435\u0442 \u0441 \u0434\u0440\u043E\u043D\u043E\u043C), swarm-merge-main (\u0431\u0430\u0437\u043E\u0432\u0443\u044E \u0432\u0435\u0442\u043A\u0443 \u0432\u043B\u0438\u0432\u0430\u0442\u044C, \u0430 \u043D\u0435 \u0440\u0435\u0431\u0435\u0439\u0437\u0438\u0442\u044C \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u0443\u044E), swarm-await-process (\u0436\u0434\u0430\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u0435, \u043D\u0435 sleep), swarm-verify-green (\u0447\u0442\u043E \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C), systematic-debugging (\u043F\u0435\u0440\u0432\u043E\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0434\u043E \u0444\u0438\u043A\u0441\u0430). \u2461 \u0421\u043A\u0438\u043B\u043B\u044B \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F (.claude/skills, \u0441\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430) \u2014 \u0437\u043D\u0430\u043D\u0438\u044F \u043E\u0431 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435: \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0441\u0442\u0435\u043A, \u043A\u0430\u043A \u0433\u043E\u043D\u044F\u0442\u044C \u0442\u0435\u0441\u0442\u044B \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u043E\u0435, UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430. \u041F\u043E\u043B\u044C\u0437\u0443\u0439\u0441\u044F \u043E\u0431\u043E\u0438\u043C\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u0438\u0437\u043E\u0431\u0440\u0435\u0442\u0430\u0439; \u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u043D\u0438\u044F \u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0431\u0435\u043B \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u043E\u0432, \u0441\u043A\u0430\u0436\u0438 note(), \u0430 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B."
15674
15890
  ];
15675
15891
  if (run.decision) {
@@ -15820,8 +16036,9 @@ var REVIEW_MISSION_BLOCK = [
15820
16036
  "\u2022 \u041D\u0430\u0445\u043E\u0434\u043A\u0430 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0421\u0426\u0415\u041D\u0410\u0420\u0418\u0415\u041C: \xAB\u0442\u0430\u043A\u043E\u0439 \u0432\u0445\u043E\u0434 \u0438\u043B\u0438 \u0442\u0430\u043A\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u2192 \u0432\u043E\u0442 \u0442\u0430\u043A\u043E\u0439 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\xBB. \xAB\u0412\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\xBB, \xAB\u0441\u0442\u043E\u0438\u0442 \u043F\u0435\u0440\u0435\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C\xBB, \xAB\u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043F\u043E\u043A\u0440\u044B\u0442\u044C \u0442\u0435\u0441\u0442\u0430\u043C\u0438\xBB \u2014 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0438; \u0442\u0430\u043A\u043E\u0435 \u043D\u0435 \u043F\u0438\u0448\u0438.",
15821
16037
  "\u2022 \u041D\u043E\u043B\u044C \u043D\u0430\u0445\u043E\u0434\u043E\u043A \u2014 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442, \u043D\u043E \u0442\u043E\u0433\u0434\u0430 \u043D\u0430\u0437\u043E\u0432\u0438, \u0427\u0422\u041E \u0438\u043C\u0435\u043D\u043D\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u043A\u0430\u043A\u0438\u043C \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0435\u043C. APPROVE \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u043E\u0439 \u0447\u0438\u0442\u0430\u0435\u0442 \u043A\u0430\u043A \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437, \u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 PR \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u0440\u0443\u0447\u043D\u0443\u044E."
15822
16038
  ].join("\n");
15823
- function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
16039
+ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY, pendingRules = []) {
15824
16040
  const name = reviewer.name || `#${reviewer.id}`;
16041
+ const rulesBlock = renderPendingRulesBlock(pendingRules);
15825
16042
  const prUrl = reviewRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
15826
16043
  const classesStep = reviewRun.review_cycle > 0 ? "" : "\u043F\u0440\u043E\u0439\u0434\u0438 \u043A\u043B\u0430\u0441\u0441\u044B \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u0432 (\u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u0442\u043E\u0436\u0435), ";
15827
16044
  return [
@@ -15863,6 +16080,7 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
15863
16080
  `2. \u0427\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C (\u0437\u0435\u043B\u0451\u043D\u044B\u0435 \u0438\u043B\u0438 \u0441 \u043A\u0440\u0430\u0441\u043D\u044B\u043C) \u0438\u043B\u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0438\u0445 \u0432\u043E\u0432\u0441\u0435 \u043D\u0435\u0442 \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0430 \u0441\u0430\u043C PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u0435\u0433\u043E \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041D\u0430\u0447\u043D\u0438 \u0441 **APPROVE** \u0438\u043B\u0438 **REQUEST CHANGES** \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u043C\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0430: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B, \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B; \u0434\u043B\u044F REQUEST CHANGES \u2014 \u0442\u043E\u0447\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B/\u0441\u0442\u0440\u043E\u043A\u0438 \u0438 \u0447\u0442\u043E \u0447\u0438\u043D\u0438\u0442\u044C.`
15864
16081
  ],
15865
16082
  `3. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "approve"|"changes", summary: "\u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B") \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \u0440\u0430\u0437\u043D\u0435\u0441\u0451\u0442 summary \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 \u0437\u0430\u0434\u0430\u0447\u0443-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A, \u0430 verdict \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D (\u0431\u0435\u0439\u0434\u0436 \u0432 \u0432\u0435\u0431\u0435). \u0422\u044B \u0441\u0430\u043C \u0440\u0435\u0432\u044C\u044E \u041D\u0415 \u043F\u043E\u0434\u043B\u0435\u0436\u0438\u0448\u044C \u2014 \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0438 \u043D\u0435\u0442.`,
16083
+ ...rulesBlock ? ["", rulesBlock] : [],
15866
16084
  '\u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D: \u0442\u0440\u0435\u0442\u044C\u0435\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442, \u0438 \u0443\u0441\u043F\u0435\u0445\u043E\u043C \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F (TASWM-375) \u2014 done() \u0431\u0435\u0437 verdict \u043E\u0442\u043A\u0430\u0436\u0435\u0442, \u0430 \u043F\u0440\u043E\u0433\u043E\u043D, \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u0440\u043E\u0439 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 "\u0443\u043F\u0430\u043B" \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043D\u043E\u0432\u043E. \u041E\u0442\u043A\u0430\u0437\u0430\u043B done() \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR (\u0448\u0430\u0433 2): \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442 \u0435\u0433\u043E \u043E\u0442\u0442\u0443\u0434\u0430.'
15867
16085
  ].join("\n");
15868
16086
  }
@@ -16177,7 +16395,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
16177
16395
  if (stats.count >= CI_WAKE_LIMIT || !canContinue) {
16178
16396
  const why = !canContinue ? "worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : `${CI_WAKE_LIMIT} \u043F\u043E\u0431\u0443\u0434(\u043E\u043A) \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 CI \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E`;
16179
16397
  deps.db.updateRun(reviewer.id, { status: "stalled" });
16180
- const freshWanted = await projectWantsReviewer(deps, run);
16398
+ const freshWanted = await projectWantsChecks(deps, run);
16181
16399
  const freshPlan = freshWanted ? "\u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR" : "\u0441\u0432\u0435\u0436\u0435\u0433\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E: \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E";
16182
16400
  deps.db.addEvent(reviewer.id, "stalled", `CI \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${why} \u2014 ${freshPlan}`);
16183
16401
  narrate(deps, `${droneLabel(reviewer)} \u0436\u0434\u0430\u043B CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}) \u2014 ${freshPlan}.`);
@@ -16221,9 +16439,12 @@ function worktreeAndTranscriptAlive(deps, worktree) {
16221
16439
  const transcript = deps.hasTranscript ?? executorHasSession;
16222
16440
  return exists(worktree) && transcript(worktree);
16223
16441
  }
16224
- async function projectWantsReviewer(deps, run) {
16442
+ async function projectWantsChecks(deps, run) {
16225
16443
  if (!deps.autoReviewForRepo) return true;
16226
- return await deps.autoReviewForRepo(effectiveRepo(deps, run)).catch(() => true);
16444
+ return await deps.autoReviewForRepo(effectiveRepo(deps, run)).catch((e) => {
16445
+ console.warn(`[overmind] autoReview \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (${effectiveRepo(deps, run)}) \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u043A\u0430\u043A \u0440\u0430\u043D\u044C\u0448\u0435:`, e);
16446
+ return true;
16447
+ });
16227
16448
  }
16228
16449
  async function spawnFreshReviewerAfterStall(deps, run, why) {
16229
16450
  if (!deps.spawnReviewer) return;
@@ -17081,14 +17302,6 @@ async function tickOnce(deps) {
17081
17302
  }
17082
17303
  }
17083
17304
  }
17084
- if (deps.heartbeat) {
17085
- const hb = deps.heartbeat;
17086
- const sinceLast = hb.lastAt == null ? Infinity : now - hb.lastAt;
17087
- if (sinceLast >= hb.intervalMs) {
17088
- hb.lastAt = now;
17089
- await hb.push();
17090
- }
17091
- }
17092
17305
  if (deps.droneBridge) {
17093
17306
  const br = deps.droneBridge;
17094
17307
  const sinceLast = br.lastAt == null ? Infinity : now - br.lastAt;
@@ -17309,7 +17522,7 @@ async function tickOnce(deps) {
17309
17522
  // не порядком веток в 900-строчной функции: увидели живого — значит закрывать его
17310
17523
  // рано, спавнить второго нельзя.
17311
17524
  liveOrWaitingReviewers(deps.db, run.id).length === 0;
17312
- if (reviewerGatesOk && deps.spawnReviewer && await projectWantsReviewer(deps, run)) {
17525
+ if (reviewerGatesOk && deps.spawnReviewer && await projectWantsChecks(deps, run)) {
17313
17526
  if (run.review_cycle >= REVIEW_CYCLE_LIMIT) {
17314
17527
  if (!deps.db.events(run.id).some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_CYCLE_ESCALATION_MARK))) {
17315
17528
  deps.db.addEvent(run.id, "step", `${REVIEW_CYCLE_ESCALATION_MARK}: ${run.review_cycle} \u0446\u0438\u043A\u043B(\u043E\u0432) \u0440\u0435\u0432\u044C\u044E-\u0444\u0438\u043A\u0441\u043E\u0432 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
@@ -17637,7 +17850,7 @@ async function tickOnce(deps) {
17637
17850
  if (deps.autoReviewForRepo && az.autoReviewEnabled) {
17638
17851
  for (const r of deps.db.listRuns("review")) {
17639
17852
  if (!r.pr_url || autoReviewSnapshot.has(r.repo)) continue;
17640
- autoReviewSnapshot.set(r.repo, await deps.autoReviewForRepo(effectiveRepo(deps, r)).catch(() => true));
17853
+ autoReviewSnapshot.set(r.repo, await projectWantsChecks(deps, r));
17641
17854
  }
17642
17855
  }
17643
17856
  const candidates = [
@@ -17842,7 +18055,8 @@ async function spawnReviewer(deps, reviewRun, files) {
17842
18055
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
17843
18056
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
17844
18057
  const attachments = await deps.taskAttachments?.(reviewRun.task_slug, wt, reviewRun.workspace_id);
17845
- const prompt = buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments, verifyMode);
18058
+ const pendingRules = await deps.pendingRepoRules?.(repo, reviewRun.task_slug).catch(() => []) ?? [];
18059
+ const prompt = buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments, verifyMode, pendingRules);
17846
18060
  const pid = deps.launch({ worktree: wt, run: reviewer, prompt });
17847
18061
  deps.db.updateRun(reviewer.id, { status: "running", worktree_path: wt, branch, pid, pr_url: reviewRun.pr_url });
17848
18062
  deps.db.addEvent(reviewRun.id, "step", `\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E: \u043F\u043E\u0434\u043D\u044F\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${reviewer.name} (#${reviewer.id})`);
@@ -18283,6 +18497,8 @@ async function runDaemon(opts) {
18283
18497
  }
18284
18498
  let onHostCommands = () => {
18285
18499
  };
18500
+ const tickIntervalMs = opts.intervalMs ?? 4e3;
18501
+ const tickHealth = initialTickHealth(Date.now());
18286
18502
  let heartbeat;
18287
18503
  if (SWARM_BACKEND === "client" && db instanceof SwarmClient && SWARM_PROD_HEARTBEAT_MS > 0) {
18288
18504
  const client = db;
@@ -18306,7 +18522,12 @@ async function runDaemon(opts) {
18306
18522
  // отвергают», а 12.08 входа не делали вовсе, и по отказу машина числилась бы
18307
18523
  // вошедшей. Адрес — WEB_PORT (он же SWARM_WEB_PORT из окружения, на нём и
18308
18524
  // поднимается морда): зашитый 7420 отправил бы человека туда, где ничего нет.
18309
- { signedIn: signedInToTaskless(), webUrl: `http://127.0.0.1:${WEB_PORT}` }
18525
+ { signedIn: signedInToTaskless(), webUrl: `http://127.0.0.1:${WEB_PORT}` },
18526
+ // TASWM-452: и ПРИЗНАК ЖИВОГО ТИКА — номер последнего завершённого прохода
18527
+ // реконсайла и его возраст. Возраст, а не момент: часы машины и прода
18528
+ // расходятся (ноутбук после сна — на минуты), а сверяет признак прод со своим
18529
+ // настенным временем. Не «демон запущен», а «цикл прокрутился».
18530
+ tickPulse(tickHealth, Date.now(), tickIntervalMs)
18310
18531
  );
18311
18532
  if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
18312
18533
  if (reply.commands.length) onHostCommands(reply.commands);
@@ -18431,8 +18652,13 @@ async function runDaemon(opts) {
18431
18652
  let exclusiveProject;
18432
18653
  let projectDroneCap;
18433
18654
  let autoReviewProject;
18655
+ let pendingRepoRules;
18434
18656
  if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
18435
18657
  const client = db;
18658
+ pendingRepoRules = async (repo, taskSlug) => {
18659
+ const repoFullName = await remoteRepoFullName(repo).catch(() => null);
18660
+ return repoFullName ? client.repoPendingRules(repoFullName, taskSlug) : [];
18661
+ };
18436
18662
  verifyModeReader = makeVerifyModeReader({
18437
18663
  fetch: async (repo) => {
18438
18664
  const repoFullName = await remoteRepoFullName(repo).catch(() => null);
@@ -18504,6 +18730,9 @@ async function runDaemon(opts) {
18504
18730
  // иначе ревьюер судит проект с verifyMode=local по правилам ci и блокирует PR
18505
18731
  // за отсутствие CI, которого у проекта нет и не предполагается.
18506
18732
  ...verifyModeReader ? { verifyMode: verifyModeReader } : {},
18733
+ // TASWM-431: правила о репозитории, принесённые дроном этого прогона, — ревьюер
18734
+ // решает их тем же вердиктом (см. buildReviewerPrompt).
18735
+ ...pendingRepoRules ? { pendingRepoRules } : {},
18507
18736
  // TASWM-283: те же вложения задачи, что у исполнителя — ревьюер без макета
18508
18737
  // не может проверить «сделано ли как просили».
18509
18738
  ...tl?.fetchFile ? {
@@ -18860,8 +19089,6 @@ async function runDaemon(opts) {
18860
19089
  // Периодический re-hydrate зеркала (client-режим): ключ есть только когда сконфигурен.
18861
19090
  ...rehydrate ? { rehydrate } : {},
18862
19091
  ...droneBridge ? { droneBridge } : {},
18863
- // Пульс overmind в прод (client-режим, TASWM-129): ключ есть только когда сконфигурен.
18864
- ...heartbeat ? { heartbeat } : {},
18865
19092
  // Сверка кода демона с main (TASWM-251): ключ есть только когда entryFile в git-репо.
18866
19093
  ...daemonVersion ? { daemonVersion } : {},
18867
19094
  // Карта репозиториев машины (TASWM-336): ключ есть только в client-режиме с непустым TTL.
@@ -19201,8 +19428,30 @@ async function runDaemon(opts) {
19201
19428
  console.error("[overmind] \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u041D\u0415 \u0432\u044B\u0431\u0440\u0430\u043D \u2014 \u043E\u043D \u043D\u0430\u0432\u044F\u0437\u0430\u043D \u043E\u0442\u043A\u0430\u0437\u043E\u043C: \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443, \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox.");
19202
19429
  }
19203
19430
  console.log(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430: http://127.0.0.1:${web.port} \u2190 \u043E\u0442\u043A\u0440\u043E\u0439 \u0438 \u0432\u043E\u0439\u0434\u0438 \u0447\u0435\u0440\u0435\u0437 Taskless`);
19204
- const interval = opts.intervalMs ?? 4e3;
19431
+ const interval = tickIntervalMs;
19205
19432
  writeHeartbeat(HEARTBEAT_PATH, Date.now());
19433
+ if (heartbeat) {
19434
+ const hb = heartbeat;
19435
+ let inFlight = false;
19436
+ setInterval(() => {
19437
+ if (inFlight) return;
19438
+ inFlight = true;
19439
+ void hb.push().catch((e) => console.error("[overmind] \u043F\u0443\u043B\u044C\u0441 \u0432 \u043F\u0440\u043E\u0434 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e)).finally(() => {
19440
+ inFlight = false;
19441
+ });
19442
+ }, hb.intervalMs);
19443
+ }
19444
+ setInterval(() => {
19445
+ if (restart.phase === "restarting") return;
19446
+ const now = Date.now();
19447
+ if (!tickSelfHealDue(tickHealth, now, interval)) return;
19448
+ const age = Math.round(tickAgeMs(tickHealth, now) / 1e3);
19449
+ console.error(
19450
+ `[overmind] \u0442\u0438\u043A \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B: \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0439 \u043F\u0440\u043E\u0445\u043E\u0434 #${tickHealth.seq} \u0431\u044B\u043B ${age}\u0441 \u043D\u0430\u0437\u0430\u0434${tickHealth.lastError ? ` (\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430: ${tickHealth.lastError})` : ""} \u2014 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0435\u0431\u044F`
19451
+ );
19452
+ narrator.narrate(`\u{1FA7A} \u0426\u0438\u043A\u043B \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B (\u043F\u0440\u043E\u0445\u043E\u0434 #${tickHealth.seq}, ${age}\u0441 \u0431\u0435\u0437 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F) \u2014 \u0434\u0435\u043C\u043E\u043D \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0441\u0435\u0431\u044F \u0441\u0430\u043C.`);
19453
+ performRestart(liveSessionIds(db, pidAlive));
19454
+ }, Math.max(1e3, interval));
19206
19455
  if (deps.sweep) {
19207
19456
  deps.sweep.lastAt = Date.now();
19208
19457
  await deps.sweep.run().catch((e) => console.error("[overmind] \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u044B\u0439 sweep \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e));
@@ -19211,7 +19460,9 @@ async function runDaemon(opts) {
19211
19460
  deps.daemonVersion.lastAt = Date.now();
19212
19461
  await deps.daemonVersion.run().catch((e) => console.error("[overmind] \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u0430\u044F \u0441\u0432\u0435\u0440\u043A\u0430 \u0432\u0435\u0440\u0441\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C:", e));
19213
19462
  }
19463
+ tickHealth.startedAt = Date.now();
19214
19464
  for (; ; ) {
19465
+ let tickError;
19215
19466
  try {
19216
19467
  writeHeartbeat(HEARTBEAT_PATH, Date.now());
19217
19468
  await tickOnce(deps);
@@ -19221,8 +19472,10 @@ async function runDaemon(opts) {
19221
19472
  else onRestartStillWaiting?.(live.length);
19222
19473
  }
19223
19474
  } catch (e) {
19224
- console.error("[overmind] tick error:", e);
19475
+ tickError = e;
19476
+ console.error(`[overmind] \u0442\u0438\u043A \u0443\u043F\u0430\u043B: ${describeTickError(e)} \u2014 \u0446\u0438\u043A\u043B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0443`, e);
19225
19477
  }
19478
+ markTickDone(tickHealth, Date.now(), tickError);
19226
19479
  await new Promise((r) => setTimeout(r, interval));
19227
19480
  }
19228
19481
  }