@taskless-app/swarm 0.6.24 → 0.6.26

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
@@ -9,17 +9,23 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // ../swarm-core/src/types.ts
12
+ function holdsFiles(run) {
13
+ if ((run.kind ?? "drone") !== "drone") return false;
14
+ if (run.status === "review") return !!run.pr_url;
15
+ return ACTIVE_DRONE_STATUSES.has(run.status);
16
+ }
12
17
  function isMilestoneKind(kind) {
13
18
  return MILESTONE_KIND_SET.has(kind);
14
19
  }
15
20
  function toRunKind(role) {
16
21
  return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
17
22
  }
18
- var ACTIVE_DRONE_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
23
+ var ACTIVE_DRONE_STATUSES, FILE_HOLDING_STATUSES, EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
19
24
  var init_types = __esm({
20
25
  "../swarm-core/src/types.ts"() {
21
26
  "use strict";
22
27
  ACTIVE_DRONE_STATUSES = /* @__PURE__ */ new Set(["running", "blocked"]);
28
+ FILE_HOLDING_STATUSES = /* @__PURE__ */ new Set([...ACTIVE_DRONE_STATUSES, "review"]);
23
29
  EVENT_KINDS = [
24
30
  "spawned",
25
31
  "step",
@@ -525,6 +531,86 @@ var init_mergeQueue = __esm({
525
531
  }
526
532
  });
527
533
 
534
+ // ../swarm-core/src/prRef.ts
535
+ function prNumberOf(ref) {
536
+ const s = (ref || "").trim();
537
+ if (!s) return null;
538
+ const url = s.match(/\/pull\/(\d+)/);
539
+ if (url) return Number(url[1]);
540
+ const bare = s.match(/^#?(\d+)$/);
541
+ return bare ? Number(bare[1]) : null;
542
+ }
543
+ function repoKeyOf(raw) {
544
+ const s = (raw ?? "").trim().replace(/\/+$/, "").replace(/\.git$/i, "");
545
+ if (!s) return null;
546
+ const url = s.match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\/(?:pull|tree|compare|commit|issues)\/.*)?$/i);
547
+ if (url) return `${url[1]}/${url[2]}`.toLowerCase();
548
+ const pair = s.match(/^([\w.-]+)\/([\w.-]+)$/);
549
+ return pair ? `${pair[1]}/${pair[2]}`.toLowerCase() : null;
550
+ }
551
+ function newest(rows) {
552
+ let best = null;
553
+ for (const r of rows) if (!best || r.order > best.order) best = r;
554
+ return best;
555
+ }
556
+ function pick(rows) {
557
+ return newest(rows.filter((r) => r.author)) ?? newest(rows);
558
+ }
559
+ function pickRunForPr(rows, ref, ctxRepo) {
560
+ const num = prNumberOf(ref);
561
+ if (num == null) return null;
562
+ const target = repoKeyOf(ref) ?? repoKeyOf(ctxRepo);
563
+ const hits = rows.filter((r) => r.prUrl && prNumberOf(r.prUrl) === num);
564
+ if (hits.length === 0) return null;
565
+ if (target) {
566
+ const mine = hits.filter((r) => keyOf(r) === target);
567
+ const blind = hits.filter((r) => keyOf(r) == null);
568
+ return pick(mine.length ? mine : blind);
569
+ }
570
+ const keys = new Set(hits.map((r) => keyOf(r) ?? ""));
571
+ if (keys.size === 1) return pick(hits);
572
+ const risky = hits.filter((r) => r.reviewVerdict !== "approve");
573
+ return pick(risky.length ? risky : hits);
574
+ }
575
+ function keyOf(r) {
576
+ return repoKeyOf(r.prUrl) ?? repoKeyOf(r.repoKey);
577
+ }
578
+ var init_prRef = __esm({
579
+ "../swarm-core/src/prRef.ts"() {
580
+ "use strict";
581
+ }
582
+ });
583
+
584
+ // ../swarm-core/src/returnReason.ts
585
+ function isReturnReason(v) {
586
+ return typeof v === "string" && RETURN_REASON_SET.has(v);
587
+ }
588
+ function toReturnReason(v) {
589
+ return isReturnReason(v) ? v : null;
590
+ }
591
+ function returnReasonLabel(r) {
592
+ switch (r) {
593
+ case "unclear_goal":
594
+ return "\u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C";
595
+ case "no_acceptance":
596
+ return "\u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442";
597
+ case "too_big":
598
+ return "\u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0432\u0435\u043B\u0438\u043A\u0430 \u0434\u043B\u044F \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430";
599
+ case "contradiction":
600
+ return "\u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0441\u0430\u043C\u0430 \u0441\u0435\u0431\u0435";
601
+ }
602
+ }
603
+ var RETURN_REASONS, RETURN_REASON_SET, UNRETURNED_FAILURE_STATUSES, UNRETURNED_FAILURE_SET;
604
+ var init_returnReason = __esm({
605
+ "../swarm-core/src/returnReason.ts"() {
606
+ "use strict";
607
+ RETURN_REASONS = ["unclear_goal", "no_acceptance", "too_big", "contradiction"];
608
+ RETURN_REASON_SET = new Set(RETURN_REASONS);
609
+ UNRETURNED_FAILURE_STATUSES = ["failed", "blocked"];
610
+ UNRETURNED_FAILURE_SET = new Set(UNRETURNED_FAILURE_STATUSES);
611
+ }
612
+ });
613
+
528
614
  // ../swarm-core/src/modelPolicy.ts
529
615
  function findProvider(id) {
530
616
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1632,7 +1718,9 @@ var init_src = __esm({
1632
1718
  init_reviewPhase();
1633
1719
  init_mergeGate();
1634
1720
  init_mergeQueue();
1721
+ init_prRef();
1635
1722
  init_verifyVerdict();
1723
+ init_returnReason();
1636
1724
  init_modelPolicy();
1637
1725
  init_status();
1638
1726
  init_deps();
@@ -1784,6 +1872,43 @@ var init_swarmFiles = __esm({
1784
1872
  }
1785
1873
  });
1786
1874
 
1875
+ // src/mergeGateHook.ts
1876
+ import { chmodSync, copyFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1877
+ import { homedir } from "os";
1878
+ import { dirname, join as join2 } from "path";
1879
+ import { fileURLToPath } from "url";
1880
+ function packagedHookPath() {
1881
+ return join2(dirname(fileURLToPath(import.meta.url)), "..", "hooks", HOOK_FILE);
1882
+ }
1883
+ function wiredCommands(settings) {
1884
+ const blocks = settings?.hooks?.PreToolUse;
1885
+ if (!Array.isArray(blocks)) return [];
1886
+ return blocks.flatMap((b) => Array.isArray(b?.hooks) ? b.hooks : []).flatMap((h) => isOurHook(h?.command) ? [h.command] : []);
1887
+ }
1888
+ function readSettings(path) {
1889
+ if (!existsSync3(path)) return void 0;
1890
+ try {
1891
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1892
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
1893
+ } catch {
1894
+ return null;
1895
+ }
1896
+ }
1897
+ function droneMergeGateHookPath(worktree, packaged = packagedHookPath()) {
1898
+ if (!existsSync3(packaged)) return null;
1899
+ const projectSettings = readSettings(join2(worktree, ".claude", "settings.json"));
1900
+ if (projectSettings && wiredCommands(projectSettings).length > 0) return null;
1901
+ return packaged;
1902
+ }
1903
+ var HOOK_FILE, isOurHook;
1904
+ var init_mergeGateHook = __esm({
1905
+ "src/mergeGateHook.ts"() {
1906
+ "use strict";
1907
+ HOOK_FILE = "pre-merge-gate.sh";
1908
+ isOurHook = (command) => typeof command === "string" && command.trim().endsWith(HOOK_FILE);
1909
+ }
1910
+ });
1911
+
1787
1912
  // src/pricing.ts
1788
1913
  function costFromUsage(usage, model, table = MODEL_PRICING) {
1789
1914
  const entry = findModelPricing(model, table);
@@ -1872,9 +1997,9 @@ var init_claudeResult = __esm({
1872
1997
  });
1873
1998
 
1874
1999
  // src/executors/claudeCode.ts
1875
- import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
1876
- import { homedir } from "os";
1877
- import { join as join2 } from "path";
2000
+ import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
2001
+ import { homedir as homedir2 } from "os";
2002
+ import { join as join3 } from "path";
1878
2003
  function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
1879
2004
  const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
1880
2005
  const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
@@ -1900,20 +2025,23 @@ function droneDenyRules(paths) {
1900
2025
  const abs = (p) => `//${p.replace(/^\/+/, "")}`;
1901
2026
  return paths.flatMap((p) => [`Read(${abs(p)})`, `Edit(${abs(p)})`]);
1902
2027
  }
1903
- function droneSettingsJson(paths) {
1904
- return JSON.stringify({ permissions: { deny: droneDenyRules(paths) } });
2028
+ function droneSettingsJson(paths, mergeGateHook = null) {
2029
+ const settings = {};
2030
+ if (paths.length > 0) settings.permissions = { deny: droneDenyRules(paths) };
2031
+ if (mergeGateHook) settings.hooks = { PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: mergeGateHook }] }] };
2032
+ return JSON.stringify(settings);
1905
2033
  }
1906
2034
  function transcriptDir(worktree, projectsDir) {
1907
- return join2(projectsDir, worktree.replace(/[/.]/g, "-"));
2035
+ return join3(projectsDir, worktree.replace(/[/.]/g, "-"));
1908
2036
  }
1909
- function sessionTranscriptExists(worktree, projectsDir = join2(homedir(), ".claude", "projects")) {
2037
+ function sessionTranscriptExists(worktree, projectsDir = join3(homedir2(), ".claude", "projects")) {
1910
2038
  try {
1911
2039
  return readdirSync2(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
1912
2040
  } catch {
1913
2041
  return false;
1914
2042
  }
1915
2043
  }
1916
- function transcriptMtimeMs(worktree, projectsDir = join2(homedir(), ".claude", "projects")) {
2044
+ function transcriptMtimeMs(worktree, projectsDir = join3(homedir2(), ".claude", "projects")) {
1917
2045
  const dir = transcriptDir(worktree, projectsDir);
1918
2046
  let files;
1919
2047
  try {
@@ -1925,7 +2053,7 @@ function transcriptMtimeMs(worktree, projectsDir = join2(homedir(), ".claude", "
1925
2053
  for (const f of files) {
1926
2054
  if (!f.endsWith(".jsonl")) continue;
1927
2055
  try {
1928
- const mtime2 = statSync(join2(dir, f)).mtimeMs;
2056
+ const mtime2 = statSync(join3(dir, f)).mtimeMs;
1929
2057
  if (latest === null || mtime2 > latest) latest = mtime2;
1930
2058
  } catch {
1931
2059
  }
@@ -1938,8 +2066,8 @@ function writeMcpConfig(spec) {
1938
2066
  [spec.mcp.name]: { command: spec.mcp.command, args: spec.mcp.args, env: spec.mcp.env }
1939
2067
  }
1940
2068
  };
1941
- const path = join2(spec.worktree, MCP_CONFIG_FILE);
1942
- writeFileSync2(path, JSON.stringify(cfg, null, 2));
2069
+ const path = join3(spec.worktree, MCP_CONFIG_FILE);
2070
+ writeFileSync3(path, JSON.stringify(cfg, null, 2));
1943
2071
  return path;
1944
2072
  }
1945
2073
  function abortField(abort) {
@@ -1951,6 +2079,7 @@ var init_claudeCode = __esm({
1951
2079
  "use strict";
1952
2080
  init_claudeBin();
1953
2081
  init_swarmFiles();
2082
+ init_mergeGateHook();
1954
2083
  init_claudeResult();
1955
2084
  ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
1956
2085
  MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
@@ -1964,7 +2093,8 @@ var init_claudeCode = __esm({
1964
2093
  // проброс колбэком), и `this` тогда уедет в undefined.
1965
2094
  plan: (spec) => {
1966
2095
  const cfgPath = writeMcpConfig(spec);
1967
- const settings = spec.denyPaths.length > 0 ? droneSettingsJson(spec.denyPaths) : void 0;
2096
+ const hook = droneMergeGateHookPath(spec.worktree);
2097
+ const settings = spec.denyPaths.length > 0 || hook ? droneSettingsJson(spec.denyPaths, hook) : void 0;
1968
2098
  return {
1969
2099
  bin: resolveClaudeBin(),
1970
2100
  args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
@@ -2006,35 +2136,35 @@ var init_claudeCode = __esm({
2006
2136
  });
2007
2137
 
2008
2138
  // src/swarmHome.ts
2009
- import { homedir as homedir2 } from "os";
2010
- import { join as join3 } from "path";
2011
- import { mkdirSync } from "fs";
2139
+ import { homedir as homedir3 } from "os";
2140
+ import { join as join4 } from "path";
2141
+ import { mkdirSync as mkdirSync2 } from "fs";
2012
2142
  var SWARM_HOME;
2013
2143
  var init_swarmHome = __esm({
2014
2144
  "src/swarmHome.ts"() {
2015
2145
  "use strict";
2016
- SWARM_HOME = process.env.SWARM_HOME || join3(homedir2(), ".taskless-swarm");
2017
- mkdirSync(SWARM_HOME, { recursive: true });
2146
+ SWARM_HOME = process.env.SWARM_HOME || join4(homedir3(), ".taskless-swarm");
2147
+ mkdirSync2(SWARM_HOME, { recursive: true });
2018
2148
  }
2019
2149
  });
2020
2150
 
2021
2151
  // src/executors/codex.ts
2022
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync3, statSync as statSync2, symlinkSync } from "fs";
2023
- import { join as join4 } from "path";
2152
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, statSync as statSync2, symlinkSync } from "fs";
2153
+ import { join as join5 } from "path";
2024
2154
  function resolveCodexBin(env = process.env) {
2025
2155
  return (env.SWARM_CODEX_BIN ?? "").trim() || "codex";
2026
2156
  }
2027
2157
  function codexHomePath(worktree, swarmHome = SWARM_HOME) {
2028
- return join4(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
2158
+ return join5(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
2029
2159
  }
2030
2160
  function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWARM_HOME) {
2031
2161
  const home = codexHomePath(worktree, swarmHome);
2032
- mkdirSync2(home, { recursive: true });
2033
- const real = (env.CODEX_HOME ?? "").trim() || join4(realHome, ".codex");
2162
+ mkdirSync3(home, { recursive: true });
2163
+ const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
2034
2164
  for (const file of ["auth.json", "config.toml"]) {
2035
- const target = join4(real, file);
2036
- const link = join4(home, file);
2037
- if (!existsSync3(target) || existsSync3(link)) continue;
2165
+ const target = join5(real, file);
2166
+ const link = join5(home, file);
2167
+ if (!existsSync4(target) || existsSync4(link)) continue;
2038
2168
  try {
2039
2169
  symlinkSync(target, link);
2040
2170
  } catch {
@@ -2098,7 +2228,7 @@ function newestRolloutMtime(dir, depth = 4) {
2098
2228
  }
2099
2229
  let latest = null;
2100
2230
  for (const name of entries) {
2101
- const full = join4(dir, name);
2231
+ const full = join5(dir, name);
2102
2232
  let st;
2103
2233
  try {
2104
2234
  st = statSync2(full);
@@ -2172,8 +2302,8 @@ ${notice}` : spec.prompt;
2172
2302
  return est.warning ? { costUsd: est.costUsd, warning: est.warning } : { costUsd: est.costUsd };
2173
2303
  },
2174
2304
  session: {
2175
- exists: (worktree) => newestRolloutMtime(join4(codexHomePath(worktree), "sessions")) !== null,
2176
- mtimeMs: (worktree) => newestRolloutMtime(join4(codexHomePath(worktree), "sessions"))
2305
+ exists: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions")) !== null,
2306
+ mtimeMs: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions"))
2177
2307
  },
2178
2308
  probe: {
2179
2309
  // Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
@@ -2298,6 +2428,7 @@ __export(config_exports, {
2298
2428
  SWARM_WORKTREE_REAP_INTERVAL_MS: () => SWARM_WORKTREE_REAP_INTERVAL_MS,
2299
2429
  TASKLESS_API: () => TASKLESS_API,
2300
2430
  TASKLESS_MCP_PATH: () => TASKLESS_MCP_PATH,
2431
+ TASKLESS_REST_PATH: () => TASKLESS_REST_PATH,
2301
2432
  TASKLESS_TASK_URL_BASE: () => TASKLESS_TASK_URL_BASE,
2302
2433
  TASKLESS_TOKEN: () => TASKLESS_TOKEN,
2303
2434
  TASKLESS_WORKSPACE: () => TASKLESS_WORKSPACE,
@@ -2309,14 +2440,16 @@ __export(config_exports, {
2309
2440
  WORKTREES_NOINDEX_DIR: () => WORKTREES_NOINDEX_DIR,
2310
2441
  droneWorktreePath: () => droneWorktreePath,
2311
2442
  droneWorktreeRoot: () => droneWorktreeRoot,
2443
+ effectiveRepoOf: () => effectiveRepoOf,
2312
2444
  mcpEndpoint: () => mcpEndpoint,
2313
2445
  migrateWorktreesToNoindex: () => migrateWorktreesToNoindex,
2314
2446
  normalizeRepo: () => normalizeRepo,
2315
2447
  resolveEffectiveWorktreesDir: () => resolveEffectiveWorktreesDir,
2448
+ restEndpoint: () => restEndpoint,
2316
2449
  reviewCutoffAt: () => reviewCutoffAt
2317
2450
  });
2318
- import { basename, join as join5, resolve } from "path";
2319
- import { readFileSync as readFileSync2, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
2451
+ import { basename, join as join6, resolve } from "path";
2452
+ import { readFileSync as readFileSync3, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
2320
2453
  function normalizeRepo(repo) {
2321
2454
  const r = (repo ?? "").trim();
2322
2455
  if (!r) return "";
@@ -2326,11 +2459,14 @@ function normalizeRepo(repo) {
2326
2459
  return resolve(r);
2327
2460
  }
2328
2461
  }
2462
+ function effectiveRepoOf(repo, fallback) {
2463
+ return normalizeRepo(repo || fallback || "");
2464
+ }
2329
2465
  function droneWorktreePath(worktreesDir, repo, slug) {
2330
- return join5(droneWorktreeRoot(worktreesDir, repo), slug);
2466
+ return join6(droneWorktreeRoot(worktreesDir, repo), slug);
2331
2467
  }
2332
2468
  function droneWorktreeRoot(worktreesDir, repo) {
2333
- return join5(worktreesDir, basename(normalizeRepo(repo)));
2469
+ return join6(worktreesDir, basename(normalizeRepo(repo)));
2334
2470
  }
2335
2471
  function resolveEffectiveWorktreesDir(deps) {
2336
2472
  if (deps.envOverride) return deps.envOverride;
@@ -2365,6 +2501,12 @@ function envNum(name, def) {
2365
2501
  const n = Number(raw);
2366
2502
  return Number.isFinite(n) && n >= 0 ? n : def;
2367
2503
  }
2504
+ function restEndpoint(api, route, prefix = TASKLESS_REST_PATH) {
2505
+ const base3 = api.replace(/\/$/, "");
2506
+ const p = prefix === "/" ? "" : prefix.startsWith("/") ? prefix.replace(/\/$/, "") : `/${prefix.replace(/\/$/, "")}`;
2507
+ const r = route.startsWith("/") ? route : `/${route}`;
2508
+ return `${base3}${p}${r}`;
2509
+ }
2368
2510
  function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
2369
2511
  const base3 = api.replace(/\/$/, "");
2370
2512
  const p = path.startsWith("/") ? path : `/${path}`;
@@ -2372,27 +2514,27 @@ function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
2372
2514
  }
2373
2515
  function tokenFromFile() {
2374
2516
  try {
2375
- return readFileSync2(join5(SWARM_HOME, "token"), "utf8").trim();
2517
+ return readFileSync3(join6(SWARM_HOME, "token"), "utf8").trim();
2376
2518
  } catch {
2377
2519
  return "";
2378
2520
  }
2379
2521
  }
2380
2522
  function reviewCutoffAt() {
2381
2523
  try {
2382
- const raw = JSON.parse(readFileSync2(REVIEW_CUTOFF_PATH, "utf8"));
2524
+ const raw = JSON.parse(readFileSync3(REVIEW_CUTOFF_PATH, "utf8"));
2383
2525
  if (raw.at) return raw.at;
2384
2526
  } catch {
2385
2527
  }
2386
2528
  const at = stamp();
2387
2529
  try {
2388
2530
  const tmp = `${REVIEW_CUTOFF_PATH}.tmp`;
2389
- writeFileSync3(tmp, JSON.stringify({ at }));
2531
+ writeFileSync4(tmp, JSON.stringify({ at }));
2390
2532
  renameSync2(tmp, REVIEW_CUTOFF_PATH);
2391
2533
  } catch {
2392
2534
  }
2393
2535
  return at;
2394
2536
  }
2395
- var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2537
+ var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_REST_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2396
2538
  var init_config = __esm({
2397
2539
  "src/config.ts"() {
2398
2540
  "use strict";
@@ -2400,22 +2542,22 @@ var init_config = __esm({
2400
2542
  init_executors();
2401
2543
  init_swarmHome();
2402
2544
  init_swarmHome();
2403
- DB_PATH = process.env.SWARM_DB || join5(SWARM_HOME, "swarm.db");
2545
+ DB_PATH = process.env.SWARM_DB || join6(SWARM_HOME, "swarm.db");
2404
2546
  EXECUTOR = resolveExecutor(process.env.SWARM_EXECUTOR);
2405
- DRONE_HOME = process.env.SWARM_DRONE_HOME || join5(SWARM_HOME, "drone-home");
2547
+ DRONE_HOME = process.env.SWARM_DRONE_HOME || join6(SWARM_HOME, "drone-home");
2406
2548
  DRONE_HOME_ISOLATION = process.env.SWARM_DRONE_ISOLATE_HOME !== "0" && process.platform !== "win32";
2407
2549
  DRONE_ISOLATION = DRONE_HOME_ISOLATION ? { home: DRONE_HOME, swarmHome: SWARM_HOME } : void 0;
2408
- WORKTREES_NOINDEX_DIR = join5(SWARM_HOME, "worktrees.noindex");
2409
- WORKTREES_LEGACY_DIR = join5(SWARM_HOME, "worktrees");
2550
+ WORKTREES_NOINDEX_DIR = join6(SWARM_HOME, "worktrees.noindex");
2551
+ WORKTREES_LEGACY_DIR = join6(SWARM_HOME, "worktrees");
2410
2552
  WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
2411
- RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join5(SWARM_HOME, "run-logs");
2412
- DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join5(SWARM_HOME, "overmind.log");
2553
+ RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join6(SWARM_HOME, "run-logs");
2554
+ DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join6(SWARM_HOME, "overmind.log");
2413
2555
  DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
2414
2556
  DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
2415
2557
  DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
2416
2558
  DIST_BUILD = true;
2417
2559
  SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
2418
- SWARM_OUTBOX = process.env.SWARM_OUTBOX || join5(SWARM_HOME, "outbox.jsonl");
2560
+ SWARM_OUTBOX = process.env.SWARM_OUTBOX || join6(SWARM_HOME, "outbox.jsonl");
2419
2561
  SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
2420
2562
  SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
2421
2563
  SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
@@ -2445,20 +2587,21 @@ var init_config = __esm({
2445
2587
  DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
2446
2588
  DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
2447
2589
  DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
2448
- DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET || join5(SWARM_HOME, "drone-channel.key");
2590
+ DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET || join6(SWARM_HOME, "drone-channel.key");
2449
2591
  DRONE_CHANNEL_GRACE_MS = envNum("SWARM_DRONE_CHANNEL_GRACE_MS", 12e4);
2450
- HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join5(SWARM_HOME, "overmind.heartbeat");
2592
+ HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join6(SWARM_HOME, "overmind.heartbeat");
2451
2593
  HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
2452
2594
  SWARM_REPO = process.env.SWARM_REPO || "";
2453
2595
  SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
2454
2596
  SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
2455
2597
  SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
2456
- REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join5(SWARM_HOME, "repo-map.json");
2598
+ REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join6(SWARM_HOME, "repo-map.json");
2457
2599
  SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
2458
2600
  SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
2459
2601
  TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
2460
2602
  SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
2461
2603
  TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
2604
+ TASKLESS_REST_PATH = process.env.TASKLESS_REST_PATH || "/api";
2462
2605
  TASKLESS_TOKEN = process.env.TASKLESS_TOKEN || tokenFromFile();
2463
2606
  TASKLESS_WORKSPACE = process.env.TASKLESS_WORKSPACE || "";
2464
2607
  TASKLESS_WORKSPACE_SLUG = process.env.TASKLESS_WORKSPACE_SLUG || "";
@@ -2467,7 +2610,7 @@ var init_config = __esm({
2467
2610
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2468
2611
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2469
2612
  SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2470
- REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
2613
+ REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join6(SWARM_HOME, "review-cutoff.json");
2471
2614
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2472
2615
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
2473
2616
  WEB_PORT = Number(process.env.SWARM_WEB_PORT) || 7420;
@@ -2507,7 +2650,7 @@ var init_quietExec = __esm({
2507
2650
 
2508
2651
  // src/resources.ts
2509
2652
  import os from "os";
2510
- import { readFileSync as readFileSync3, statfsSync } from "fs";
2653
+ import { readFileSync as readFileSync4, statfsSync } from "fs";
2511
2654
  function isTransientResourceError(e) {
2512
2655
  const code = e?.code;
2513
2656
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
@@ -2565,7 +2708,7 @@ function fileDescriptorLoadRatio() {
2565
2708
  }
2566
2709
  if (process.platform === "linux") {
2567
2710
  try {
2568
- const [allocated, , max] = readFileSync3("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
2711
+ const [allocated, , max] = readFileSync4("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
2569
2712
  if (Number.isFinite(allocated ?? NaN) && Number.isFinite(max ?? NaN) && max) return allocated / max;
2570
2713
  } catch {
2571
2714
  }
@@ -2882,15 +3025,15 @@ var init_storm = __esm({
2882
3025
  });
2883
3026
 
2884
3027
  // src/heartbeat.ts
2885
- import { readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
3028
+ import { readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
2886
3029
  function writeHeartbeat(path, now) {
2887
3030
  const tmp = `${path}.tmp`;
2888
- writeFileSync5(tmp, String(now));
3031
+ writeFileSync6(tmp, String(now));
2889
3032
  renameSync4(tmp, path);
2890
3033
  }
2891
3034
  function readHeartbeat(path) {
2892
3035
  try {
2893
- const ms = Number(readFileSync5(path, "utf8").trim());
3036
+ const ms = Number(readFileSync6(path, "utf8").trim());
2894
3037
  return Number.isFinite(ms) ? ms : null;
2895
3038
  } catch {
2896
3039
  return null;
@@ -2946,11 +3089,11 @@ var init_logNoise = __esm({
2946
3089
  });
2947
3090
 
2948
3091
  // src/agentToken.ts
2949
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
2950
- import { join as join8 } from "path";
3092
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
3093
+ import { join as join9 } from "path";
2951
3094
  function loadAgentToken() {
2952
3095
  try {
2953
- const s = JSON.parse(readFileSync6(STORE_PATH, "utf8"));
3096
+ const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
2954
3097
  return s.token ? s : null;
2955
3098
  } catch {
2956
3099
  return null;
@@ -3003,7 +3146,7 @@ var init_agentToken = __esm({
3003
3146
  "src/agentToken.ts"() {
3004
3147
  "use strict";
3005
3148
  init_config();
3006
- STORE_PATH = join8(SWARM_HOME, "agent.json");
3149
+ STORE_PATH = join9(SWARM_HOME, "agent.json");
3007
3150
  base = (api) => api.replace(/\/$/, "");
3008
3151
  TOKEN_PROBE_TIMEOUT_MS = 8e3;
3009
3152
  }
@@ -3441,9 +3584,9 @@ var init_netTimeout = __esm({
3441
3584
  });
3442
3585
 
3443
3586
  // src/git.ts
3444
- import { existsSync as existsSync10, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
3587
+ import { existsSync as existsSync11, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
3445
3588
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
3446
- import { isAbsolute as isAbsolute2, join as join17, sep as sep2 } from "path";
3589
+ import { isAbsolute as isAbsolute2, join as join18, sep as sep2 } from "path";
3447
3590
  import { $ as $2 } from "execa";
3448
3591
  function gitNet(repo, args, timeoutMs) {
3449
3592
  return withHardTimeout(
@@ -3466,7 +3609,7 @@ async function reclaimStaleWorktree(repo, worktree) {
3466
3609
  }
3467
3610
  function isLinkedWorktree(dir) {
3468
3611
  try {
3469
- return statSync8(join17(dir, ".git")).isFile();
3612
+ return statSync8(join18(dir, ".git")).isFile();
3470
3613
  } catch {
3471
3614
  return false;
3472
3615
  }
@@ -3478,7 +3621,7 @@ async function detachWorktree(repo, worktree) {
3478
3621
  }
3479
3622
  }
3480
3623
  async function ensureWorktreeDirGone(worktree) {
3481
- if (!existsSync10(worktree)) return false;
3624
+ if (!existsSync11(worktree)) return false;
3482
3625
  if (!isLinkedWorktree(worktree)) {
3483
3626
  console.error(
3484
3627
  `[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
@@ -3493,7 +3636,7 @@ async function ensureWorktreeDirGone(worktree) {
3493
3636
  );
3494
3637
  return true;
3495
3638
  }
3496
- if (existsSync10(worktree)) {
3639
+ if (existsSync11(worktree)) {
3497
3640
  console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
3498
3641
  }
3499
3642
  return true;
@@ -3541,7 +3684,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
3541
3684
  }
3542
3685
  const busyAt = await worktreeForBranch(repo, branch);
3543
3686
  if (busyAt) {
3544
- const exists = existsSync10(busyAt);
3687
+ const exists = existsSync11(busyAt);
3545
3688
  const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
3546
3689
  if (live) throw new WorktreeBusyError(branch, busyAt);
3547
3690
  if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
@@ -3576,8 +3719,8 @@ async function ensureGitExcluded(worktree, pattern) {
3576
3719
  try {
3577
3720
  const commonDir = await gitPath(worktree, "--git-common-dir");
3578
3721
  if (!commonDir) return false;
3579
- const infoDir = join17(commonDir, "info");
3580
- const excludeFile = join17(infoDir, "exclude");
3722
+ const infoDir = join18(commonDir, "info");
3723
+ const excludeFile = join18(infoDir, "exclude");
3581
3724
  let current = "";
3582
3725
  try {
3583
3726
  current = await readFile(excludeFile, "utf8");
@@ -3597,7 +3740,7 @@ async function gitPath(worktree, flag) {
3597
3740
  const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
3598
3741
  const raw = out.stdout.trim();
3599
3742
  if (out.exitCode !== 0 || !raw) return null;
3600
- return isAbsolute2(raw) ? raw : join17(worktree, raw);
3743
+ return isAbsolute2(raw) ? raw : join18(worktree, raw);
3601
3744
  }
3602
3745
  async function ensureWorktreeHygiene(worktree) {
3603
3746
  for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
@@ -3607,12 +3750,12 @@ async function installGuardHooks(worktree) {
3607
3750
  try {
3608
3751
  const gitDir = await gitPath(worktree, "--git-dir");
3609
3752
  if (!gitDir) return false;
3610
- const hooksDir = join17(gitDir, "swarm-hooks");
3753
+ const hooksDir = join18(gitDir, "swarm-hooks");
3611
3754
  const prev = await previousHooksPath(worktree);
3612
3755
  await mkdir(hooksDir, { recursive: true });
3613
3756
  for (const hook of GUARD_HOOKS) {
3614
- await writeFile(join17(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3615
- await chmod(join17(hooksDir, hook), 493);
3757
+ await writeFile(join18(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3758
+ await chmod(join18(hooksDir, hook), 493);
3616
3759
  }
3617
3760
  await $2`git -C ${worktree} config extensions.worktreeConfig true`;
3618
3761
  await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
@@ -3631,7 +3774,7 @@ async function previousHooksPath(worktree) {
3631
3774
  if (out.exitCode === 0 && value) return value;
3632
3775
  }
3633
3776
  const commonDir = await gitPath(worktree, "--git-common-dir");
3634
- return commonDir ? join17(commonDir, "hooks") : null;
3777
+ return commonDir ? join18(commonDir, "hooks") : null;
3635
3778
  }
3636
3779
  async function removeWorktree(repo, path) {
3637
3780
  const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
@@ -3656,7 +3799,7 @@ async function worktreeDirty(path) {
3656
3799
  async function repairMovedWorktrees(targetDir) {
3657
3800
  const subdirs = (dir) => {
3658
3801
  try {
3659
- return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join17(dir, d.name));
3802
+ return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join18(dir, d.name));
3660
3803
  } catch {
3661
3804
  return [];
3662
3805
  }
@@ -3717,7 +3860,7 @@ async function mergeRef(worktree, ref) {
3717
3860
  return { clean: false, conflicts: u ? u.split("\n") : [], changed: false };
3718
3861
  }
3719
3862
  async function pushHeadTo(worktree, branch) {
3720
- await gitNetStrict(worktree, ["push", "origin", `HEAD:${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
3863
+ await gitNetStrict(worktree, ["push", "origin", `HEAD:refs/heads/${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
3721
3864
  }
3722
3865
  async function snapshotWorktree(worktree, message) {
3723
3866
  try {
@@ -3785,20 +3928,20 @@ __export(droneChannel_exports, {
3785
3928
  verifyLocalCliToken: () => verifyLocalCliToken
3786
3929
  });
3787
3930
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3788
- import { chmodSync, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
3789
- import { dirname as dirname3 } from "path";
3931
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
3932
+ import { dirname as dirname4 } from "path";
3790
3933
  import { serve } from "@hono/node-server";
3791
3934
  function loadChannelSecret(path) {
3792
3935
  try {
3793
- const s = readFileSync14(path, "utf8").trim();
3936
+ const s = readFileSync15(path, "utf8").trim();
3794
3937
  if (s.length >= 32) return s;
3795
3938
  } catch {
3796
3939
  }
3797
3940
  const secret = randomBytes(32).toString("hex");
3798
- mkdirSync8(dirname3(path), { recursive: true });
3799
- writeFileSync10(path, secret, { mode: 384 });
3941
+ mkdirSync9(dirname4(path), { recursive: true });
3942
+ writeFileSync11(path, secret, { mode: 384 });
3800
3943
  try {
3801
- chmodSync(path, 384);
3944
+ chmodSync2(path, 384);
3802
3945
  } catch {
3803
3946
  }
3804
3947
  return secret;
@@ -3856,6 +3999,12 @@ function invalidPatchValue(field, v) {
3856
3999
  return typeof v === "number" && Number.isInteger(v) && v >= 0 ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0446\u0438\u043A\u043B \u0440\u0435\u0432\u044C\u044E: ${String(v)}`;
3857
4000
  case "pr_url":
3858
4001
  return v === null || typeof v === "string" ? null : "pr_url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439";
4002
+ case "changed_paths": {
4003
+ if (!Array.isArray(v)) return "changed_paths \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C \u043F\u0443\u0442\u0435\u0439";
4004
+ if (v.length > CHANGED_PATHS_MAX) return `changed_paths: \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u043F\u0443\u0442\u0435\u0439 (${v.length} > ${CHANGED_PATHS_MAX})`;
4005
+ const badPath = v.find((p) => typeof p !== "string" || !p || p.length > CHANGED_PATH_LEN_MAX);
4006
+ return badPath === void 0 ? null : `changed_paths: \u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u043F\u0443\u0442\u044C: ${String(badPath).slice(0, 80)}`;
4007
+ }
3859
4008
  default:
3860
4009
  return `\u043F\u043E\u043B\u0435 \xAB${field}\xBB \u043A\u0430\u043D\u0430\u043B\u0443 \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442\u0441\u044F`;
3861
4010
  }
@@ -4109,7 +4258,7 @@ function channelDroneStore(opts) {
4109
4258
  openQuestionsFor: async () => (await call("openQuestionsFor")).questions
4110
4259
  };
4111
4260
  }
4112
- var LOCAL_CLI_KEY, PATCHABLE, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
4261
+ var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
4113
4262
  var init_droneChannel = __esm({
4114
4263
  "src/droneChannel.ts"() {
4115
4264
  "use strict";
@@ -4123,8 +4272,16 @@ var init_droneChannel = __esm({
4123
4272
  // и пишется он тем же путём на ПРОВЕРЯЕМЫЙ прогон. Без него роль verifier была немой:
4124
4273
  // вердикт выносился комментарием на PR, а `done` отбивался каналом.
4125
4274
  "verify_verdict",
4126
- "review_cycle"
4275
+ "review_cycle",
4276
+ // TASWM-506: файлы, которые прогон занял своим PR. Едут тем же вызовом, что и сдача
4277
+ // (done → status=review + pr_url): считает их НАШ код в дрон-сервере (`changedPaths` по
4278
+ // worktree), а не модель — со стороны канала это такой же самоотчёт прогона о себе, как
4279
+ // pr_url. Потолки на размер — ниже, в invalidPatchValue: список идёт в гейт раздачи, и
4280
+ // сорвавшийся дрон не должен уметь заявить занятым весь репозиторий.
4281
+ "changed_paths"
4127
4282
  ]);
4283
+ CHANGED_PATHS_MAX = 1e3;
4284
+ CHANGED_PATH_LEN_MAX = 1024;
4128
4285
  EVENT_KIND_SET = new Set(EVENT_KINDS);
4129
4286
  ChannelUnavailableError = class extends Error {
4130
4287
  constructor(downMs, message) {
@@ -4140,7 +4297,7 @@ var init_droneChannel = __esm({
4140
4297
 
4141
4298
  // src/attachments.ts
4142
4299
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
4143
- import { join as join20 } from "path";
4300
+ import { join as join21 } from "path";
4144
4301
  function attachmentsFromDoc(doc, origin) {
4145
4302
  const out = [];
4146
4303
  const walk2 = (node) => {
@@ -4231,7 +4388,7 @@ async function materializeAttachments(opts) {
4231
4388
  };
4232
4389
  const result = { files: [], skipped: [] };
4233
4390
  if (opts.refs.length === 0) return result;
4234
- const dir = join20(opts.worktree, ATTACHMENTS_DIR);
4391
+ const dir = join21(opts.worktree, ATTACHMENTS_DIR);
4235
4392
  await io.removeDir(dir);
4236
4393
  let dirReady = false;
4237
4394
  let total = 0;
@@ -4277,7 +4434,7 @@ async function materializeAttachments(opts) {
4277
4434
  await io.mkdir(dir);
4278
4435
  dirReady = true;
4279
4436
  }
4280
- await io.writeFile(join20(dir, name), file.bytes);
4437
+ await io.writeFile(join21(dir, name), file.bytes);
4281
4438
  } catch (e) {
4282
4439
  result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
4283
4440
  continue;
@@ -4430,7 +4587,7 @@ var init_attachments = __esm({
4430
4587
 
4431
4588
  // src/shotUpload.ts
4432
4589
  import { readFile as fsReadFile } from "fs/promises";
4433
- import { basename as basename4, isAbsolute as isAbsolute3, join as join21 } from "path";
4590
+ import { basename as basename4, isAbsolute as isAbsolute3, join as join22 } from "path";
4434
4591
  function shotMimetype(path) {
4435
4592
  const dot = path.lastIndexOf(".");
4436
4593
  if (dot < 0) return null;
@@ -4456,7 +4613,7 @@ async function readShots(paths, opts) {
4456
4613
  }
4457
4614
  let bytes;
4458
4615
  try {
4459
- bytes = await read(isAbsolute3(path) ? path : join21(opts.worktree, path));
4616
+ bytes = await read(isAbsolute3(path) ? path : join22(opts.worktree, path));
4460
4617
  } catch (e) {
4461
4618
  skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
4462
4619
  continue;
@@ -4466,7 +4623,10 @@ async function readShots(paths, opts) {
4466
4623
  continue;
4467
4624
  }
4468
4625
  if (bytes.byteLength > maxBytes) {
4469
- skipped.push({ path, reason: `${(bytes.byteLength / 1024 / 1024).toFixed(1)} \u041C\u0411 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043B\u0438\u043C\u0438\u0442\u0430 ${maxBytes / 1024 / 1024} \u041C\u0411` });
4626
+ skipped.push({
4627
+ path,
4628
+ reason: `${(bytes.byteLength / 1024 / 1024).toFixed(1)} \u041C\u0411 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043B\u0438\u043C\u0438\u0442\u0430 ${maxBytes / 1024 / 1024} \u041C\u0411; \u043F\u0435\u0440\u0435\u0441\u043D\u0438\u043C\u0438 \u043F\u043E\u043B\u0435\u0433\u0447\u0435: swarm shot \u2026 --scale 1 (\u0438\u043B\u0438 --scroll-to <\u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440> \u0432\u043C\u0435\u0441\u0442\u043E \u0434\u043B\u0438\u043D\u043D\u043E\u0433\u043E \u043A\u0430\u0434\u0440\u0430)`
4629
+ });
4470
4630
  continue;
4471
4631
  }
4472
4632
  files.push({ path, fileName: basename4(path), mimetype, bytes });
@@ -4521,7 +4681,7 @@ var init_shotUpload = __esm({
4521
4681
  "src/shotUpload.ts"() {
4522
4682
  "use strict";
4523
4683
  SHOT_MAX_FILES = 6;
4524
- SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
4684
+ SHOT_MAX_FILE_BYTES = 20 * 1024 * 1024;
4525
4685
  MIME_BY_EXT = {
4526
4686
  ".png": "image/png",
4527
4687
  ".jpg": "image/jpeg",
@@ -4534,8 +4694,8 @@ var init_shotUpload = __esm({
4534
4694
 
4535
4695
  // src/oauth.ts
4536
4696
  import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
4537
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
4538
- import { join as join22 } from "path";
4697
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
4698
+ import { join as join23 } from "path";
4539
4699
  function makePkce() {
4540
4700
  const verifier = randomBytes2(48).toString("base64url");
4541
4701
  const challenge = createHash2("sha256").update(verifier).digest("base64url");
@@ -4619,13 +4779,13 @@ function toStore(clientId, t, now) {
4619
4779
  }
4620
4780
  function loadStore() {
4621
4781
  try {
4622
- return JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
4782
+ return JSON.parse(readFileSync16(STORE_PATH2, "utf8"));
4623
4783
  } catch {
4624
4784
  return null;
4625
4785
  }
4626
4786
  }
4627
4787
  function saveStore(s) {
4628
- writeFileSync11(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
4788
+ writeFileSync12(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
4629
4789
  }
4630
4790
  function clearStore() {
4631
4791
  try {
@@ -4635,7 +4795,7 @@ function clearStore() {
4635
4795
  }
4636
4796
  function loadClientId() {
4637
4797
  try {
4638
- return JSON.parse(readFileSync15(CLIENT_PATH, "utf8")).clientId;
4798
+ return JSON.parse(readFileSync16(CLIENT_PATH, "utf8")).clientId;
4639
4799
  } catch {
4640
4800
  return null;
4641
4801
  }
@@ -4644,7 +4804,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
4644
4804
  const existing = loadClientId();
4645
4805
  if (existing) return existing;
4646
4806
  const clientId = await registerClient({ api, redirectUri }, fetchFn);
4647
- writeFileSync11(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4807
+ writeFileSync12(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4648
4808
  return clientId;
4649
4809
  }
4650
4810
  async function ensureAccess(store, opts = {}) {
@@ -4670,8 +4830,8 @@ var init_oauth = __esm({
4670
4830
  AUTHORIZE = "/api/oauth/authorize";
4671
4831
  TOKEN = "/api/oauth/token";
4672
4832
  SCOPE = "openid profile email taskless:full";
4673
- STORE_PATH2 = join22(SWARM_HOME, "oauth.json");
4674
- CLIENT_PATH = join22(SWARM_HOME, "oauth-client.json");
4833
+ STORE_PATH2 = join23(SWARM_HOME, "oauth.json");
4834
+ CLIENT_PATH = join23(SWARM_HOME, "oauth-client.json");
4675
4835
  base2 = (api) => api.replace(/\/$/, "");
4676
4836
  }
4677
4837
  });
@@ -4789,11 +4949,11 @@ var init_authState = __esm({
4789
4949
  });
4790
4950
 
4791
4951
  // src/workspace.ts
4792
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
4793
- import { join as join23 } from "path";
4952
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
4953
+ import { join as join24 } from "path";
4794
4954
  function loadWorkspace() {
4795
4955
  try {
4796
- const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
4956
+ const w = JSON.parse(readFileSync17(WORKSPACE_PATH, "utf8"));
4797
4957
  return w && typeof w.id === "string" && w.id ? w : null;
4798
4958
  } catch {
4799
4959
  return null;
@@ -4807,7 +4967,7 @@ var init_workspace = __esm({
4807
4967
  "src/workspace.ts"() {
4808
4968
  "use strict";
4809
4969
  init_config();
4810
- WORKSPACE_PATH = join23(SWARM_HOME, "workspace.json");
4970
+ WORKSPACE_PATH = join24(SWARM_HOME, "workspace.json");
4811
4971
  }
4812
4972
  });
4813
4973
 
@@ -4844,7 +5004,7 @@ function isClosingTaskCategory(category) {
4844
5004
  function taskAuthor(u) {
4845
5005
  return u?.id ? { id: u.id, name: u.displayName || "\u0430\u0432\u0442\u043E\u0440 \u0437\u0430\u0434\u0430\u0447\u0438" } : null;
4846
5006
  }
4847
- function toolErrorText(r) {
5007
+ function toolErrorText2(r) {
4848
5008
  const fromContent = (r.content ?? []).map((c) => c.text ?? "").filter(Boolean).join(" ").trim();
4849
5009
  return fromContent || JSON.stringify(r);
4850
5010
  }
@@ -4856,7 +5016,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4856
5016
  try {
4857
5017
  const r = await call(name, WS_FREE_TOOLS.has(name) ? args : { ...args, ...ws });
4858
5018
  if (r?.isError) {
4859
- const raw = toolErrorText(r);
5019
+ const raw = toolErrorText2(r);
4860
5020
  const detail = trimDetail(raw);
4861
5021
  console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, detail);
4862
5022
  return { ok: false, failure: { code: classifyToolFailure(raw), detail } };
@@ -5388,7 +5548,7 @@ __export(db_exports, {
5388
5548
  });
5389
5549
  import { createRequire as createRequire2 } from "module";
5390
5550
  import { randomUUID as randomUUID3 } from "crypto";
5391
- import { existsSync as existsSync12 } from "fs";
5551
+ import { existsSync as existsSync13 } from "fs";
5392
5552
  import { resolve as resolve3 } from "path";
5393
5553
  function rowToQuestion(row) {
5394
5554
  return {
@@ -5432,9 +5592,14 @@ function rowToRun(row) {
5432
5592
  tokens_out: row.tokens_out ?? 0,
5433
5593
  duration_ms: row.duration_ms ?? 0,
5434
5594
  pr_url: row.pr_url ?? null,
5595
+ // TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
5596
+ changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
5435
5597
  superseded_by: row.superseded_by ?? null,
5436
5598
  decision: row.decision ? JSON.parse(row.decision) : null,
5437
5599
  stop_reason: row.stop_reason ?? null,
5600
+ // TASWM-505: через узкий парсер, как verify_verdict ниже — в базе может лежать код,
5601
+ // записанный демоном другой версии; непонятное здесь значит «кода нет».
5602
+ return_reason: toReturnReason(row.return_reason),
5438
5603
  respawns: row.respawns ?? 0,
5439
5604
  // старые прогоны без колонки → 0
5440
5605
  resolves_run_id: row.resolves_run_id ?? null,
@@ -5509,6 +5674,7 @@ CREATE TABLE IF NOT EXISTS runs (
5509
5674
  superseded_by INTEGER,
5510
5675
  decision TEXT,
5511
5676
  stop_reason TEXT,
5677
+ return_reason TEXT,
5512
5678
  respawns INTEGER NOT NULL DEFAULT 0,
5513
5679
  resolves_run_id INTEGER,
5514
5680
  reviews_run_id INTEGER,
@@ -5625,7 +5791,16 @@ CREATE TABLE IF NOT EXISTS imports (
5625
5791
  // TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
5626
5792
  // Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
5627
5793
  // оттуда, а не ходят за задачей в прод в момент спавна.
5628
- "ALTER TABLE runs ADD COLUMN review_model TEXT"
5794
+ "ALTER TABLE runs ADD COLUMN review_model TEXT",
5795
+ // TASWM-505: КОД причины возврата задачи автору (unclear_goal|no_acceptance|too_big|
5796
+ // contradiction) рядом с прозой в stop_reason — из него складывается распределение
5797
+ // «чего в постановках не хватает чаще всего». NULL — прогон не возвращали либо
5798
+ // возврат сделан демоном до этой фичи.
5799
+ "ALTER TABLE runs ADD COLUMN return_reason TEXT",
5800
+ // TASWM-506: файлы, которые держит прогон с открытым PR (JSON-массив, как depends_on).
5801
+ // Снимается в момент сдачи с PR: worktree сносится на мерже, и пересчитать список
5802
+ // потом неоткуда.
5803
+ "ALTER TABLE runs ADD COLUMN changed_paths TEXT"
5629
5804
  ]) {
5630
5805
  try {
5631
5806
  this.db.exec(alter);
@@ -5771,7 +5946,7 @@ CREATE TABLE IF NOT EXISTS imports (
5771
5946
  const set = fields.map((f) => `${f} = ?`).join(", ");
5772
5947
  const vals = fields.map((f) => {
5773
5948
  const v = patch[f];
5774
- if (f === "depends_on") {
5949
+ if (f === "depends_on" || f === "changed_paths") {
5775
5950
  const arr = v;
5776
5951
  return arr && arr.length ? JSON.stringify(arr) : null;
5777
5952
  }
@@ -5996,7 +6171,7 @@ CREATE TABLE IF NOT EXISTS imports (
5996
6171
  const repoN = normalizeRepo(repo);
5997
6172
  const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
5998
6173
  if (already) return { imported: false, runs: already.run_count };
5999
- if (!existsSync12(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
6174
+ if (!existsSync13(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
6000
6175
  const src = new this.databaseCtor(abs);
6001
6176
  try {
6002
6177
  const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
@@ -6364,10 +6539,10 @@ var init_droneStore = __esm({
6364
6539
  });
6365
6540
 
6366
6541
  // src/overmind.ts
6367
- import { homedir as homedir5 } from "os";
6368
- import { existsSync as existsSync17, readFileSync as readFileSync20, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6369
- import { basename as basename6, dirname as dirname4, join as join30 } from "path";
6370
- import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
6542
+ import { homedir as homedir6 } from "os";
6543
+ import { existsSync as existsSync18, readFileSync as readFileSync21, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6544
+ import { basename as basename6, dirname as dirname5, join as join31 } from "path";
6545
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
6371
6546
  import { spawn as spawn3 } from "child_process";
6372
6547
 
6373
6548
  // src/restart.ts
@@ -6446,8 +6621,8 @@ init_config();
6446
6621
  init_resources();
6447
6622
  init_names2();
6448
6623
  init_storm();
6449
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
6450
- import { dirname } from "path";
6624
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
6625
+ import { dirname as dirname2 } from "path";
6451
6626
  import { randomUUID } from "crypto";
6452
6627
 
6453
6628
  // src/repoRules.ts
@@ -6499,12 +6674,12 @@ function renderPendingRulesBlock(rules) {
6499
6674
  }
6500
6675
 
6501
6676
  // src/logTail.ts
6502
- import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
6503
- import { join as join7 } from "path";
6677
+ import { closeSync, existsSync as existsSync6, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
6678
+ import { join as join8 } from "path";
6504
6679
 
6505
6680
  // src/runLogDir.ts
6506
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync as renameSync3, statSync as statSync3 } from "fs";
6507
- import { join as join6 } from "path";
6681
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync3, statSync as statSync3 } from "fs";
6682
+ import { join as join7 } from "path";
6508
6683
  var LEGACY_RUN_LOGS_SUBDIR = "legacy";
6509
6684
  function runLogDirName(runLogKey) {
6510
6685
  const safe2 = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
@@ -6512,12 +6687,12 @@ function runLogDirName(runLogKey) {
6512
6687
  return /^\d+$/.test(safe2) ? `k-${safe2}` : safe2;
6513
6688
  }
6514
6689
  function runLogDirPath(runLogsDir, runLogKey) {
6515
- return join6(runLogsDir, runLogDirName(runLogKey));
6690
+ return join7(runLogsDir, runLogDirName(runLogKey));
6516
6691
  }
6517
6692
  function findRunLogDir(runLogsDir, keys) {
6518
6693
  for (const key of keys) {
6519
6694
  const dir = runLogDirPath(runLogsDir, key);
6520
- if (existsSync4(dir)) return dir;
6695
+ if (existsSync5(dir)) return dir;
6521
6696
  }
6522
6697
  return null;
6523
6698
  }
@@ -6527,7 +6702,7 @@ function runLogDirForWrite(runLogsDir, keys) {
6527
6702
  return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
6528
6703
  }
6529
6704
  function sweepLegacyRunLogs(runLogsDir) {
6530
- const legacyDir = join6(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
6705
+ const legacyDir = join7(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
6531
6706
  let entries;
6532
6707
  try {
6533
6708
  entries = readdirSync4(runLogsDir);
@@ -6538,12 +6713,12 @@ function sweepLegacyRunLogs(runLogsDir) {
6538
6713
  let failed = 0;
6539
6714
  for (const name of entries) {
6540
6715
  if (!/^\d+$/.test(name)) continue;
6541
- const from = join6(runLogsDir, name);
6716
+ const from = join7(runLogsDir, name);
6542
6717
  try {
6543
6718
  if (!statSync3(from).isDirectory()) continue;
6544
- mkdirSync4(legacyDir, { recursive: true });
6545
- let to = join6(legacyDir, name);
6546
- for (let n = 2; existsSync4(to) && n < 100; n++) to = join6(legacyDir, `${name}-${n}`);
6719
+ mkdirSync5(legacyDir, { recursive: true });
6720
+ let to = join7(legacyDir, name);
6721
+ for (let n = 2; existsSync5(to) && n < 100; n++) to = join7(legacyDir, `${name}-${n}`);
6547
6722
  renameSync3(from, to);
6548
6723
  moved++;
6549
6724
  } catch {
@@ -6609,8 +6784,8 @@ function latestRunLaunch(runLogsDir, runLogKeys) {
6609
6784
  }
6610
6785
  if (indexes.size === 0) return null;
6611
6786
  const index = Math.max(...indexes);
6612
- const out = [join7(dir, `${index}.json`), join7(dir, `${index}.json.done`)].find((p) => existsSync5(p)) ?? null;
6613
- const err = existsSync5(join7(dir, `${index}.err.log`)) ? join7(dir, `${index}.err.log`) : null;
6787
+ const out = [join8(dir, `${index}.json`), join8(dir, `${index}.json.done`)].find((p) => existsSync6(p)) ?? null;
6788
+ const err = existsSync6(join8(dir, `${index}.err.log`)) ? join8(dir, `${index}.err.log`) : null;
6614
6789
  return { index, total: indexes.size, out, err };
6615
6790
  }
6616
6791
  function runLogTail(runLogsDir, runLogKeys, opts) {
@@ -6692,6 +6867,11 @@ async function serveLogRequests(deps, commands) {
6692
6867
  init_src();
6693
6868
  var RESOLVER_TERMINAL = /* @__PURE__ */ new Set([...TERMINAL_STATUSES, "stalled"]);
6694
6869
  var SLUG_TERMINAL = new Set(TERMINAL_STATUSES);
6870
+ function toolErrorText(res) {
6871
+ const text = (res?.content ?? []).map((c) => c.text ?? "").join(" ").replace(/\s+/g, " ").trim();
6872
+ if (!text) return "\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433 \u0432\u044B\u0437\u043E\u0432 \u0431\u0435\u0437 \u043E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u044F";
6873
+ return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
6874
+ }
6695
6875
  function runRef(runKey) {
6696
6876
  return { __runRef: runKey };
6697
6877
  }
@@ -6777,6 +6957,17 @@ var SwarmClient = class _SwarmClient {
6777
6957
  // нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
6778
6958
  depSlug = /* @__PURE__ */ new Map();
6779
6959
  static DEPS_TTL_MS = 6e4;
6960
+ // TASWM-486: ссылки, по которым прод НЕ ОТВЕТИЛ (отверг вызов или не дозвонились) —
6961
+ // с какого времени, сколько попыток и чем именно отказал. Отказ ≠ ответ: вердикта в
6962
+ // `depsProd` не появляется, `depVerdicts` отдаёт `pending`, и ждущий прогон стоит,
6963
+ // пока человек не разбудит его руками (21.08 — дважды за час). Отсюда две вещи:
6964
+ // короткий backoff вместо полного TTL (переспросить, а не ждать минуту) и
6965
+ // `depsUnconfirmed` — чтобы «стою вслепую» было ВИДНО в причине отказа и в аномалии.
6966
+ depsUnanswered = /* @__PURE__ */ new Map();
6967
+ // Первый переспрос — через 5 с (тик ~4 с, то есть буквально «на следующем тике»),
6968
+ // дальше удвоением до потолка TTL: устойчиво отвергающий прод (старее демона, скоуп
6969
+ // токена) не должен получать вопрос каждые четыре секунды до конца своей жизни.
6970
+ static DEPS_RETRY_BASE_MS = 5e3;
6780
6971
  // TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
6781
6972
  // ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
6782
6973
  // каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
@@ -6835,26 +7026,26 @@ var SwarmClient = class _SwarmClient {
6835
7026
  this.repoFullNames = registry.map((e) => e.repoFullName);
6836
7027
  this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
6837
7028
  this.host = opts.host ?? null;
6838
- this.repoExists = opts.repoExists ?? existsSync6;
7029
+ this.repoExists = opts.repoExists ?? existsSync7;
6839
7030
  this.questionSyncMs = opts.questionSyncMs ?? 3e3;
6840
7031
  this.executorId = opts.executorId ?? EXECUTOR.id;
6841
7032
  this.outboxPath = opts.outboxPath;
6842
7033
  this.mapPath = `${opts.outboxPath}.prodmap.json`;
6843
7034
  this.stormPath = `${opts.outboxPath}.storm.jsonl`;
6844
- mkdirSync5(dirname(this.outboxPath), { recursive: true });
7035
+ mkdirSync6(dirname2(this.outboxPath), { recursive: true });
6845
7036
  this.recover();
6846
7037
  }
6847
7038
  // ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
6848
7039
  recover() {
6849
- if (existsSync6(this.mapPath)) {
7040
+ if (existsSync7(this.mapPath)) {
6850
7041
  try {
6851
- const obj = JSON.parse(readFileSync4(this.mapPath, "utf8"));
7042
+ const obj = JSON.parse(readFileSync5(this.mapPath, "utf8"));
6852
7043
  for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
6853
7044
  } catch {
6854
7045
  }
6855
7046
  }
6856
- if (existsSync6(this.outboxPath)) {
6857
- const txt = readFileSync4(this.outboxPath, "utf8");
7047
+ if (existsSync7(this.outboxPath)) {
7048
+ const txt = readFileSync5(this.outboxPath, "utf8");
6858
7049
  for (const line of txt.split("\n")) {
6859
7050
  const s = line.trim();
6860
7051
  if (!s) continue;
@@ -6874,10 +7065,10 @@ var SwarmClient = class _SwarmClient {
6874
7065
  // eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
6875
7066
  // переприсваиваем локально (id событий — только порядок, не корреляция с продом).
6876
7067
  replayGlobalEvents() {
6877
- if (!existsSync6(this.stormPath)) return;
7068
+ if (!existsSync7(this.stormPath)) return;
6878
7069
  let txt;
6879
7070
  try {
6880
- txt = readFileSync4(this.stormPath, "utf8");
7071
+ txt = readFileSync5(this.stormPath, "utf8");
6881
7072
  } catch {
6882
7073
  return;
6883
7074
  }
@@ -6963,9 +7154,11 @@ var SwarmClient = class _SwarmClient {
6963
7154
  if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
6964
7155
  if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
6965
7156
  if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
7157
+ if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
6966
7158
  if ("branch" in patch) out.branch = patch.branch ?? null;
6967
7159
  if ("repo" in patch) out.repo = patch.repo ?? null;
6968
7160
  if ("stop_reason" in patch) out.stopReason = patch.stop_reason ?? null;
7161
+ if ("return_reason" in patch) out.returnReason = patch.return_reason ?? null;
6969
7162
  if ("depends_on" in patch && patch.depends_on) out.dependsOn = patch.depends_on;
6970
7163
  if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
6971
7164
  if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
@@ -7041,9 +7234,12 @@ var SwarmClient = class _SwarmClient {
7041
7234
  tokens_out: 0,
7042
7235
  duration_ms: 0,
7043
7236
  pr_url: null,
7237
+ changed_paths: [],
7238
+ // файлы прогона снимаются при открытии PR (TASWM-506)
7044
7239
  superseded_by: null,
7045
7240
  decision: input.decision ?? null,
7046
7241
  stop_reason: null,
7242
+ return_reason: null,
7047
7243
  respawns: 0,
7048
7244
  resolves_run_id: input.resolves_run_id ?? null,
7049
7245
  reviews_run_id: input.reviews_run_id ?? null,
@@ -7354,27 +7550,41 @@ var SwarmClient = class _SwarmClient {
7354
7550
  * Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
7355
7551
  * бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
7356
7552
  * дороже.
7553
+ *
7554
+ * TASWM-486: ОТКАЗ ПРОДА — НЕ ОТВЕТ. Раньше `isError` засчитывался как «спросили», и
7555
+ * следующая попытка шла только через TTL; при УСТОЙЧИВОМ отказе (прод старее демона,
7556
+ * просроченный скоуп токена демона, сбой на батче) вердикта не появлялось никогда —
7557
+ * зависимость навсегда оставалась `pending`, а ждущий прогон стоял при свободной машине
7558
+ * до ручного `wake_run`. Теперь отказ помнится отдельно (`depsUnanswered`): переспрос идёт
7559
+ * по короткому нарастающему backoff, а сам факт «прод молчит» становится видимым —
7560
+ * причина отказа спавна называет его вслух, и на затянувшуюся слепоту звенит аномалия.
7357
7561
  */
7358
7562
  async refreshDeps(refs, nowMs = Date.now()) {
7359
7563
  const ask = [...new Set(refs)].filter((ref) => {
7360
7564
  if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
7361
7565
  const asked = this.depsAskedAt.get(ref);
7362
- return asked === void 0 || nowMs - asked >= _SwarmClient.DEPS_TTL_MS;
7566
+ if (asked === void 0) return true;
7567
+ return nowMs - asked >= this.depsRetryDelay(ref);
7363
7568
  });
7364
7569
  if (!ask.length) return;
7365
7570
  for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
7366
7571
  let res;
7367
7572
  try {
7368
7573
  res = await this.call("swarm_deps_status", { refs: ask });
7369
- } catch {
7574
+ } catch (e) {
7370
7575
  for (const ref of ask) this.depsAskedAt.delete(ref);
7576
+ this.noteDepsUnanswered(ask, nowMs, String(e));
7577
+ return;
7578
+ }
7579
+ if (res?.isError) {
7580
+ this.noteDepsUnanswered(ask, nowMs, toolErrorText(res));
7371
7581
  return;
7372
7582
  }
7373
- if (res?.isError) return;
7374
7583
  const deps = res.structuredContent?.deps ?? [];
7375
7584
  for (const d of deps) {
7376
7585
  const ref = typeof d.ref === "string" ? d.ref : null;
7377
7586
  if (!ref) continue;
7587
+ this.depsUnanswered.delete(ref);
7378
7588
  const outcome = String(d.outcome ?? "pending");
7379
7589
  this.depsProd.set(ref, {
7380
7590
  outcome,
@@ -7386,6 +7596,75 @@ var SwarmClient = class _SwarmClient {
7386
7596
  if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
7387
7597
  }
7388
7598
  }
7599
+ /**
7600
+ * Через сколько переспрашивать ссылку (TASWM-486). Пауза зависит от того, ЧЕГО мы ждём —
7601
+ * и сколько стоит опоздать с ответом:
7602
+ *
7603
+ * • ПРОД ОТКАЗАЛ (isError / не дозвонились) — короткий backoff с удвоением (5с →
7604
+ * потолок TTL). Ждём, что прод вообще начнёт отвечать; до тех пор вердикта нет вовсе.
7605
+ * • БЛОКЕР ЖИВ либо вердикта ещё не было (`pending`) — КАЖДЫЙ ТИК. Это единственное
7606
+ * состояние, в котором ждущий прогон ЖЖЁТ ВРЕМЯ: блокер может закрыться в любую
7607
+ * секунду, и до TASWM-486 минута TTL превращалась в минуту простоя при свободной
7608
+ * машине (21.08: «стартует сам в пределах одного-двух тиков» не выполнялось даже
7609
+ * когда прод отвечал исправно). Цена — ОДИН батч-вызов на тик (все ссылки очереди
7610
+ * идут одним запросом, см. refreshDeps), и только пока кто-то реально ждёт.
7611
+ * • ЗАВИСИМОСТЬ УЖЕ СНЯТА неуспехом (`exhausted`) или блокера не знает никто
7612
+ * (`unknown`) — штатный TTL: прогон этими вердиктами больше не задерживается
7613
+ * (`exhausted`) либо не поедет в любом случае (`unknown`, там своя аномалия), и
7614
+ * переспрос нужен только на случай, что оператор переиграет блокер новым прогоном.
7615
+ * • `done` сюда не доходит вовсе — монотонен, отфильтрован раньше.
7616
+ */
7617
+ depsRetryDelay(ref) {
7618
+ const fail = this.depsUnanswered.get(ref);
7619
+ if (fail) {
7620
+ const backoff = _SwarmClient.DEPS_RETRY_BASE_MS * 2 ** Math.min(fail.attempts - 1, 10);
7621
+ return Math.min(backoff, _SwarmClient.DEPS_TTL_MS);
7622
+ }
7623
+ const known = this.depsProd.get(ref);
7624
+ if (!known || known.outcome === "pending") return 0;
7625
+ return _SwarmClient.DEPS_TTL_MS;
7626
+ }
7627
+ /** Запомнить, что прод по этим ссылкам не ответил (см. depsUnanswered / TASWM-486). */
7628
+ noteDepsUnanswered(refs, nowMs, error) {
7629
+ for (const ref of refs) {
7630
+ const prev = this.depsUnanswered.get(ref);
7631
+ this.depsUnanswered.set(ref, {
7632
+ ref,
7633
+ slug: this.depSlug.get(ref) ?? ref,
7634
+ // `since` — начало ЭПИЗОДА слепоты, а не последняя попытка: именно возраст эпизода
7635
+ // отвечает на вопрос «сколько прогон уже стоит вслепую».
7636
+ since: prev?.since ?? nowMs,
7637
+ attempts: (prev?.attempts ?? 0) + 1,
7638
+ error
7639
+ });
7640
+ }
7641
+ }
7642
+ /**
7643
+ * TASWM-486: ссылки `dependsOn`, по которым ВЕРДИКТА ПРОДА НЕТ — не «блокер жив», а «мы
7644
+ * не знаем, жив ли он». Снаружи оба выглядели одинаково (`pending`) и вели себя одинаково
7645
+ * молча, а разница ровно в том, дождётся ли ждущий чего-нибудь сам: живой блокер
7646
+ * закончится, а неотвечающий прод — нет, и прогон стоит до ручной побудки.
7647
+ *
7648
+ * Что сюда НЕ попадает и почему:
7649
+ * • прод отвечает — слепоты нет вовсе (запись стирается первым же ответом);
7650
+ * • последний вердикт уже СНЯЛ зависимость (`met`) — прогон ею не задерживается,
7651
+ * свежесть ответа ничего не решает;
7652
+ * • последний вердикт — `unknown`: это территория `dep_missing`, у неё и текст
7653
+ * точнее («блокера не знает никто»), и лечение другое.
7654
+ * А вот УСТАРЕВШИЙ `pending` при замолчавшем проде — как раз слепота: когда-то блокер
7655
+ * был жив, с тех пор он мог закрыться, и узнать это не у кого.
7656
+ */
7657
+ depsUnconfirmed(refs) {
7658
+ const out = [];
7659
+ for (const ref of new Set(refs)) {
7660
+ const known = this.depsProd.get(ref);
7661
+ if (known?.met || known?.outcome === "unknown") continue;
7662
+ const fail = this.depsUnanswered.get(ref);
7663
+ if (!fail) continue;
7664
+ out.push({ ...fail, slug: this.depSlug.get(ref) ?? fail.slug });
7665
+ }
7666
+ return out;
7667
+ }
7389
7668
  /**
7390
7669
  * TASWM-300: точечно перечитать прогон из прода — см. докблок SwarmStore.refreshRun.
7391
7670
  * `swarm_list_runs` фильтруем по задаче прогона (taskId принимает человеко-слаг) и берём
@@ -7702,7 +7981,7 @@ var SwarmClient = class _SwarmClient {
7702
7981
  * транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
7703
7982
  */
7704
7983
  reconcileRuntime(opts) {
7705
- const wtExists = opts.worktreeExists ?? existsSync6;
7984
+ const wtExists = opts.worktreeExists ?? existsSync7;
7706
7985
  const pidByWorktree = /* @__PURE__ */ new Map();
7707
7986
  try {
7708
7987
  for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
@@ -8562,9 +8841,12 @@ var SwarmClient = class _SwarmClient {
8562
8841
  tokens_out: 0,
8563
8842
  duration_ms: 0,
8564
8843
  pr_url: null,
8844
+ changed_paths: [],
8845
+ // занятые файлы (TASWM-506) — прод-поле, заполняется ниже
8565
8846
  superseded_by: null,
8566
8847
  decision: null,
8567
8848
  stop_reason: null,
8849
+ return_reason: null,
8568
8850
  respawns: 0,
8569
8851
  resolves_run_id: null,
8570
8852
  reviews_run_id: null,
@@ -8620,6 +8902,7 @@ var SwarmClient = class _SwarmClient {
8620
8902
  const verProd = pr.verifiesRunId ?? null;
8621
8903
  if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
8622
8904
  local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
8905
+ local.return_reason = toReturnReason(pr.returnReason) ?? local.return_reason;
8623
8906
  local.review_phase = pr.reviewPhase ?? local.review_phase;
8624
8907
  local.repo = this.resolveRepo(pr.repo ?? null);
8625
8908
  local.host_id = pr.hostId ?? null;
@@ -8639,6 +8922,7 @@ var SwarmClient = class _SwarmClient {
8639
8922
  local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
8640
8923
  local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
8641
8924
  local.pr_url = pr.prUrl ?? null;
8925
+ local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
8642
8926
  local.stop_reason = pr.stopReason ?? null;
8643
8927
  local.respawns = typeof pr.respawns === "number" ? pr.respawns : 0;
8644
8928
  local.decision = parseDecision(pr.decision);
@@ -8895,13 +9179,13 @@ var SwarmClient = class _SwarmClient {
8895
9179
  }
8896
9180
  persistPending() {
8897
9181
  try {
8898
- writeFileSync4(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
9182
+ writeFileSync5(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
8899
9183
  } catch {
8900
9184
  }
8901
9185
  }
8902
9186
  persistMap() {
8903
9187
  try {
8904
- writeFileSync4(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
9188
+ writeFileSync5(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
8905
9189
  } catch {
8906
9190
  }
8907
9191
  }
@@ -8971,7 +9255,7 @@ function tickPulse(health, now, intervalMs) {
8971
9255
  }
8972
9256
 
8973
9257
  // src/logRotate.ts
8974
- import { copyFileSync, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
9258
+ import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
8975
9259
  var realIo = {
8976
9260
  size: (path) => {
8977
9261
  try {
@@ -8980,11 +9264,11 @@ var realIo = {
8980
9264
  return null;
8981
9265
  }
8982
9266
  },
8983
- copy: copyFileSync,
9267
+ copy: copyFileSync2,
8984
9268
  rename: renameSync5,
8985
9269
  truncate: (path) => truncateSync(path, 0),
8986
9270
  unlink: unlinkSync2,
8987
- exists: existsSync7
9271
+ exists: existsSync8
8988
9272
  };
8989
9273
  var realOwnerIo = {
8990
9274
  statFd: (fd) => {
@@ -9111,13 +9395,13 @@ async function buildRepoRegistry(paths) {
9111
9395
  }
9112
9396
 
9113
9397
  // src/repoMemory.ts
9114
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
9115
- import { join as join9 } from "path";
9398
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
9399
+ import { join as join10 } from "path";
9116
9400
  var FILE = "repos.json";
9117
9401
  function loadRememberedRepos(home) {
9118
9402
  let raw;
9119
9403
  try {
9120
- raw = readFileSync7(join9(home, FILE), "utf8");
9404
+ raw = readFileSync8(join10(home, FILE), "utf8");
9121
9405
  } catch {
9122
9406
  return [];
9123
9407
  }
@@ -9132,7 +9416,7 @@ function loadRememberedRepos(home) {
9132
9416
  function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
9133
9417
  const clean = paths.filter((p) => p.trim().length > 0);
9134
9418
  if (clean.length === 0) return;
9135
- writeFileSync7(join9(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
9419
+ writeFileSync8(join10(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
9136
9420
  `);
9137
9421
  }
9138
9422
  function pickRepoPaths(args) {
@@ -9146,13 +9430,13 @@ function pickRepoPaths(args) {
9146
9430
  init_src();
9147
9431
  init_config();
9148
9432
  init_linkFailure();
9149
- import { homedir as homedir3 } from "os";
9150
- import { readFileSync as readFileSync9, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "fs";
9151
- import { isAbsolute, join as join11, resolve as resolve2 } from "path";
9433
+ import { homedir as homedir4 } from "os";
9434
+ import { readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
9435
+ import { isAbsolute, join as join12, resolve as resolve2 } from "path";
9152
9436
 
9153
9437
  // src/repoScan.ts
9154
- import { readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
9155
- import { join as join10 } from "path";
9438
+ import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
9439
+ import { join as join11 } from "path";
9156
9440
  var SKIP = /* @__PURE__ */ new Set([
9157
9441
  "node_modules",
9158
9442
  "vendor",
@@ -9182,7 +9466,7 @@ function parseOriginFromGitConfig(text) {
9182
9466
  }
9183
9467
  function originOf(dir) {
9184
9468
  try {
9185
- return parseOriginFromGitConfig(readFileSync8(join10(dir, ".git", "config"), "utf8"));
9469
+ return parseOriginFromGitConfig(readFileSync9(join11(dir, ".git", "config"), "utf8"));
9186
9470
  } catch {
9187
9471
  return null;
9188
9472
  }
@@ -9217,7 +9501,7 @@ function walk(dir, depth, maxDepth, found) {
9217
9501
  }
9218
9502
  for (const name of entries) {
9219
9503
  if (name.startsWith(".") || SKIP.has(name)) continue;
9220
- const child = join10(dir, name);
9504
+ const child = join11(dir, name);
9221
9505
  try {
9222
9506
  if (!statSync6(child).isDirectory()) continue;
9223
9507
  } catch {
@@ -9239,13 +9523,13 @@ function defaultScanRoots(home, knownPaths = []) {
9239
9523
  if (parent && parent !== p) add(parent);
9240
9524
  }
9241
9525
  for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
9242
- add(join10(home, name));
9526
+ add(join11(home, name));
9243
9527
  }
9244
9528
  return out;
9245
9529
  }
9246
9530
 
9247
9531
  // src/repoDiscovery.ts
9248
- function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
9532
+ function resolveScanRoots(raw, home = homedir4(), knownPaths = []) {
9249
9533
  const v = (raw ?? "").trim();
9250
9534
  if (!v) return defaultScanRoots(home, knownPaths);
9251
9535
  if (["off", "none", "0"].includes(v.toLowerCase())) return [];
@@ -9253,7 +9537,7 @@ function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
9253
9537
  for (const part of v.split(",")) {
9254
9538
  const p = part.trim();
9255
9539
  if (!p) continue;
9256
- const abs = p === "~" ? home : p.startsWith("~/") ? join11(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
9540
+ const abs = p === "~" ? home : p.startsWith("~/") ? join12(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
9257
9541
  if (!out.includes(abs)) out.push(abs);
9258
9542
  }
9259
9543
  return out;
@@ -9467,7 +9751,7 @@ function repoMapRows(state) {
9467
9751
  }
9468
9752
  function loadRepoMapCache(path) {
9469
9753
  try {
9470
- const raw = JSON.parse(readFileSync9(path, "utf8"));
9754
+ const raw = JSON.parse(readFileSync10(path, "utf8"));
9471
9755
  if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
9472
9756
  return {
9473
9757
  scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
@@ -9482,7 +9766,7 @@ function loadRepoMapCache(path) {
9482
9766
  function writeRepoMapCache(path, entries, scannedAt = stamp()) {
9483
9767
  try {
9484
9768
  const tmp = `${path}.tmp`;
9485
- writeFileSync8(tmp, JSON.stringify({ at: stamp(), scannedAt, entries }, null, 2));
9769
+ writeFileSync9(tmp, JSON.stringify({ at: stamp(), scannedAt, entries }, null, 2));
9486
9770
  renameSync6(tmp, path);
9487
9771
  } catch {
9488
9772
  }
@@ -9490,17 +9774,17 @@ function writeRepoMapCache(path, entries, scannedAt = stamp()) {
9490
9774
 
9491
9775
  // src/hostIdentity.ts
9492
9776
  import { hostname } from "os";
9493
- import { existsSync as existsSync8, readFileSync as readFileSync10, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "fs";
9494
- import { join as join12 } from "path";
9777
+ import { existsSync as existsSync9, readFileSync as readFileSync11, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
9778
+ import { join as join13 } from "path";
9495
9779
  import { randomUUID as randomUUID2 } from "crypto";
9496
9780
  function hostIdentityPath(home) {
9497
- return join12(home, "host.json");
9781
+ return join13(home, "host.json");
9498
9782
  }
9499
9783
  function loadHostIdentity(deps) {
9500
- const exists = deps.exists ?? existsSync8;
9501
- const readFile2 = deps.readFile ?? ((p) => readFileSync10(p, "utf8"));
9784
+ const exists = deps.exists ?? existsSync9;
9785
+ const readFile2 = deps.readFile ?? ((p) => readFileSync11(p, "utf8"));
9502
9786
  const writeFile3 = deps.writeFile ?? ((p, data) => {
9503
- writeFileSync9(`${p}.tmp`, data);
9787
+ writeFileSync10(`${p}.tmp`, data);
9504
9788
  renameSync7(`${p}.tmp`, p);
9505
9789
  });
9506
9790
  const host = deps.hostname ?? hostname;
@@ -9683,15 +9967,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
9683
9967
 
9684
9968
  // src/launcher.ts
9685
9969
  import { spawn } from "child_process";
9686
- import { closeSync as closeSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
9970
+ import { closeSync as closeSync2, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
9687
9971
  import { createRequire } from "module";
9688
- import { homedir as homedir4 } from "os";
9689
- import { dirname as dirname2, join as join14 } from "path";
9690
- import { fileURLToPath } from "url";
9972
+ import { homedir as homedir5 } from "os";
9973
+ import { dirname as dirname3, join as join15 } from "path";
9974
+ import { fileURLToPath as fileURLToPath2 } from "url";
9691
9975
 
9692
9976
  // src/droneHome.ts
9693
- import { lstatSync, mkdirSync as mkdirSync6, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
9694
- import { join as join13, relative, sep } from "path";
9977
+ import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
9978
+ import { join as join14, relative, sep } from "path";
9695
9979
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
9696
9980
  function droneHomeDeny(env = process.env) {
9697
9981
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -9709,26 +9993,26 @@ function buildDroneHome(opts) {
9709
9993
  const { realHome, droneHome } = opts;
9710
9994
  const deny = opts.deny ?? droneHomeDeny();
9711
9995
  const platform = opts.platform ?? process.platform;
9712
- mkdirSync6(droneHome, { recursive: true, mode: 448 });
9996
+ mkdirSync7(droneHome, { recursive: true, mode: 448 });
9713
9997
  const wanted = new Set(mirroredEntries(readdirSync7(realHome), deny, platform));
9714
9998
  const rel = relative(realHome, droneHome);
9715
9999
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
9716
10000
  for (const existing of readdirSync7(droneHome)) {
9717
10001
  if (wanted.has(existing)) continue;
9718
- const p = join13(droneHome, existing);
10002
+ const p = join14(droneHome, existing);
9719
10003
  try {
9720
10004
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
9721
10005
  } catch {
9722
10006
  }
9723
10007
  }
9724
10008
  for (const name of wanted) {
9725
- const link = join13(droneHome, name);
10009
+ const link = join14(droneHome, name);
9726
10010
  try {
9727
10011
  lstatSync(link);
9728
10012
  continue;
9729
10013
  } catch {
9730
10014
  }
9731
- const target = join13(realHome, name);
10015
+ const target = join14(realHome, name);
9732
10016
  let isDir;
9733
10017
  try {
9734
10018
  isDir = statSync7(target).isDirectory();
@@ -9749,7 +10033,7 @@ var REAL_PATH_ENV_PINS = [
9749
10033
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
9750
10034
  ];
9751
10035
  function realPathEnvPins(realHome) {
9752
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join13(realHome, ...segments)]));
10036
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join14(realHome, ...segments)]));
9753
10037
  }
9754
10038
  function dronePrivatePaths(realHome, swarmHome) {
9755
10039
  return [
@@ -9805,7 +10089,7 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
9805
10089
 
9806
10090
  // src/swarmModels.ts
9807
10091
  init_src();
9808
- function pick(v) {
10092
+ function pick2(v) {
9809
10093
  const s = typeof v === "string" ? v.trim() : "";
9810
10094
  return s.length > 0 ? s : null;
9811
10095
  }
@@ -9821,10 +10105,10 @@ function forKind(kind, swarm, norm) {
9821
10105
  }
9822
10106
  function resolveRunModelRef(run, swarm, env = process.env) {
9823
10107
  const configured = parseModelRef(
9824
- pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL)
10108
+ pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
9825
10109
  );
9826
10110
  if (configured) return configured;
9827
- const providerId = pick(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
10111
+ const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
9828
10112
  return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
9829
10113
  }
9830
10114
  function resolveRunModel(run, swarm, env = process.env) {
@@ -9861,7 +10145,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
9861
10145
  const env = { ...base3 };
9862
10146
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
9863
10147
  if (isolate && !base3.TURBO_CACHE_DIR) {
9864
- env.TURBO_CACHE_DIR = join14(worktree, ".turbo", "cache");
10148
+ env.TURBO_CACHE_DIR = join15(worktree, ".turbo", "cache");
9865
10149
  }
9866
10150
  Object.assign(env, droneTestEnv(droneCap, base3));
9867
10151
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -9889,18 +10173,18 @@ var DAEMON_STATE_ENV = [
9889
10173
  "TASKLESS_TOKEN"
9890
10174
  ];
9891
10175
  function bundledRoleDir(kind) {
9892
- return join14(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
10176
+ return join15(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
9893
10177
  }
9894
10178
  function roleDir(worktree, kind) {
9895
- return join14(worktree, ".claude", "roles", kind);
10179
+ return join15(worktree, ".claude", "roles", kind);
9896
10180
  }
9897
10181
  function roleDirs(worktree, kind) {
9898
- return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync9(dir));
10182
+ return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync10(dir));
9899
10183
  }
9900
10184
  function nextRunLogPath(runLogsDir, runLogKeys) {
9901
10185
  const dir = runLogDirForWrite(runLogsDir, runLogKeys);
9902
10186
  if (!dir) throw new Error("\u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 (SwarmStore.runLogKeys)");
9903
- mkdirSync7(dir, { recursive: true });
10187
+ mkdirSync8(dir, { recursive: true });
9904
10188
  let files = [];
9905
10189
  try {
9906
10190
  files = readdirSync8(dir);
@@ -9911,7 +10195,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
9911
10195
  const m = /^(\d+)\.json/.exec(f);
9912
10196
  if (m) next = Math.max(next, Number(m[1]) + 1);
9913
10197
  }
9914
- return join14(dir, `${next}.json`);
10198
+ return join15(dir, `${next}.json`);
9915
10199
  }
9916
10200
  function openNextRunLog(runLogsDir, runLogKeys) {
9917
10201
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -9992,11 +10276,11 @@ function makeLauncher(cfg) {
9992
10276
  } catch (e) {
9993
10277
  console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0441\u0438\u043B\u0438\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
9994
10278
  }
9995
- const realHome = cfg.droneIsolation?.realHome ?? homedir4();
10279
+ const realHome = cfg.droneIsolation?.realHome ?? homedir5();
9996
10280
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
9997
- if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(join14(cfg.droneIsolation.swarmHome, "token"))) {
10281
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync10(join15(cfg.droneIsolation.swarmHome, "token"))) {
9998
10282
  console.error(
9999
- `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join14(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
10283
+ `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join15(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
10000
10284
  );
10001
10285
  }
10002
10286
  const executor = cfg.executor ?? resolveExecutor();
@@ -10057,8 +10341,8 @@ function makeLauncher(cfg) {
10057
10341
  init_procCleanup();
10058
10342
 
10059
10343
  // src/cost.ts
10060
- import { readFileSync as readFileSync11, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10061
- import { join as join15 } from "path";
10344
+ import { readFileSync as readFileSync12, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10345
+ import { join as join16 } from "path";
10062
10346
  init_executors();
10063
10347
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
10064
10348
  function unprocessedLogFiles(dir) {
@@ -10080,10 +10364,10 @@ function accountRunCost(deps, run) {
10080
10364
  let durationMs = 0;
10081
10365
  const processed = [];
10082
10366
  for (const f of files) {
10083
- const full = join15(dir, f);
10367
+ const full = join16(dir, f);
10084
10368
  let raw = null;
10085
10369
  try {
10086
- raw = readFileSync11(full, "utf8");
10370
+ raw = readFileSync12(full, "utf8");
10087
10371
  } catch {
10088
10372
  continue;
10089
10373
  }
@@ -10132,8 +10416,8 @@ function makeCostSweeper(deps) {
10132
10416
  }
10133
10417
 
10134
10418
  // src/runOutcome.ts
10135
- import { readdirSync as readdirSync10, readFileSync as readFileSync12 } from "fs";
10136
- import { join as join16 } from "path";
10419
+ import { readdirSync as readdirSync10, readFileSync as readFileSync13 } from "fs";
10420
+ import { join as join17 } from "path";
10137
10421
  init_executors();
10138
10422
  function endedItself(outcome) {
10139
10423
  return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
@@ -10160,7 +10444,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
10160
10444
  }
10161
10445
  if (!latest) return null;
10162
10446
  try {
10163
- return readFileSync12(join16(dir, latest.name), "utf8");
10447
+ return readFileSync13(join17(dir, latest.name), "utf8");
10164
10448
  } catch {
10165
10449
  return null;
10166
10450
  }
@@ -10193,20 +10477,20 @@ init_src();
10193
10477
  init_git();
10194
10478
 
10195
10479
  // src/deps.ts
10196
- import { existsSync as existsSync11, readdirSync as readdirSync12, readFileSync as readFileSync13 } from "fs";
10197
- import { join as join19 } from "path";
10480
+ import { existsSync as existsSync12, readdirSync as readdirSync12, readFileSync as readFileSync14 } from "fs";
10481
+ import { join as join20 } from "path";
10198
10482
  import { execa } from "execa";
10199
10483
 
10200
10484
  // src/prepareLock.ts
10201
10485
  import { createHash } from "crypto";
10202
10486
  import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
10203
- import { join as join18 } from "path";
10487
+ import { join as join19 } from "path";
10204
10488
  import { tmpdir } from "os";
10205
10489
  var WAIT_TIMEOUT_MS = 5 * 6e4;
10206
10490
  var POLL_MS = 300;
10207
10491
  var STALE_MS = 20 * 6e4;
10208
10492
  function machineLockPath(key, dir = tmpdir()) {
10209
- return join18(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
10493
+ return join19(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
10210
10494
  }
10211
10495
  function defaultTryCreate(path, content) {
10212
10496
  try {
@@ -10280,7 +10564,7 @@ async function runCommand(cmd, worktree) {
10280
10564
  }
10281
10565
  function safeReadText(p) {
10282
10566
  try {
10283
- return readFileSync13(p, "utf8");
10567
+ return readFileSync14(p, "utf8");
10284
10568
  } catch {
10285
10569
  return null;
10286
10570
  }
@@ -10320,21 +10604,21 @@ function overridePlan(override) {
10320
10604
  };
10321
10605
  }
10322
10606
  function detectPackageManager(worktree, io = {}) {
10323
- const exists = io.exists ?? existsSync11;
10607
+ const exists = io.exists ?? existsSync12;
10324
10608
  const readText = io.readText ?? safeReadText;
10325
- const hasPkgJson = exists(join19(worktree, "package.json"));
10609
+ const hasPkgJson = exists(join20(worktree, "package.json"));
10326
10610
  if (!hasPkgJson) {
10327
- if (exists(join19(worktree, "deno.json")) || exists(join19(worktree, "deno.jsonc")) || exists(join19(worktree, "deno.lock"))) return "deno";
10611
+ if (exists(join20(worktree, "deno.json")) || exists(join20(worktree, "deno.jsonc")) || exists(join20(worktree, "deno.lock"))) return "deno";
10328
10612
  return null;
10329
10613
  }
10330
- const pkg = readJson(readText, join19(worktree, "package.json"));
10614
+ const pkg = readJson(readText, join20(worktree, "package.json"));
10331
10615
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
10332
10616
  if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
10333
- if (exists(join19(worktree, "bun.lock")) || exists(join19(worktree, "bun.lockb"))) return "bun";
10334
- if (exists(join19(worktree, "pnpm-lock.yaml"))) return "pnpm";
10335
- if (exists(join19(worktree, "yarn.lock"))) return "yarn";
10336
- if (exists(join19(worktree, "package-lock.json"))) return "npm";
10337
- if (exists(join19(worktree, "deno.lock"))) return "deno";
10617
+ if (exists(join20(worktree, "bun.lock")) || exists(join20(worktree, "bun.lockb"))) return "bun";
10618
+ if (exists(join20(worktree, "pnpm-lock.yaml"))) return "pnpm";
10619
+ if (exists(join20(worktree, "yarn.lock"))) return "yarn";
10620
+ if (exists(join20(worktree, "package-lock.json"))) return "npm";
10621
+ if (exists(join20(worktree, "deno.lock"))) return "deno";
10338
10622
  return "npm";
10339
10623
  }
10340
10624
  function jsInstallCommand(pm) {
@@ -10359,7 +10643,10 @@ var ECOSYSTEM_MARKERS = [
10359
10643
  // pyproject сам по себе не говорит, кем ставить — решает лок-файл рядом.
10360
10644
  command: null,
10361
10645
  refine: [
10362
- { file: "uv.lock", ecosystem: "python/uv", command: "uv sync" },
10646
+ // --frozen: ставим ровно по локу и не переписываем его (TAS-572). Без
10647
+ // флага uv доресолвливает лок под pyproject прямо в worktree дрона, и
10648
+ // чужой дифф на полторы сотни строк уезжает в PR по несвязанной задаче.
10649
+ { file: "uv.lock", ecosystem: "python/uv", command: "uv sync --frozen" },
10363
10650
  { file: "poetry.lock", ecosystem: "python/poetry", command: "poetry install" },
10364
10651
  { file: "requirements.txt", ecosystem: "python/pip", command: "pip install -r requirements.txt" }
10365
10652
  ]
@@ -10387,7 +10674,7 @@ var ECOSYSTEM_MARKERS = [
10387
10674
  // конфигурация — дело сборки, не подготовки
10388
10675
  ];
10389
10676
  function detectPlan(worktree, io) {
10390
- const exists = io.exists ?? existsSync11;
10677
+ const exists = io.exists ?? existsSync12;
10391
10678
  const listFiles = io.listFiles ?? safeListFiles;
10392
10679
  const pm = detectPackageManager(worktree, io);
10393
10680
  if (pm) {
@@ -10395,7 +10682,7 @@ function detectPlan(worktree, io) {
10395
10682
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
10396
10683
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
10397
10684
  // заново, но он дешёвый: тянет из того же общего кэша.
10398
- { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join19(worktree, "node_modules"), source: "detect" }
10685
+ { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join20(worktree, "node_modules"), source: "detect" }
10399
10686
  ];
10400
10687
  for (const target of workspaceBuildTargets(worktree, io)) {
10401
10688
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -10404,12 +10691,12 @@ function detectPlan(worktree, io) {
10404
10691
  return { source: "detect", ecosystem: `node/${pm}`, commands };
10405
10692
  }
10406
10693
  for (const marker of ECOSYSTEM_MARKERS) {
10407
- const hit = marker.files.some((f) => exists(join19(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
10694
+ const hit = marker.files.some((f) => exists(join20(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
10408
10695
  if (!hit) continue;
10409
10696
  let ecosystem = marker.ecosystem;
10410
10697
  let command = marker.command;
10411
10698
  for (const refine of marker.refine ?? []) {
10412
- if (!exists(join19(worktree, refine.file))) continue;
10699
+ if (!exists(join20(worktree, refine.file))) continue;
10413
10700
  ecosystem = refine.ecosystem;
10414
10701
  command = refine.command;
10415
10702
  break;
@@ -10466,12 +10753,12 @@ var CI_INSTALL_PATTERNS = [
10466
10753
  function ciWorkflowPlan(worktree, io) {
10467
10754
  const readText = io.readText ?? safeReadText;
10468
10755
  const listFiles = io.listFiles ?? safeListFiles;
10469
- const dir = join19(worktree, ".github", "workflows");
10756
+ const dir = join20(worktree, ".github", "workflows");
10470
10757
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
10471
10758
  const seen = /* @__PURE__ */ new Set();
10472
10759
  const commands = [];
10473
10760
  for (const name of files.sort()) {
10474
- const yaml = readText(join19(dir, name));
10761
+ const yaml = readText(join20(dir, name));
10475
10762
  if (yaml === null) continue;
10476
10763
  for (const raw of workflowRunLines(yaml)) {
10477
10764
  const line = raw.replace(/\s+/g, " ").trim();
@@ -10486,7 +10773,7 @@ function ciWorkflowPlan(worktree, io) {
10486
10773
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
10487
10774
  }
10488
10775
  function workspaceGlobs(worktree, readText) {
10489
- const yaml = readText(join19(worktree, "pnpm-workspace.yaml"));
10776
+ const yaml = readText(join20(worktree, "pnpm-workspace.yaml"));
10490
10777
  if (yaml !== null) {
10491
10778
  const globs = [];
10492
10779
  let inPackages = false;
@@ -10506,7 +10793,7 @@ function workspaceGlobs(worktree, readText) {
10506
10793
  }
10507
10794
  if (globs.length) return globs;
10508
10795
  }
10509
- const pkg = readJson(readText, join19(worktree, "package.json"));
10796
+ const pkg = readJson(readText, join20(worktree, "package.json"));
10510
10797
  const ws = pkg?.workspaces;
10511
10798
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
10512
10799
  return list.filter((g) => typeof g === "string");
@@ -10517,14 +10804,14 @@ function workspaceDirs(worktree, globs, io) {
10517
10804
  if (glob.startsWith("!")) continue;
10518
10805
  const star = glob.indexOf("*");
10519
10806
  if (star === -1) {
10520
- const dir = join19(worktree, glob);
10521
- if (io.exists(join19(dir, "package.json"))) dirs.add(dir);
10807
+ const dir = join20(worktree, glob);
10808
+ if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
10522
10809
  continue;
10523
10810
  }
10524
- const base3 = join19(worktree, glob.slice(0, star).replace(/\/$/, ""));
10811
+ const base3 = join20(worktree, glob.slice(0, star).replace(/\/$/, ""));
10525
10812
  for (const name of io.listDirs(base3)) {
10526
- const dir = join19(base3, name);
10527
- if (io.exists(join19(dir, "package.json"))) dirs.add(dir);
10813
+ const dir = join20(base3, name);
10814
+ if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
10528
10815
  }
10529
10816
  }
10530
10817
  return [...dirs];
@@ -10545,18 +10832,18 @@ function entryPaths(pkg) {
10545
10832
  return out;
10546
10833
  }
10547
10834
  function workspaceBuildTargets(worktree, io = {}) {
10548
- const exists = io.exists ?? existsSync11;
10835
+ const exists = io.exists ?? existsSync12;
10549
10836
  const readText = io.readText ?? safeReadText;
10550
10837
  const listDirs = io.listDirs ?? safeListDirs;
10551
10838
  const globs = workspaceGlobs(worktree, readText);
10552
10839
  if (!globs.length) return [];
10553
10840
  const pkgs = [];
10554
10841
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
10555
- const pkg = readJson(readText, join19(dir, "package.json"));
10842
+ const pkg = readJson(readText, join20(dir, "package.json"));
10556
10843
  if (!pkg || typeof pkg.name !== "string") continue;
10557
10844
  const scripts = pkg.scripts ?? {};
10558
10845
  const entries = entryPaths(pkg);
10559
- const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join19(dir, e)));
10846
+ const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join20(dir, e)));
10560
10847
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
10561
10848
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
10562
10849
  }
@@ -10584,7 +10871,7 @@ function devDatabaseUrl(worktree, io = {}) {
10584
10871
  const fromEnv = (io.env ?? process.env).DATABASE_URL;
10585
10872
  if (fromEnv && fromEnv.trim()) return fromEnv.trim();
10586
10873
  for (const file of [".env", ".env.example"]) {
10587
- const raw = readText(join19(worktree, file));
10874
+ const raw = readText(join20(worktree, file));
10588
10875
  if (raw === null) continue;
10589
10876
  const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
10590
10877
  const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
@@ -10600,11 +10887,11 @@ function isLocalDatabase(url) {
10600
10887
  }
10601
10888
  }
10602
10889
  function prismaPackages(worktree, io) {
10603
- const exists = io.exists ?? existsSync11;
10890
+ const exists = io.exists ?? existsSync12;
10604
10891
  const readText = io.readText ?? safeReadText;
10605
10892
  const listDirs = io.listDirs ?? safeListDirs;
10606
10893
  const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
10607
- return dirs.filter((dir) => exists(join19(dir, "prisma", "schema.prisma")) && listDirs(join19(dir, "prisma", "migrations")).length > 0);
10894
+ return dirs.filter((dir) => exists(join20(dir, "prisma", "schema.prisma")) && listDirs(join20(dir, "prisma", "migrations")).length > 0);
10608
10895
  }
10609
10896
  function jsExecBin(pm, command) {
10610
10897
  switch (pm) {
@@ -10637,7 +10924,7 @@ function planPrepare(worktree, io = {}) {
10637
10924
  return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
10638
10925
  }
10639
10926
  async function ensureWorktreeDeps(worktree, io = {}) {
10640
- const exists = io.exists ?? existsSync11;
10927
+ const exists = io.exists ?? existsSync12;
10641
10928
  const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
10642
10929
  const approve = io.approve ?? (() => true);
10643
10930
  const plan = planPrepare(worktree, io);
@@ -10833,6 +11120,10 @@ function isPreStartFailure(run) {
10833
11120
  function isSpawnParked(run) {
10834
11121
  return (run.stop_reason ?? "").includes(SPAWN_PARK_MARK);
10835
11122
  }
11123
+ var UNRETRIABLE_PARK_MARK = "\u043F\u043E\u0432\u0442\u043E\u0440 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442";
11124
+ function isUnretriableParked(run) {
11125
+ return isPreStartFailure(run) && (run.stop_reason ?? "").includes(UNRETRIABLE_PARK_MARK);
11126
+ }
10836
11127
  var SPAWN_ATTEMPT_WINDOW_MS = 60 * 6e4;
10837
11128
  function preStartAttempts(runs, taskSlug, opts = {}) {
10838
11129
  const windowMs = opts.windowMs ?? SPAWN_ATTEMPT_WINDOW_MS;
@@ -10846,9 +11137,13 @@ function preStartAttempts(runs, taskSlug, opts = {}) {
10846
11137
  const at = run.created_at ? stampMs(run.created_at) : Number.NaN;
10847
11138
  if (Number.isFinite(at) && now - at >= windowMs) break;
10848
11139
  n += 1;
11140
+ if (isUnretriableParked(run)) return Math.max(n, PRE_START_ATTEMPT_LIMIT);
10849
11141
  }
10850
11142
  return n;
10851
11143
  }
11144
+ function lastPreStartAttempt(runs, taskSlug) {
11145
+ return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort((a, b) => b.id - a.id)[0] ?? null;
11146
+ }
10852
11147
  function unresolvedRepoReason(repo, served) {
10853
11148
  const raw = repo.trim();
10854
11149
  if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
@@ -10858,6 +11153,9 @@ function unresolvedRepoReason(repo, served) {
10858
11153
  function parkReason(attempts, reason) {
10859
11154
  return `${PRE_START_MARK} ${attempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
10860
11155
  }
11156
+ function parkReasonUnretriable(reason) {
11157
+ return `${PRE_START_MARK}, \u0438 ${UNRETRIABLE_PARK_MARK} \u2014 ${SPAWN_PARK_MARK} \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0442\u0440\u0430\u0442\u044F ${PRE_START_ATTEMPT_LIMIT} \u043F\u043E\u043F\u044B\u0442\u043A\u0438. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
11158
+ }
10861
11159
 
10862
11160
  // src/worktreeReap.ts
10863
11161
  init_src();
@@ -11046,7 +11344,7 @@ async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
11046
11344
  return APPROVE_RE.test(m[1]) ? "approve" : "changes";
11047
11345
  });
11048
11346
  }
11049
- async function scanPrComments(prUrl, sinceIso, run, pick3) {
11347
+ async function scanPrComments(prUrl, sinceIso, run, pick4) {
11050
11348
  if (!isGithubPrUrl(prUrl)) return null;
11051
11349
  let out;
11052
11350
  try {
@@ -11066,7 +11364,7 @@ async function scanPrComments(prUrl, sinceIso, run, pick3) {
11066
11364
  const c = comments[i];
11067
11365
  if (!c?.body || !c.createdAt) continue;
11068
11366
  if (Number.isFinite(sinceMs) && Date.parse(c.createdAt) < sinceMs) continue;
11069
- const verdict = pick3(c.body);
11367
+ const verdict = pick4(c.body);
11070
11368
  if (verdict == null) continue;
11071
11369
  return { verdict, createdAt: c.createdAt };
11072
11370
  }
@@ -11243,65 +11541,270 @@ ${logs.join("\n---\n")}` : header;
11243
11541
  // src/droneServer.ts
11244
11542
  init_droneChannel();
11245
11543
  init_taskless();
11544
+ init_toolFailure();
11246
11545
  init_attachments();
11247
11546
  init_shotUpload();
11248
11547
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11249
11548
  import { z } from "zod";
11250
11549
  init_git();
11251
11550
 
11252
- // src/zoneNotes.ts
11253
- init_src();
11254
- function parseZoneMemory(raw, zone) {
11255
- const key = normalizeZoneLabel(zone);
11256
- if (!key || !raw || typeof raw !== "object" || Array.isArray(raw)) return [];
11257
- const notes = raw[key];
11258
- if (!Array.isArray(notes)) return [];
11259
- const out = [];
11260
- for (const item of notes) {
11261
- if (!item || typeof item !== "object") continue;
11262
- const n = item;
11263
- const id = typeof n.id === "string" ? n.id : "";
11264
- const claim = typeof n.claim === "string" ? n.claim.trim() : "";
11265
- if (id && claim) out.push({ id, claim });
11266
- }
11267
- return out;
11551
+ // src/diff.ts
11552
+ import { statSync as statSync10 } from "fs";
11553
+ import { join as join25 } from "path";
11554
+ import { $ as $4 } from "execa";
11555
+ var MAX_FILES = 200;
11556
+ var MAX_LINES_PER_FILE = 1500;
11557
+ var MAX_UNTRACKED = 60;
11558
+ function cleanPath(raw) {
11559
+ let p = raw.trim();
11560
+ if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) p = p.slice(1, -1);
11561
+ return p;
11268
11562
  }
11269
- function normalizeZoneLabel(zone) {
11270
- return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
11563
+ function stripAB(raw) {
11564
+ const p = cleanPath(raw);
11565
+ if (p === "/dev/null") return p;
11566
+ if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
11567
+ return p;
11271
11568
  }
11272
- function renderZoneMemoryBlock(zone, notes) {
11273
- const label2 = normalizeZoneLabel(zone);
11274
- if (!label2) return "";
11275
- const head = notes.length ? [
11276
- `\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
11277
- ...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
11278
- "\u042D\u0442\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438, \u0430 \u043D\u0435 \u0438\u0441\u0442\u0438\u043D\u0430 \u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0446\u0438\u0438: \u0440\u0430\u0437\u043E\u0448\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C \u2014 \u0432\u0435\u0440\u044C \u043A\u043E\u0434\u0443 \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0441\u0434\u0430\u0447\u0435.",
11279
- `\u041F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u0432 done: \`zone_notes_used: ["${notes[0].id}"]\` (id \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435 \u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u042D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0436\u0438\u0432\u0451\u0442: \u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430, \u0438 \u0432\u044B\u0442\u0435\u0441\u043D\u044F\u0435\u0442\u0441\u044F \u0442\u0430, \u0432 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0440\u043E\u0439 \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0445 \u043D\u0435 \u0443\u0431\u0435\u0436\u0434\u0430\u043B\u0441\u044F. \u041D\u0435 \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u043E\u0441\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E, \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434.`
11280
- ] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
11281
- return [
11282
- ...head,
11283
- `\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label2}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\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, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\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.`
11284
- ].join("\n");
11569
+ function parseUnifiedDiff(text) {
11570
+ const files = [];
11571
+ if (!text) return files;
11572
+ const lines = text.split("\n");
11573
+ let f = null;
11574
+ let hunk = null;
11575
+ let oldNo = 0;
11576
+ let newNo = 0;
11577
+ const push = () => {
11578
+ if (f) files.push(f);
11579
+ };
11580
+ for (const line of lines) {
11581
+ if (line.startsWith("diff --git ")) {
11582
+ push();
11583
+ hunk = null;
11584
+ const m = line.match(/^diff --git (.+) (.+)$/);
11585
+ const guess = m ? stripAB(m[2]) : "";
11586
+ f = { path: guess, oldPath: null, status: "modified", added: 0, removed: 0, binary: false, hunks: [] };
11587
+ continue;
11588
+ }
11589
+ if (!f) continue;
11590
+ if (line.startsWith("new file mode")) {
11591
+ f.status = "added";
11592
+ continue;
11593
+ }
11594
+ if (line.startsWith("deleted file mode")) {
11595
+ f.status = "deleted";
11596
+ continue;
11597
+ }
11598
+ if (line.startsWith("rename from ") || line.startsWith("copy from ")) {
11599
+ f.oldPath = cleanPath(line.replace(/^(rename|copy) from /, ""));
11600
+ f.status = "renamed";
11601
+ continue;
11602
+ }
11603
+ if (line.startsWith("rename to ") || line.startsWith("copy to ")) {
11604
+ f.path = cleanPath(line.replace(/^(rename|copy) to /, ""));
11605
+ f.status = "renamed";
11606
+ continue;
11607
+ }
11608
+ if (line.startsWith("Binary files ")) {
11609
+ f.binary = true;
11610
+ continue;
11611
+ }
11612
+ if (line.startsWith("--- ")) {
11613
+ const p = stripAB(line.slice(4));
11614
+ if (p === "/dev/null") f.status = "added";
11615
+ continue;
11616
+ }
11617
+ if (line.startsWith("+++ ")) {
11618
+ const p = stripAB(line.slice(4));
11619
+ if (p === "/dev/null") f.status = "deleted";
11620
+ else if (p) f.path = p;
11621
+ continue;
11622
+ }
11623
+ if (line.startsWith("@@")) {
11624
+ const m = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)$/);
11625
+ if (m) {
11626
+ oldNo = Number(m[1]);
11627
+ newNo = Number(m[2]);
11628
+ hunk = { header: (m[3] ?? "").trim(), lines: [] };
11629
+ f.hunks.push(hunk);
11630
+ }
11631
+ continue;
11632
+ }
11633
+ if (!hunk) continue;
11634
+ if (line.startsWith("\\")) continue;
11635
+ const tag = line[0];
11636
+ const body = line.slice(1);
11637
+ if (tag === "+") {
11638
+ hunk.lines.push({ k: "add", o: null, n: newNo++, t: body });
11639
+ f.added++;
11640
+ } else if (tag === "-") {
11641
+ hunk.lines.push({ k: "del", o: oldNo++, n: null, t: body });
11642
+ f.removed++;
11643
+ } else if (tag === " ") {
11644
+ hunk.lines.push({ k: "ctx", o: oldNo++, n: newNo++, t: body });
11645
+ }
11646
+ }
11647
+ push();
11648
+ return files;
11285
11649
  }
11286
- function collectZoneNoteConfirmations(report) {
11287
- const seen = /* @__PURE__ */ new Set();
11288
- const out = [];
11289
- for (const raw of report.used ?? []) {
11290
- const s = typeof raw === "string" ? raw.trim() : "";
11291
- if (!s || seen.has(s)) continue;
11292
- seen.add(s);
11293
- out.push(s);
11650
+ function truncateFile(f) {
11651
+ let count = 0;
11652
+ const hunks = [];
11653
+ let cut = false;
11654
+ for (const h of f.hunks) {
11655
+ if (count >= MAX_LINES_PER_FILE) {
11656
+ cut = true;
11657
+ break;
11658
+ }
11659
+ const room = MAX_LINES_PER_FILE - count;
11660
+ if (h.lines.length <= room) {
11661
+ hunks.push(h);
11662
+ count += h.lines.length;
11663
+ } else {
11664
+ hunks.push({ header: h.header, lines: h.lines.slice(0, room) });
11665
+ count += room;
11666
+ cut = true;
11667
+ break;
11668
+ }
11294
11669
  }
11295
- return out;
11670
+ return cut ? { ...f, hunks, truncated: true } : f;
11296
11671
  }
11297
-
11298
- // src/droneServer.ts
11299
- init_src();
11300
-
11672
+ async function resolveBase(wt, baseBranch) {
11673
+ const tryRef = async (ref) => {
11674
+ const sha = (await $4({ reject: false })`git -C ${wt} rev-parse --verify --quiet ${ref + "^{commit}"}`).stdout.trim();
11675
+ if (!sha) return null;
11676
+ const mb2 = (await $4({ reject: false })`git -C ${wt} merge-base HEAD ${ref}`).stdout.trim();
11677
+ return mb2 || null;
11678
+ };
11679
+ const originHead = (await $4({ reject: false })`git -C ${wt} symbolic-ref --quiet --short refs/remotes/origin/HEAD`).stdout.trim();
11680
+ const candidates = [
11681
+ ...baseBranch ? [`origin/${baseBranch}`, baseBranch] : [],
11682
+ originHead,
11683
+ "origin/main",
11684
+ "origin/master",
11685
+ "main",
11686
+ "master"
11687
+ ].filter(Boolean);
11688
+ for (const c of candidates) {
11689
+ const mb2 = await tryRef(c);
11690
+ if (mb2) return mb2;
11691
+ }
11692
+ return null;
11693
+ }
11694
+ function mtime(path) {
11695
+ try {
11696
+ return statSync10(path).mtimeMs;
11697
+ } catch {
11698
+ return 0;
11699
+ }
11700
+ }
11701
+ async function collectRunDiff(worktree, opts = {}) {
11702
+ const stub = (reason) => ({ ok: false, reason, pr: opts.pr ?? null, files: [], added: 0, removed: 0 });
11703
+ const gone = opts.status === "queued" ? "\u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442" : "worktree \u0443\u0431\u0440\u0430\u043D \u2014 \u043A\u043E\u0434 \u0441\u043C\u043E\u0442\u0440\u0438 \u0432 PR";
11704
+ if (!worktree) return stub(gone);
11705
+ const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
11706
+ if (isWt !== "true") return stub(gone);
11707
+ const base3 = await resolveBase(worktree, opts.baseBranch);
11708
+ const from = base3 ?? "HEAD";
11709
+ const trackedTxt = (await $4({ reject: false })`git -C ${worktree} diff --no-color --find-renames ${from} --`).stdout;
11710
+ let files = parseUnifiedDiff(trackedTxt);
11711
+ const untrackedRaw = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
11712
+ const untracked = untrackedRaw.split("\0").filter(Boolean).slice(0, MAX_UNTRACKED);
11713
+ for (const rel of untracked) {
11714
+ const t = (await $4({ reject: false })`git -C ${worktree} diff --no-color --no-index -- /dev/null ${rel}`).stdout;
11715
+ for (const pf of parseUnifiedDiff(t)) {
11716
+ pf.status = "added";
11717
+ if (!pf.path || pf.path === "/dev/null") pf.path = rel;
11718
+ files.push(pf);
11719
+ }
11720
+ }
11721
+ files.sort((a, b) => mtime(join25(worktree, b.path)) - mtime(join25(worktree, a.path)));
11722
+ const truncatedFiles = files.length > MAX_FILES;
11723
+ if (truncatedFiles) files = files.slice(0, MAX_FILES);
11724
+ const added = files.reduce((s, f) => s + f.added, 0);
11725
+ const removed = files.reduce((s, f) => s + f.removed, 0);
11726
+ const out = files.map(truncateFile);
11727
+ return {
11728
+ ok: true,
11729
+ base: base3 ? base3.slice(0, 9) : null,
11730
+ files: out,
11731
+ added,
11732
+ removed,
11733
+ ...truncatedFiles ? { truncated: true } : {}
11734
+ };
11735
+ }
11736
+ async function changedPaths(worktree, opts = {}) {
11737
+ if (!worktree) return [];
11738
+ try {
11739
+ const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
11740
+ if (isWt !== "true") return [];
11741
+ const from = await resolveBase(worktree, opts.baseBranch) ?? "HEAD";
11742
+ const tracked = (await $4({ reject: false })`git -C ${worktree} diff --name-only --find-renames ${from} --`).stdout;
11743
+ const untracked = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
11744
+ const all = [...tracked.split("\n"), ...untracked.split("\0")].map((s) => s.trim()).filter(Boolean);
11745
+ return [...new Set(all)].sort();
11746
+ } catch {
11747
+ return [];
11748
+ }
11749
+ }
11750
+
11751
+ // src/zoneNotes.ts
11752
+ init_src();
11753
+ function parseZoneMemory(raw, zone) {
11754
+ const key = normalizeZoneLabel(zone);
11755
+ if (!key || !raw || typeof raw !== "object" || Array.isArray(raw)) return [];
11756
+ const notes = raw[key];
11757
+ if (!Array.isArray(notes)) return [];
11758
+ const out = [];
11759
+ for (const item of notes) {
11760
+ if (!item || typeof item !== "object") continue;
11761
+ const n = item;
11762
+ const id = typeof n.id === "string" ? n.id : "";
11763
+ const claim = typeof n.claim === "string" ? n.claim.trim() : "";
11764
+ if (id && claim) out.push({ id, claim });
11765
+ }
11766
+ return out;
11767
+ }
11768
+ function normalizeZoneLabel(zone) {
11769
+ return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
11770
+ }
11771
+ function renderZoneMemoryBlock(zone, notes) {
11772
+ const label2 = normalizeZoneLabel(zone);
11773
+ if (!label2) return "";
11774
+ const head = notes.length ? [
11775
+ `\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u2014 \u0447\u0442\u043E \u0440\u043E\u0439 \u0443\u0436\u0435 \u0432\u044B\u044F\u0441\u043D\u0438\u043B \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u043E\u043C, \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u043A\u043E\u0434\u0430):`,
11776
+ ...notes.map((n) => `\u2022 [${n.id}] ${n.claim}`),
11777
+ "\u042D\u0442\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438, \u0430 \u043D\u0435 \u0438\u0441\u0442\u0438\u043D\u0430 \u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0439 \u0438\u043D\u0441\u0442\u0430\u043D\u0446\u0438\u0438: \u0440\u0430\u0437\u043E\u0448\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C \u2014 \u0432\u0435\u0440\u044C \u043A\u043E\u0434\u0443 \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u0441\u0434\u0430\u0447\u0435.",
11778
+ `\u041F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u0430\u0441\u044C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u0451 \u0432 done: \`zone_notes_used: ["${notes[0].id}"]\` (id \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435 \u0438\u043B\u0438 \u0441\u0430\u043C \u0442\u0435\u043A\u0441\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F). \u042D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0435, \u0447\u0435\u043C \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0436\u0438\u0432\u0451\u0442: \u043F\u0430\u043C\u044F\u0442\u044C \u0437\u043E\u043D\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0430, \u0438 \u0432\u044B\u0442\u0435\u0441\u043D\u044F\u0435\u0442\u0441\u044F \u0442\u0430, \u0432 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0440\u043E\u0439 \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0445 \u043D\u0435 \u0443\u0431\u0435\u0436\u0434\u0430\u043B\u0441\u044F. \u041D\u0435 \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u043B\u043E\u0441\u044C \u043D\u0438\u0447\u0435\u0433\u043E \u2014 \u043D\u0435 \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E, \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434.`
11779
+ ] : [`\u{1F9E0} \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \`${label2}\` \u043F\u0443\u0441\u0442\u0430 \u2014 \u0440\u043E\u0439 \u043F\u0440\u043E \u044D\u0442\u043E\u0442 \u043A\u0443\u0441\u043E\u043A \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B.`];
11780
+ return [
11781
+ ...head,
11782
+ `\u{1F9E0} \u0417\u0410\u041C\u0415\u0422\u041A\u0410 \u0412 \u041F\u0410\u041C\u042F\u0422\u042C \u0417\u041E\u041D\u042B \u2014 \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0434\u043E ${ZONE_NOTES_PER_RUN_MAX} \u0448\u0442\u0443\u043A: \`zone_notes: [{ zone: "${label2}", claim: "\u2026", why: "\u2026" }]\` (zone \u043C\u043E\u0436\u043D\u043E \u043D\u0435 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u2014 \u0432\u043E\u0437\u044C\u043C\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u044F). \u0427\u0422\u041E \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 (\xAB\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0437 \u043E\u0434\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E \u2026\xBB), \u0433\u0440\u0430\u0431\u043B\u0438 \u0441 \u0446\u0435\u043D\u043E\u0439 (\xAB\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, \u2014 \u0441\u0442\u043E\u0438\u043B\u043E \u0447\u0430\u0441\u0430\xBB), \u043E\u0442\u0431\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u0427\u0422\u041E \u041D\u0415 \u0433\u043E\u0434\u0438\u0442\u0441\u044F: \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u043E \u043A\u043E\u0434\u0430 (\xAB\u0445\u0435\u043D\u0434\u043B\u0435\u0440 \u043B\u0435\u0436\u0438\u0442 \u0432 handlers.ts\xBB, \xAB\u044D\u0442\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F \u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E-\u0442\u043E\xBB) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0435\u0442 \u044D\u0442\u043E \u0441\u0430\u043C \u0438 \u0431\u044B\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043C \u0442\u0432\u043E\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440; \u043D\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.`
11783
+ ].join("\n");
11784
+ }
11785
+ function collectZoneNoteConfirmations(report) {
11786
+ const seen = /* @__PURE__ */ new Set();
11787
+ const out = [];
11788
+ for (const raw of report.used ?? []) {
11789
+ const s = typeof raw === "string" ? raw.trim() : "";
11790
+ if (!s || seen.has(s)) continue;
11791
+ seen.add(s);
11792
+ out.push(s);
11793
+ }
11794
+ return out;
11795
+ }
11796
+
11797
+ // src/droneServer.ts
11798
+ init_src();
11799
+ init_src();
11800
+
11301
11801
  // src/reviewCycle.ts
11302
11802
  init_src();
11803
+ function needsFixIteration(run) {
11804
+ return run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
11805
+ }
11303
11806
  function reviewTransitionPatch(run, prUrl) {
11304
- const isFixIteration = run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
11807
+ const isFixIteration = needsFixIteration(run);
11305
11808
  if (isFixIteration) {
11306
11809
  const cycle = (run.review_cycle ?? 0) + 1;
11307
11810
  return {
@@ -11336,6 +11839,18 @@ function applyReviewTransition(db, run, prUrl) {
11336
11839
 
11337
11840
  // src/droneServer.ts
11338
11841
  init_src();
11842
+ function readTaskFailureInstruction(slug, failure) {
11843
+ const why = describeToolFailure(failure);
11844
+ if (TOOL_FAILURE_RETRIABLE[failure.code]) {
11845
+ return `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${why}. \u042D\u0442\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E\u043A\u0430 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435, \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 read_task \u0435\u0449\u0451 \u0440\u0430\u0437 \u043F\u043E\u0437\u0436\u0435 (\u043F\u0435\u0440\u0435\u0434 \u0441\u0434\u0430\u0447\u0435\u0439 \u2014 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E).`;
11846
+ }
11847
+ return [
11848
+ `\u26D4 \u0417\u0410\u0414\u0410\u0427\u0423 ${slug} \u041F\u0420\u041E\u0427\u0418\u0422\u0410\u0422\u042C \u041D\u0415\u041B\u042C\u0417\u042F: ${why}.`,
11849
+ "\u041F\u043E\u0432\u0442\u043E\u0440 \u0442\u0443\u0442 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442 \u2014 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0432 \u0441\u0435\u0442\u0438, \u043E\u043D\u0430 \u0441\u0430\u043C\u0430 \u043D\u0435 \u0440\u0430\u0437\u043E\u0439\u0434\u0451\u0442\u0441\u044F.",
11850
+ "\u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E \u0441\u043B\u0430\u0433\u0443 \u0438 \u043F\u043E \u043F\u0430\u043C\u044F\u0442\u0438: \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0442\u044B \u043D\u0435 \u0432\u0438\u0434\u0435\u043B, \u043D\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0438\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 (TASWM-288).",
11851
+ "\u0421\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0447\u0435\u0440\u0435\u0437 ask() \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u043C\u0443 \u044D\u0442\u0443 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u043E \u2014 \u0438 \u0434\u0430\u043B\u044C\u0448\u0435 \u0434\u0435\u043B\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043E\u043D \u043E\u0442\u0432\u0435\u0442\u0438\u0442."
11852
+ ].join("\n");
11853
+ }
11339
11854
  function freezeInstruction(branch, reason = "\u0435\u0433\u043E \u043F\u0443\u043B\u044C\u0441 (heartbeat) \u0443\u0441\u0442\u0430\u0440\u0435\u043B") {
11340
11855
  return [
11341
11856
  `\u26D4 OVERMIND \u041D\u0415\u0414\u041E\u0421\u0422\u0423\u041F\u0415\u041D \u2014 ${reason}. \u041D\u0430\u0434\u0437\u043E\u0440\u0430 \u043D\u0430\u0434 \u0442\u043E\u0431\u043E\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442: \u043D\u0435\u043A\u043E\u043C\u0443`,
@@ -11433,16 +11948,17 @@ async function buildDroneServer(store, opts = {}) {
11433
11948
  mismatchNote = `\u26A0\uFE0F \u0434\u0435\u043C\u043E\u043D \u0447\u0438\u0441\u043B\u0438\u0442 \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \xAB${run.task_slug}\xBB \u2014 \u044D\u0442\u043E \u041D\u0415 \u0442\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 (TASWM-211). \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D \u0434\u043E\u0432\u0435\u0440\u0435\u043D\u043D\u044B\u0439 slug \u0438\u0437 \u0441\u043F\u0430\u0432\u043D\u0430.`;
11434
11949
  }
11435
11950
  if (!slug) return { content: [{ type: "text", text: "\u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D" }], isError: true };
11436
- const task = await opts.taskless.getTask(slug);
11437
- if (!task) {
11438
- const notFoundText = `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug} \u0438\u0437 Taskless (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435.`;
11951
+ const read = await opts.taskless.getTaskDetailed(slug);
11952
+ if (!read.ok) {
11953
+ const failText = readTaskFailureInstruction(slug, read.failure);
11439
11954
  return {
11440
11955
  content: [{ type: "text", text: mismatchNote ? `${mismatchNote}
11441
11956
 
11442
- ${notFoundText}` : notFoundText }],
11957
+ ${failText}` : failText }],
11443
11958
  isError: true
11444
11959
  };
11445
11960
  }
11961
+ const task = read.task;
11446
11962
  const context = await fetchTaskContext(opts.taskless, task);
11447
11963
  const attachments = await prepareTaskAttachments({
11448
11964
  worktree: opts.worktree ?? process.cwd(),
@@ -11530,19 +12046,36 @@ ${notFoundText}` : notFoundText }],
11530
12046
  );
11531
12047
  server.tool(
11532
12048
  "return_task",
11533
- "\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0410\u0412\u0422\u041E\u0420\u0423 \u043D\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0435 \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (\u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F, \u0440\u043E\u0439 \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u0417\u043E\u0432\u0438, \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0418\u041B\u0418 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0414\u0432\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B\u0445 \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u2014 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434: \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u041F\u043E\u043D\u044F\u0442\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u2014 \u0442\u043E\u0436\u0435 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434: \u044D\u0442\u043E ask. \u0417\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 (\u043D\u0430\u0437\u043D\u0430\u0447\u0438\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0451\u043C \u0432 \u0440\u0430\u0431\u043E\u0442\u0443), \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043E\u0442 \u0442\u0432\u043E\u0435\u0433\u043E \u0438\u043C\u0435\u043D\u0438. \u041F\u043E\u0441\u043B\u0435 \u0432\u044B\u0437\u043E\u0432\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0437\u043E\u0432\u0438 \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
12049
+ "\u0412\u0435\u0440\u043D\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0410\u0412\u0422\u041E\u0420\u0423 \u043D\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0435 \u0438 \u0437\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (\u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F, \u0440\u043E\u0439 \u0432\u043E\u0437\u044C\u043C\u0451\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443). \u0417\u043E\u0432\u0438, \u0422\u041E\u041B\u042C\u041A\u041E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0418\u041B\u0418 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0414\u0432\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B\u0445 \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u2014 \u041D\u0415 \u043F\u043E\u0432\u043E\u0434: \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u041F\u043E\u043D\u044F\u0442\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u2014 \u0442\u043E\u0436\u0435 \u043D\u0435 \u043F\u043E\u0432\u043E\u0434: \u044D\u0442\u043E ask. \u041E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0441\u043B\u0443\u0447\u0430\u0439 \u2014 \u0437\u0430\u0434\u0430\u0447\u0430 \u0421\u041B\u0418\u0428\u041A\u041E\u041C \u0412\u0415\u041B\u0418\u041A\u0410 (reason=too_big): \u043F\u043E\u043D\u044F\u0442\u043D\u0430 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u0430, \u043D\u043E \u044D\u0442\u043E \u043B\u0438\u0441\u0442 \u043D\u0430 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432, \u0438 \u0443\u043F\u0435\u0440\u0435\u0442\u044C\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043D\u0430 \u0441\u0435\u0440\u0435\u0434\u0438\u043D\u0435 \u0445\u0443\u0436\u0435, \u0447\u0435\u043C \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0435\u0451 \u0441\u0440\u0430\u0437\u0443; \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418\u0422\u042C (split), \u0430 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0435 \u0437\u0430\u0432\u043E\u0434\u0438 \u2014 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0430\u0432\u0442\u043E\u0440\u0430. \u0417\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 (\u043D\u0430\u0437\u043D\u0430\u0447\u0438\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0451\u043C \u0432 \u0440\u0430\u0431\u043E\u0442\u0443), \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u043E\u0442 \u0442\u0432\u043E\u0435\u0433\u043E \u0438\u043C\u0435\u043D\u0438. \u041F\u043E\u0441\u043B\u0435 \u0432\u044B\u0437\u043E\u0432\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0437\u043E\u0432\u0438 \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E.",
11534
12050
  {
12051
+ // TASWM-505: КОД причины рядом с прозой. Проза объясняет автору ЭТУ задачу, код
12052
+ // складывается по бэклогу в распределение «чего в постановках не хватает чаще
12053
+ // всего» (аналитика роя). Обязательный: необязательное поле дрон пропустит, а
12054
+ // распределение по половине возвратов — не распределение.
12055
+ reason: z.enum(RETURN_REASONS).describe(
12056
+ "\u043A\u043E\u0434 \u043F\u0440\u0438\u0447\u0438\u043D\u044B: unclear_goal \u2014 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0427\u0422\u041E \u0434\u0435\u043B\u0430\u0442\u044C; no_acceptance \u2014 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442; too_big \u2014 \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0437\u0430\u0434\u0430\u0447\u0430, \u0430 \u043F\u0430\u0447\u043A\u0430 \u0437\u0430\u0434\u0430\u0447 (\u043B\u0438\u0441\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0437\u0430 \u043E\u0434\u0438\u043D \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0434\u043E\u0432\u0435\u0441\u0442\u0438); contradiction \u2014 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0441\u0430\u043C\u0430 \u0441\u0435\u0431\u0435 \u0438\u043B\u0438 \u0443\u0436\u0435 \u043F\u0440\u0438\u043D\u044F\u0442\u043E\u043C\u0443 \u0440\u0435\u0448\u0435\u043D\u0438\u044E"
12057
+ ),
11535
12058
  missing: z.string().min(30).describe("\u0447\u0435\u0433\u043E \u0432 \u0437\u0430\u0434\u0430\u0447\u0435 \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442, \u0447\u0442\u043E\u0431\u044B \u0435\u0451 \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u0438\u043B\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E, \u0430 \u043D\u0435 \xAB\u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E\xBB"),
11536
12059
  readings: z.array(z.string().min(10)).optional().describe("\u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u044F \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0442\u044B \u0440\u0430\u0441\u0441\u043C\u043E\u0442\u0440\u0435\u043B (\u0438 \u0447\u0435\u043C \u043A\u0430\u0436\u0434\u043E\u0435 \u043F\u043B\u043E\u0445\u043E/\u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0432\u043E)"),
11537
12060
  assumed: z.string().min(30).describe("\u0447\u0442\u043E \u0442\u044B \u043F\u0440\u0435\u0434\u043F\u043E\u043B\u043E\u0436\u0438\u043B \u0431\u044B \u0441\u0434\u0435\u043B\u0430\u0442\u044C, \u0435\u0441\u043B\u0438 \u0431\u044B \u043F\u0440\u0438\u0448\u043B\u043E\u0441\u044C \u0433\u0430\u0434\u0430\u0442\u044C, \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u043D\u0435 \u0441\u0442\u0430\u043B"),
11538
- questions: z.array(z.string().min(10)).min(1).max(7).describe("\u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0430\u0432\u0442\u043E\u0440\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u043D\u0438\u0445 \u0434\u043E\u043B\u0436\u043D\u044B \u0434\u0435\u043B\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043C\u043E\u0439")
12061
+ questions: z.array(z.string().min(10)).min(1).max(7).describe("\u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0430\u0432\u0442\u043E\u0440\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u043D\u0438\u0445 \u0434\u043E\u043B\u0436\u043D\u044B \u0434\u0435\u043B\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u043C\u043E\u0439"),
12062
+ // TASWM-505: разбиение — ПРЕДЛОЖЕНИЕ автору, а не действие дрона. Подзадачи дрон не
12063
+ // заводит сознательно: разбиение — продуктовое решение, а плохое разбиение хуже
12064
+ // никакого, потому что выглядит авторитетно (заведённые подзадачи прочтут как
12065
+ // согласованный план). Поэтому — строчками в комменте, которые автор правит или
12066
+ // выбрасывает одним движением.
12067
+ split: z.array(z.string().min(10)).max(7).optional().describe(
12068
+ "\u0422\u041E\u041B\u042C\u041A\u041E \u043A reason=too_big: \u043A\u0430\u043A \u0431\u044B \u0442\u044B \u0440\u0430\u0437\u0431\u0438\u043B \u0437\u0430\u0434\u0430\u0447\u0443 \u2014 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043A\u0443\u0441\u043A\u0443 \u043D\u0430 \u0441\u0442\u0440\u043E\u043A\u0443. \u0423\u0435\u0434\u0435\u0442 \u0430\u0432\u0442\u043E\u0440\u0443 \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0415\u041D\u0418\u0415\u041C \u0432 \u0442\u043E\u043C \u0436\u0435 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0435; \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u0430\u043C \u041D\u0415 \u0437\u0430\u0432\u043E\u0434\u0438 (\u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u2014 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0430\u0432\u0442\u043E\u0440\u0430)"
12069
+ )
11539
12070
  },
11540
12071
  guard(
11541
12072
  async ({
12073
+ reason,
11542
12074
  missing,
11543
12075
  readings,
11544
12076
  assumed,
11545
- questions
12077
+ questions,
12078
+ split
11546
12079
  }) => {
11547
12080
  if (await overmindDead()) return freeze();
11548
12081
  const run = await store.myRun();
@@ -11577,10 +12110,20 @@ ${notFoundText}` : notFoundText }],
11577
12110
  "",
11578
12111
  `${author ? `${author.name}, ` : ""}\u044F \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C \u0437\u0430 \u044D\u0442\u0443 \u0437\u0430\u0434\u0430\u0447\u0443: \u0435\u0451 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C, \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u0430\u0432 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0443 \u0437\u0430 \u0442\u0435\u0431\u044F. \u0427\u0442\u043E \u043C\u0435\u0448\u0430\u0435\u0442:`,
11579
12112
  "",
12113
+ `**\u041F\u0440\u0438\u0447\u0438\u043D\u0430.** ${returnReasonLabel(reason)} (\`${reason}\`).`,
12114
+ "",
11580
12115
  `**\u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442.** ${missing.trim()}`,
11581
12116
  ...readings?.length ? ["", "**\u041A\u0430\u043A \u044D\u0442\u043E \u043C\u043E\u0436\u043D\u043E \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C.**", ...readings.map((r) => `- ${r.trim()}`)] : [],
11582
12117
  "",
11583
12118
  `**\u0427\u0442\u043E \u044F \u043F\u0440\u0435\u0434\u043F\u043E\u043B\u043E\u0436\u0438\u043B \u0431\u044B \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u043D\u0435 \u0441\u0442\u0430\u043B.** ${assumed.trim()}`,
12119
+ // Разбиение — только предложение и только к too_big (TASWM-505). Формулировка
12120
+ // «на твоё усмотрение» здесь не вежливость: заведённые подзадачи выглядели бы
12121
+ // согласованным планом, а это решение автора, и плохое разбиение хуже никакого.
12122
+ ...reason === "too_big" && split?.length ? [
12123
+ "",
12124
+ "**\u041A\u0430\u043A \u044D\u0442\u043E \u043C\u043E\u0436\u043D\u043E \u0440\u0430\u0437\u0431\u0438\u0442\u044C** \u2014 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435, \u043D\u0435 \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435: \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u044F \u043D\u0435 \u0437\u0430\u0432\u043E\u0434\u0438\u043B, \u0440\u0435\u0448\u0430\u0442\u044C \u0442\u0435\u0431\u0435.",
12125
+ ...split.map((s) => `- ${s.trim()}`)
12126
+ ] : [],
11584
12127
  "",
11585
12128
  "**\u0412\u043E\u043F\u0440\u043E\u0441\u044B, \u043F\u043E\u0441\u043B\u0435 \u043E\u0442\u0432\u0435\u0442\u043E\u0432 \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0443 \u043C\u043E\u0436\u043D\u043E \u0431\u0440\u0430\u0442\u044C:**",
11586
12129
  ...questions.map((q, i) => `${i + 1}. ${q.trim()}`)
@@ -11624,9 +12167,9 @@ ${notFoundText}` : notFoundText }],
11624
12167
  };
11625
12168
  }
11626
12169
  }
11627
- const reason = `\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443${author ? ` (${author.name})` : ""}: ${missing.trim()}`;
11628
- await store.updateRun(null, { status: "returned", stop_reason: reason });
11629
- await store.addEvent(null, "returned", failures.length ? `${reason} \xB7 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E: ${failures.join("; ")}` : reason);
12170
+ const stopReason = `\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443${author ? ` (${author.name})` : ""} \xB7 ${returnReasonLabel(reason)}: ${missing.trim()}`;
12171
+ await store.updateRun(null, { status: "returned", stop_reason: stopReason, return_reason: reason });
12172
+ await store.addEvent(null, "returned", failures.length ? `${stopReason} \xB7 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E: ${failures.join("; ")}` : stopReason);
11630
12173
  return {
11631
12174
  content: [
11632
12175
  {
@@ -11972,9 +12515,11 @@ ${report}${shots.warning}`,
11972
12515
  const headBranch = opts.prHeadBranch ? await opts.prHeadBranch(pr_url).catch(() => null) : null;
11973
12516
  const branchFix = branchFromPrPatch(run?.branch, headBranch);
11974
12517
  if (branchFix) await store.addEvent(null, "step", branchFix.event);
12518
+ const held = await changedPaths(opts.worktree ?? process.cwd(), { baseBranch: run?.base_branch ?? null }).catch(() => []);
12519
+ const heldPatch = held.length ? { changed_paths: held } : {};
11975
12520
  if (run) {
11976
12521
  const t = reviewTransitionPatch(run, pr_url);
11977
- await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {} });
12522
+ await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {}, ...heldPatch });
11978
12523
  await store.addEvent(null, "pr_opened", pr_url);
11979
12524
  await postReport(reportSlug(run.task_slug), t.cycle ?? run.review_cycle ?? 0);
11980
12525
  const rulesNote = await offerRules(reportSlug(run.task_slug));
@@ -11989,7 +12534,7 @@ ${report}${shots.warning}`,
11989
12534
  }
11990
12535
  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}${zoneNote}` }] };
11991
12536
  }
11992
- await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {} });
12537
+ await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {}, ...heldPatch });
11993
12538
  await store.addEvent(null, "pr_opened", pr_url);
11994
12539
  const fallbackSlug = reportSlug(void 0);
11995
12540
  if (fallbackSlug) await postReport(fallbackSlug, void 0);
@@ -12062,8 +12607,9 @@ ${report}${shots.warning}`,
12062
12607
  if (!taskless) return errText2("Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D.");
12063
12608
  const hero = await heroRun();
12064
12609
  if (!hero) return errText2("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
12065
- const task = await taskless.getTask(slug);
12066
- if (!task) return errText2(`\u0437\u0430\u0434\u0430\u0447\u0430 ${slug} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 Taskless.`);
12610
+ const read = await taskless.getTaskDetailed(slug);
12611
+ if (!read.ok) return errText2(`\u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${describeToolFailure(read.failure)}.`);
12612
+ const task = read.task;
12067
12613
  const ep = await epic(hero.task_slug);
12068
12614
  if (!ep || task.parentId !== ep.id) {
12069
12615
  return errText2(`${slug} \u2014 \u043D\u0435 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0432\u043E\u0435\u0433\u043E \u044D\u043F\u0438\u043A\u0430 ${hero.task_slug}. \u0414\u0438\u0441\u043F\u0430\u0442\u0447\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u0432\u043E\u0438 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438.`);
@@ -12576,6 +13122,31 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
12576
13122
  })
12577
13123
  });
12578
13124
  }
13125
+ const GROUP_DEP_UNCONFIRMED = {
13126
+ id: "dep_unconfirmed",
13127
+ title: (n) => `${n} ${plural2(n, ["\u043F\u0440\u043E\u0433\u043E\u043D \u0436\u0434\u0451\u0442", "\u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0436\u0434\u0443\u0442", "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0436\u0434\u0443\u0442"])} \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043F\u0440\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442`
13128
+ };
13129
+ for (const run of allRuns) {
13130
+ if (run.status !== "queued") continue;
13131
+ if (!run.depends_on || run.depends_on.length === 0) continue;
13132
+ const blind = db.depsUnconfirmed?.(run.depends_on) ?? [];
13133
+ if (!blind.length) continue;
13134
+ candidates.push({
13135
+ key: `dep_unconfirmed:${run.task_slug}`,
13136
+ // по СЛАГУ, а не по id зеркала (см. dep_missing)
13137
+ minDurationMs: thresholds.depMissingMs,
13138
+ group: GROUP_DEP_UNCONFIRMED,
13139
+ build: () => ({
13140
+ runId: run.id,
13141
+ // Пуш: сам рой из этого не выйдет — вердикт приходит только от прода, а прод
13142
+ // отказывает устойчиво. Лечит человек (обновить прод / перевыдать токен демона).
13143
+ level: "decision",
13144
+ title: runTitle(run, "\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043F\u0440\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442"),
13145
+ body: `${droneOf(run)}: \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0442\u043E\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C\u044E ${joinCapped(blind.map((d) => d.slug))}, \u043D\u043E \u043F\u0440\u043E\u0434 \u043D\u0430 swarm_deps_status \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (${blind[0].attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A), \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u2014 ${blind[0].error}). \u0412\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442, \u043F\u043E\u043A\u0430 \u044D\u0442\u043E \u043D\u0435 \u043F\u043E\u0447\u0438\u043D\u0438\u0442\u044C: \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442, \u0430 \u043C\u0430\u0448\u0438\u043D\u0430 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u0430.`,
13146
+ ...hrefOf(run.task_url)
13147
+ })
13148
+ });
13149
+ }
12579
13150
  if (opts.gateReason && queuedCount > 0) {
12580
13151
  const reason = opts.gateReason;
12581
13152
  candidates.push({
@@ -12892,6 +13463,7 @@ function resilientSwarmMcp(opts = {}) {
12892
13463
 
12893
13464
  // src/overmind.ts
12894
13465
  init_taskless();
13466
+ init_toolFailure();
12895
13467
 
12896
13468
  // src/workspaceAccess.ts
12897
13469
  function workspaceAccessLines(input) {
@@ -13123,29 +13695,27 @@ function makeDroneCapReader(opts) {
13123
13695
 
13124
13696
  // src/mergeGate.ts
13125
13697
  init_src();
13698
+ init_config();
13126
13699
 
13127
13700
  // src/mergeQueue.ts
13128
13701
  init_src();
13702
+ init_config();
13129
13703
 
13130
13704
  // src/prNumber.ts
13131
- function prNumberOf(ref) {
13132
- const s = (ref || "").trim();
13133
- if (!s) return null;
13134
- const url = s.match(/\/pull\/(\d+)/);
13135
- if (url) return Number(url[1]);
13136
- const bare = s.match(/^#?(\d+)$/);
13137
- return bare ? Number(bare[1]) : null;
13138
- }
13705
+ init_src();
13139
13706
 
13140
13707
  // src/mergeQueue.ts
13141
13708
  var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
13142
13709
  function isMergeCandidate(r) {
13143
- return !!r.pr_url && r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
13710
+ return !!r.pr_url && isPrAuthorRun(r) && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
13711
+ }
13712
+ function isPrAuthorRun(r) {
13713
+ return r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null;
13144
13714
  }
13145
- function mergeQueueCandidates(runs, repo) {
13715
+ function mergeQueueCandidates(runs, repo, repoFallback) {
13146
13716
  const byPr = /* @__PURE__ */ new Map();
13147
13717
  for (const r of runs) {
13148
- if (r.repo !== repo || !isMergeCandidate(r)) continue;
13718
+ if (effectiveRepoOf(r.repo, repoFallback) !== repo || !isMergeCandidate(r)) continue;
13149
13719
  const num = prNumberOf(r.pr_url);
13150
13720
  const seen = byPr.get(num);
13151
13721
  if (!seen || r.id > seen.id) byPr.set(num, r);
@@ -13175,7 +13745,7 @@ function makeMergeQueueResolver(deps) {
13175
13745
  if (!deps.baseSync || !run.pr_url) return null;
13176
13746
  const num = prNumberOf(run.pr_url);
13177
13747
  if (num == null) return null;
13178
- const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
13748
+ const candidates = mergeQueueCandidates(deps.listRuns(), effectiveRepoOf(run.repo, deps.repoFallback), deps.repoFallback);
13179
13749
  const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
13180
13750
  if (!mine) return null;
13181
13751
  const ready = [];
@@ -13209,15 +13779,16 @@ function needsBaseRefresh(d) {
13209
13779
  }
13210
13780
 
13211
13781
  // src/mergeGate.ts
13212
- function findRunByPr(runs, ref) {
13213
- const num = prNumberOf(ref);
13214
- if (num == null) return null;
13215
- let best = null;
13216
- for (const r of runs) {
13217
- if (!r.pr_url || prNumberOf(r.pr_url) !== num) continue;
13218
- if (!best || r.id > best.id) best = r;
13219
- }
13220
- return best;
13782
+ function findRunByPr(runs, ref, scope = {}) {
13783
+ const rows = runs.map((run) => ({
13784
+ run,
13785
+ prUrl: run.pr_url,
13786
+ repoKey: scope.repoOfRun ? scope.repoOfRun(run) : null,
13787
+ author: isPrAuthorRun(run),
13788
+ reviewVerdict: run.review_verdict,
13789
+ order: run.id
13790
+ }));
13791
+ return pickRunForPr(rows, ref, scope.repo ?? null)?.run ?? null;
13221
13792
  }
13222
13793
  function makeMergeGateResolver(deps) {
13223
13794
  return makeMergeGateParts(deps).gate;
@@ -13233,15 +13804,17 @@ function makeMergeGateParts(deps) {
13233
13804
  verifyVerdict: run.verify_verdict ?? null
13234
13805
  });
13235
13806
  };
13807
+ const repoOfRun = deps.repoFullNameOf ? (r) => deps.repoFullNameOf(effectiveRepoOf(r.repo, deps.repoFallback)) : void 0;
13236
13808
  const queueFor = makeMergeQueueResolver({
13237
13809
  listRuns: deps.listRuns,
13238
13810
  gateAllows: async (r) => (await gateFor(r)).allowed,
13811
+ ...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
13239
13812
  ...deps.baseSync ? { baseSync: deps.baseSync } : {},
13240
13813
  ...deps.checks ? { checks: deps.checks } : {},
13241
13814
  ...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
13242
13815
  });
13243
- const gate = async (ref) => {
13244
- const run = findRunByPr(deps.listRuns(), ref);
13816
+ const gate = async (ref, ctxRepo) => {
13817
+ const run = findRunByPr(deps.listRuns(), ref, { repo: ctxRepo ?? null, ...repoOfRun ? { repoOfRun } : {} });
13245
13818
  if (!run) {
13246
13819
  return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
13247
13820
  }
@@ -13264,22 +13837,22 @@ function makeMergeGateParts(deps) {
13264
13837
  }
13265
13838
 
13266
13839
  // src/envGate.ts
13267
- import { existsSync as existsSync14 } from "fs";
13268
- import { join as join25 } from "path";
13840
+ import { existsSync as existsSync15 } from "fs";
13841
+ import { join as join27 } from "path";
13269
13842
  import { createConnection } from "net";
13270
13843
  import { execa as execa2 } from "execa";
13271
13844
 
13272
13845
  // src/platform.ts
13273
- import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
13274
- import { delimiter, join as join24 } from "path";
13846
+ import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
13847
+ import { delimiter, join as join26 } from "path";
13275
13848
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
13276
- if (name.includes("/") || name.includes("\\")) return existsSync13(name) ? name : null;
13849
+ if (name.includes("/") || name.includes("\\")) return existsSync14(name) ? name : null;
13277
13850
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
13278
13851
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
13279
13852
  for (const dir of dirs) {
13280
13853
  for (const ext of exts) {
13281
- const candidate = join24(dir, name + ext);
13282
- if (existsSync13(candidate)) return candidate;
13854
+ const candidate = join26(dir, name + ext);
13855
+ if (existsSync14(candidate)) return candidate;
13283
13856
  }
13284
13857
  }
13285
13858
  return null;
@@ -13362,8 +13935,8 @@ var COMPOSE_MARKERS = [
13362
13935
  "compose.yaml"
13363
13936
  ];
13364
13937
  function detectRequirements(repo, io = {}) {
13365
- const exists = io.exists ?? existsSync14;
13366
- const marker = COMPOSE_MARKERS.find((f) => exists(join25(repo, f)));
13938
+ const exists = io.exists ?? existsSync15;
13939
+ const marker = COMPOSE_MARKERS.find((f) => exists(join27(repo, f)));
13367
13940
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
13368
13941
  }
13369
13942
  function resolveRequirements(repo, declared, io = {}) {
@@ -13498,210 +14071,10 @@ import { randomBytes as randomBytes3 } from "crypto";
13498
14071
  import { basename as basename5 } from "path";
13499
14072
  import { serve as serve2 } from "@hono/node-server";
13500
14073
 
13501
- // src/diff.ts
13502
- import { statSync as statSync10 } from "fs";
13503
- import { join as join26 } from "path";
13504
- import { $ as $4 } from "execa";
13505
- var MAX_FILES = 200;
13506
- var MAX_LINES_PER_FILE = 1500;
13507
- var MAX_UNTRACKED = 60;
13508
- function cleanPath(raw) {
13509
- let p = raw.trim();
13510
- if (p.startsWith('"') && p.endsWith('"') && p.length >= 2) p = p.slice(1, -1);
13511
- return p;
13512
- }
13513
- function stripAB(raw) {
13514
- const p = cleanPath(raw);
13515
- if (p === "/dev/null") return p;
13516
- if (p.startsWith("a/") || p.startsWith("b/")) return p.slice(2);
13517
- return p;
13518
- }
13519
- function parseUnifiedDiff(text) {
13520
- const files = [];
13521
- if (!text) return files;
13522
- const lines = text.split("\n");
13523
- let f = null;
13524
- let hunk = null;
13525
- let oldNo = 0;
13526
- let newNo = 0;
13527
- const push = () => {
13528
- if (f) files.push(f);
13529
- };
13530
- for (const line of lines) {
13531
- if (line.startsWith("diff --git ")) {
13532
- push();
13533
- hunk = null;
13534
- const m = line.match(/^diff --git (.+) (.+)$/);
13535
- const guess = m ? stripAB(m[2]) : "";
13536
- f = { path: guess, oldPath: null, status: "modified", added: 0, removed: 0, binary: false, hunks: [] };
13537
- continue;
13538
- }
13539
- if (!f) continue;
13540
- if (line.startsWith("new file mode")) {
13541
- f.status = "added";
13542
- continue;
13543
- }
13544
- if (line.startsWith("deleted file mode")) {
13545
- f.status = "deleted";
13546
- continue;
13547
- }
13548
- if (line.startsWith("rename from ") || line.startsWith("copy from ")) {
13549
- f.oldPath = cleanPath(line.replace(/^(rename|copy) from /, ""));
13550
- f.status = "renamed";
13551
- continue;
13552
- }
13553
- if (line.startsWith("rename to ") || line.startsWith("copy to ")) {
13554
- f.path = cleanPath(line.replace(/^(rename|copy) to /, ""));
13555
- f.status = "renamed";
13556
- continue;
13557
- }
13558
- if (line.startsWith("Binary files ")) {
13559
- f.binary = true;
13560
- continue;
13561
- }
13562
- if (line.startsWith("--- ")) {
13563
- const p = stripAB(line.slice(4));
13564
- if (p === "/dev/null") f.status = "added";
13565
- continue;
13566
- }
13567
- if (line.startsWith("+++ ")) {
13568
- const p = stripAB(line.slice(4));
13569
- if (p === "/dev/null") f.status = "deleted";
13570
- else if (p) f.path = p;
13571
- continue;
13572
- }
13573
- if (line.startsWith("@@")) {
13574
- const m = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@(.*)$/);
13575
- if (m) {
13576
- oldNo = Number(m[1]);
13577
- newNo = Number(m[2]);
13578
- hunk = { header: (m[3] ?? "").trim(), lines: [] };
13579
- f.hunks.push(hunk);
13580
- }
13581
- continue;
13582
- }
13583
- if (!hunk) continue;
13584
- if (line.startsWith("\\")) continue;
13585
- const tag = line[0];
13586
- const body = line.slice(1);
13587
- if (tag === "+") {
13588
- hunk.lines.push({ k: "add", o: null, n: newNo++, t: body });
13589
- f.added++;
13590
- } else if (tag === "-") {
13591
- hunk.lines.push({ k: "del", o: oldNo++, n: null, t: body });
13592
- f.removed++;
13593
- } else if (tag === " ") {
13594
- hunk.lines.push({ k: "ctx", o: oldNo++, n: newNo++, t: body });
13595
- }
13596
- }
13597
- push();
13598
- return files;
13599
- }
13600
- function truncateFile(f) {
13601
- let count = 0;
13602
- const hunks = [];
13603
- let cut = false;
13604
- for (const h of f.hunks) {
13605
- if (count >= MAX_LINES_PER_FILE) {
13606
- cut = true;
13607
- break;
13608
- }
13609
- const room = MAX_LINES_PER_FILE - count;
13610
- if (h.lines.length <= room) {
13611
- hunks.push(h);
13612
- count += h.lines.length;
13613
- } else {
13614
- hunks.push({ header: h.header, lines: h.lines.slice(0, room) });
13615
- count += room;
13616
- cut = true;
13617
- break;
13618
- }
13619
- }
13620
- return cut ? { ...f, hunks, truncated: true } : f;
13621
- }
13622
- async function resolveBase(wt, baseBranch) {
13623
- const tryRef = async (ref) => {
13624
- const sha = (await $4({ reject: false })`git -C ${wt} rev-parse --verify --quiet ${ref + "^{commit}"}`).stdout.trim();
13625
- if (!sha) return null;
13626
- const mb2 = (await $4({ reject: false })`git -C ${wt} merge-base HEAD ${ref}`).stdout.trim();
13627
- return mb2 || null;
13628
- };
13629
- const originHead = (await $4({ reject: false })`git -C ${wt} symbolic-ref --quiet --short refs/remotes/origin/HEAD`).stdout.trim();
13630
- const candidates = [
13631
- ...baseBranch ? [`origin/${baseBranch}`, baseBranch] : [],
13632
- originHead,
13633
- "origin/main",
13634
- "origin/master",
13635
- "main",
13636
- "master"
13637
- ].filter(Boolean);
13638
- for (const c of candidates) {
13639
- const mb2 = await tryRef(c);
13640
- if (mb2) return mb2;
13641
- }
13642
- return null;
13643
- }
13644
- function mtime(path) {
13645
- try {
13646
- return statSync10(path).mtimeMs;
13647
- } catch {
13648
- return 0;
13649
- }
13650
- }
13651
- async function collectRunDiff(worktree, opts = {}) {
13652
- const stub = (reason) => ({ ok: false, reason, pr: opts.pr ?? null, files: [], added: 0, removed: 0 });
13653
- const gone = opts.status === "queued" ? "\u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u043A\u043E\u0434\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442" : "worktree \u0443\u0431\u0440\u0430\u043D \u2014 \u043A\u043E\u0434 \u0441\u043C\u043E\u0442\u0440\u0438 \u0432 PR";
13654
- if (!worktree) return stub(gone);
13655
- const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
13656
- if (isWt !== "true") return stub(gone);
13657
- const base3 = await resolveBase(worktree, opts.baseBranch);
13658
- const from = base3 ?? "HEAD";
13659
- const trackedTxt = (await $4({ reject: false })`git -C ${worktree} diff --no-color --find-renames ${from} --`).stdout;
13660
- let files = parseUnifiedDiff(trackedTxt);
13661
- const untrackedRaw = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
13662
- const untracked = untrackedRaw.split("\0").filter(Boolean).slice(0, MAX_UNTRACKED);
13663
- for (const rel of untracked) {
13664
- const t = (await $4({ reject: false })`git -C ${worktree} diff --no-color --no-index -- /dev/null ${rel}`).stdout;
13665
- for (const pf of parseUnifiedDiff(t)) {
13666
- pf.status = "added";
13667
- if (!pf.path || pf.path === "/dev/null") pf.path = rel;
13668
- files.push(pf);
13669
- }
13670
- }
13671
- files.sort((a, b) => mtime(join26(worktree, b.path)) - mtime(join26(worktree, a.path)));
13672
- const truncatedFiles = files.length > MAX_FILES;
13673
- if (truncatedFiles) files = files.slice(0, MAX_FILES);
13674
- const added = files.reduce((s, f) => s + f.added, 0);
13675
- const removed = files.reduce((s, f) => s + f.removed, 0);
13676
- const out = files.map(truncateFile);
13677
- return {
13678
- ok: true,
13679
- base: base3 ? base3.slice(0, 9) : null,
13680
- files: out,
13681
- added,
13682
- removed,
13683
- ...truncatedFiles ? { truncated: true } : {}
13684
- };
13685
- }
13686
- async function changedPaths(worktree, opts = {}) {
13687
- if (!worktree) return [];
13688
- try {
13689
- const isWt = (await $4({ reject: false })`git -C ${worktree} rev-parse --is-inside-work-tree`).stdout.trim();
13690
- if (isWt !== "true") return [];
13691
- const from = await resolveBase(worktree, opts.baseBranch) ?? "HEAD";
13692
- const tracked = (await $4({ reject: false })`git -C ${worktree} diff --name-only --find-renames ${from} --`).stdout;
13693
- const untracked = (await $4({ reject: false })`git -C ${worktree} ls-files --others --exclude-standard -z`).stdout;
13694
- const all = [...tracked.split("\n"), ...untracked.split("\0")].map((s) => s.trim()).filter(Boolean);
13695
- return [...new Set(all)].sort();
13696
- } catch {
13697
- return [];
13698
- }
13699
- }
13700
-
13701
14074
  // package.json
13702
14075
  var package_default = {
13703
14076
  name: "@taskless-app/swarm",
13704
- version: "0.6.24",
14077
+ version: "0.6.26",
13705
14078
  type: "module",
13706
14079
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13707
14080
  keywords: [
@@ -13731,6 +14104,7 @@ var package_default = {
13731
14104
  files: [
13732
14105
  "dist",
13733
14106
  "docs/HERO.md",
14107
+ "hooks",
13734
14108
  "roles"
13735
14109
  ],
13736
14110
  scripts: {
@@ -13751,7 +14125,7 @@ var package_default = {
13751
14125
  execa: "^10.0.0",
13752
14126
  zod: "3.25.76"
13753
14127
  },
13754
- gitHead: "af6c4af0f523891b5db805d9b4583021688ed1f5"
14128
+ gitHead: "52416995d46b0a843374d14bfb00dd310f71c4bf"
13755
14129
  };
13756
14130
 
13757
14131
  // src/version.ts
@@ -15199,13 +15573,15 @@ function makeHandler(deps) {
15199
15573
  if (p === "/merge-gate") {
15200
15574
  const ref = url.searchParams.get("pr") ?? "";
15201
15575
  if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
15576
+ const ctxRepo = url.searchParams.get("repo");
15202
15577
  const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
15203
15578
  listRuns: () => deps.db.listRuns(),
15204
15579
  fallbackAutoReview: SWARM_AUTO_REVIEW,
15205
- autoVerify: SWARM_AUTO_VERIFY
15580
+ autoVerify: SWARM_AUTO_VERIFY,
15206
15581
  // TASWM-344
15582
+ ...defaultRepo ? { repoFallback: defaultRepo } : {}
15207
15583
  });
15208
- return Response.json(await resolve4(ref));
15584
+ return Response.json(await resolve4(ref, ctxRepo));
15209
15585
  }
15210
15586
  if (p === "/resources") return Response.json(hostResourceSnapshot());
15211
15587
  if (p === "/timeline") return Response.json(timelineState(deps.db, Date.now(), readNarratorLog(deps.narratorLog)));
@@ -15384,21 +15760,21 @@ async function checkDaemonVersion(start, opts = {}) {
15384
15760
 
15385
15761
  // src/hostCommands.ts
15386
15762
  init_src();
15387
- import { readFileSync as readFileSync18, renameSync as renameSync9, writeFileSync as writeFileSync13 } from "fs";
15388
- import { join as join27 } from "path";
15763
+ import { readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
15764
+ import { join as join28 } from "path";
15389
15765
  function hostCommandStatePath(home) {
15390
- return join27(home, "host-command.json");
15766
+ return join28(home, "host-command.json");
15391
15767
  }
15392
15768
  function readState(path) {
15393
15769
  try {
15394
- return JSON.parse(readFileSync18(path, "utf8"));
15770
+ return JSON.parse(readFileSync19(path, "utf8"));
15395
15771
  } catch {
15396
15772
  return {};
15397
15773
  }
15398
15774
  }
15399
15775
  function writeState(path, state) {
15400
15776
  try {
15401
- writeFileSync13(`${path}.tmp`, JSON.stringify(state));
15777
+ writeFileSync14(`${path}.tmp`, JSON.stringify(state));
15402
15778
  renameSync9(`${path}.tmp`, path);
15403
15779
  } catch {
15404
15780
  }
@@ -15561,8 +15937,8 @@ async function doRestart(cmd, opts, note) {
15561
15937
 
15562
15938
  // src/selfUpdate.ts
15563
15939
  init_git();
15564
- import { existsSync as existsSync15 } from "fs";
15565
- import { join as join28 } from "path";
15940
+ import { existsSync as existsSync16 } from "fs";
15941
+ import { join as join29 } from "path";
15566
15942
  import { $ as $5 } from "execa";
15567
15943
  var DAEMON_PACKAGE = "@taskless-app/swarm";
15568
15944
  var defaultRun = async (cmd, args, cwd) => {
@@ -15575,7 +15951,7 @@ function tail(text, max = 400) {
15575
15951
  }
15576
15952
  async function runSelfUpdate(io) {
15577
15953
  const run = io.run ?? defaultRun;
15578
- const exists = io.exists ?? existsSync15;
15954
+ const exists = io.exists ?? existsSync16;
15579
15955
  const repo = io.repo;
15580
15956
  await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
15581
15957
  const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
@@ -15604,7 +15980,7 @@ async function runSelfUpdate(io) {
15604
15980
  };
15605
15981
  }
15606
15982
  await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
15607
- if (!exists(join28(repo, "pnpm-lock.yaml"))) {
15983
+ if (!exists(join29(repo, "pnpm-lock.yaml"))) {
15608
15984
  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" };
15609
15985
  }
15610
15986
  const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
@@ -15763,19 +16139,19 @@ async function fetchExternalStorm(url, opts = {}) {
15763
16139
  }
15764
16140
 
15765
16141
  // src/anomalyState.ts
15766
- import { existsSync as existsSync16, readFileSync as readFileSync19, renameSync as renameSync10, writeFileSync as writeFileSync14 } from "fs";
15767
- import { join as join29 } from "path";
16142
+ import { existsSync as existsSync17, readFileSync as readFileSync20, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
16143
+ import { join as join30 } from "path";
15768
16144
  var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
15769
16145
  var MAX_RESOLVED = 200;
15770
16146
  function emptyState() {
15771
16147
  return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
15772
16148
  }
15773
16149
  function anomalyStatePath(home) {
15774
- return join29(home, "anomaly-state.json");
16150
+ return join30(home, "anomaly-state.json");
15775
16151
  }
15776
16152
  function loadAnomalyState(home, now = Date.now(), io = {}) {
15777
- const exists = io.exists ?? existsSync16;
15778
- const readFile2 = io.readFile ?? ((p) => readFileSync19(p, "utf8"));
16153
+ const exists = io.exists ?? existsSync17;
16154
+ const readFile2 = io.readFile ?? ((p) => readFileSync20(p, "utf8"));
15779
16155
  const path = anomalyStatePath(home);
15780
16156
  if (!exists(path)) return emptyState();
15781
16157
  try {
@@ -15789,9 +16165,9 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
15789
16165
  )
15790
16166
  );
15791
16167
  return {
15792
- since: pick2(since, fresh),
15793
- lastFiredAt: pick2(lastFiredAt, fresh),
15794
- fires: pick2(fires, fresh),
16168
+ since: pick3(since, fresh),
16169
+ lastFiredAt: pick3(lastFiredAt, fresh),
16170
+ fires: pick3(fires, fresh),
15795
16171
  // TAS-485: очередь гашения НЕ фильтруем тем же TTL, что ключи. Она про
15796
16172
  // уведомление, которое УЖЕ висит у человека в инбоксе: пролежав неделю
15797
16173
  // недоставленным, гашение не становится лишним — оно становится нужнее.
@@ -15805,7 +16181,7 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
15805
16181
  function saveAnomalyState(home, state, io = {}) {
15806
16182
  const path = anomalyStatePath(home);
15807
16183
  const writeFile3 = io.writeFile ?? ((p, data) => {
15808
- writeFileSync14(`${p}.tmp`, data);
16184
+ writeFileSync15(`${p}.tmp`, data);
15809
16185
  renameSync10(`${p}.tmp`, p);
15810
16186
  });
15811
16187
  writeFile3(path, JSON.stringify(state, null, 2));
@@ -15875,7 +16251,7 @@ function strings(raw) {
15875
16251
  if (!Array.isArray(raw)) return [];
15876
16252
  return raw.filter((v) => typeof v === "string" && v.length > 0);
15877
16253
  }
15878
- function pick2(src, keys) {
16254
+ function pick3(src, keys) {
15879
16255
  const out = {};
15880
16256
  for (const k of keys) if (src[k] != null) out[k] = src[k];
15881
16257
  return out;
@@ -16131,6 +16507,12 @@ function predictForRun(model, run) {
16131
16507
  function short(files, n = 3) {
16132
16508
  return files.slice(0, n).join(", ") + (files.length > n ? ` \u0438 \u0435\u0449\u0451 ${files.length - n}` : "");
16133
16509
  }
16510
+ function heldBecause(w) {
16511
+ const parts = [];
16512
+ if (w.prUrl) parts.push(`\u0440\u0430\u0431\u043E\u0442\u0430 \u0441\u0434\u0430\u043D\u0430, PR \u0435\u0449\u0451 \u043D\u0435 \u0432\u043B\u0438\u0442: ${w.prUrl}`);
16513
+ if (!w.actual) parts.push(w.prUrl ? "\u0444\u0430\u0439\u043B\u044B \u0432\u0437\u044F\u0442\u044B \u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0441\u043D\u0438\u043C\u043A\u0430 \u043D\u0435 \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C" : "\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434");
16514
+ return parts.length ? ` (${parts.join("; ")})` : "";
16515
+ }
16134
16516
  var SPINE_PREDICTION_WEIGHT = 0.7;
16135
16517
  function spineOfPrediction(spine, predicted) {
16136
16518
  return predicted.filter((p) => spine.has(p.path) && p.weight >= SPINE_PREDICTION_WEIGHT).map((p) => p.path);
@@ -16151,7 +16533,7 @@ function decideConflict({ model, candidate, active }) {
16151
16533
  return {
16152
16534
  kind: "wait",
16153
16535
  blockedBy: w.run,
16154
- message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 \u043E\u0434\u043D\u0438 \u0444\u0430\u0439\u043B\u044B \u043E\u0431\u0449\u0435\u0433\u043E \u0445\u0440\u0435\u0431\u0442\u0430 (${short(shared)}) \u2014 \u0442\u0430\u043A\u0438\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + (w.actual ? "" : " (\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434)")
16536
+ message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 \u043E\u0434\u043D\u0438 \u0444\u0430\u0439\u043B\u044B \u043E\u0431\u0449\u0435\u0433\u043E \u0445\u0440\u0435\u0431\u0442\u0430 (${short(shared)}) \u2014 \u0442\u0430\u043A\u0438\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + heldBecause(w)
16155
16537
  };
16156
16538
  }
16157
16539
  }
@@ -16162,7 +16544,7 @@ function decideConflict({ model, candidate, active }) {
16162
16544
  return {
16163
16545
  kind: "wait",
16164
16546
  blockedBy: w.run,
16165
- message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 ${what} \u2014 \u0441\u0442\u0430\u0432\u0438\u043C \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + (w.actual ? "" : " (\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434)")
16547
+ message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 ${what} \u2014 \u0441\u0442\u0430\u0432\u0438\u043C \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + heldBecause(w)
16166
16548
  };
16167
16549
  }
16168
16550
  return { kind: "clear" };
@@ -16215,10 +16597,16 @@ var ActiveFilesCache = class {
16215
16597
  this.cache.set(run.id, { at: now, files });
16216
16598
  return files;
16217
16599
  }
16218
- /** Забыть всех, кого нет среди живых (дрон закончил — его файлы больше не заняты). */
16219
- prune(liveIds) {
16220
- const live = new Set(liveIds);
16221
- for (const id of [...this.cache.keys()]) if (!live.has(id)) this.cache.delete(id);
16600
+ /**
16601
+ * Забыть всех, кто больше НЕ ДЕРЖИТ файлы. Раньше здесь стоял список ЖИВЫХ дронов и
16602
+ * комментарий «дрон закончил — его файлы больше не заняты»; это и была дырка TASWM-506:
16603
+ * дрон заканчивал, открывал PR, PR висел непомерженным — а строки уже считались
16604
+ * свободными. Держатель теперь переживает своего дрона и уходит из кэша на мерже
16605
+ * (прогон выходит из `review`), см. holdsFiles.
16606
+ */
16607
+ prune(holderIds) {
16608
+ const holders = new Set(holderIds);
16609
+ for (const id of [...this.cache.keys()]) if (!holders.has(id)) this.cache.delete(id);
16222
16610
  }
16223
16611
  };
16224
16612
  function withManualAreas(history, areas) {
@@ -16227,7 +16615,7 @@ function withManualAreas(history, areas) {
16227
16615
 
16228
16616
  // src/overmind.ts
16229
16617
  var ANOMALY_RESOLVE_BATCH = 50;
16230
- var THIS_FILE = fileURLToPath2(import.meta.url);
16618
+ var THIS_FILE = fileURLToPath3(import.meta.url);
16231
16619
  var executorHasSession = (p) => EXECUTOR.session.exists(p);
16232
16620
  var OVERMIND_ENTRY = THIS_FILE;
16233
16621
  var STALL_MS = Number(process.env.SWARM_STALL_MS) || 10 * 60 * 1e3;
@@ -16827,12 +17215,19 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
16827
17215
  // вторую ветку мимо PR. Пишем ту, что реально записана на прогоне; пусто (первый спавн —
16828
17216
  // ветку заводит spawnDrone из слага) — прежняя формулировка.
16829
17217
  `\u0422\u044B \u2014 \u0434\u0440\u043E\u043D \u0440\u043E\u044F. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u044D\u0442\u043E\u043C git-worktree, \u0432\u0435\u0442\u043A\u0430 \`${run.branch || run.task_slug}\`${run.branch && run.branch !== run.task_slug ? " (\u043E\u043D\u0430 \u041D\u0415 \u0440\u0430\u0432\u043D\u0430 \u0441\u043B\u0430\u0433\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E PR, \u043F\u0443\u0448 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043D\u0435\u0451)" : " \u0441\u043E\u0437\u0434\u0430\u043D\u0430 \u043F\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438"}.`,
16830
- "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; return_task(missing, readings, assumed, questions) \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0421\u042B\u0420\u0423\u042E \u0437\u0430\u0434\u0430\u0447\u0443 \u0430\u0432\u0442\u043E\u0440\u0443 (\u0441\u043C. \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0438\u0436\u0435); note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
17218
+ "\u0418\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B overmind (MCP): report_step(text) \u2014 \u043B\u043E\u0433\u0438\u0440\u0443\u0439 \u0445\u043E\u0434 \u0440\u0430\u0431\u043E\u0442\u044B; read_task() \u2014 \u0441\u0432\u0435\u0436\u0438\u0439 \u0441\u043D\u0430\u043F\u0448\u043E\u0442 \u0437\u0430\u0434\u0430\u0447\u0438 (\u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435+\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438+\u043A\u043E\u043C\u043C\u0435\u043D\u0442\u044B+\u0434\u043E\u043A\u0438+\u0438\u0441\u0445\u043E\u0434\u044B \u0441\u043E\u0441\u0435\u0434\u043D\u0438\u0445 \u0437\u0430\u0434\u0430\u0447 \u043F\u043E \u0437\u043E\u043D\u0435 \u043A\u043E\u0434\u0430) \u0431\u0435\u0437 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432, \u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F \u043C\u043E\u0433\u043B\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0442\u0430\u0440\u0442\u0430; ask(question, options?) \u2014 \u0435\u0441\u043B\u0438 \u0443\u043F\u0451\u0440\u0441\u044F \u0432 \u0420\u0415\u0428\u0415\u041D\u0418\u0415, \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 (\u0434\u0430\u0439 2\u20134 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430 \u0432 options, \u0435\u0441\u043B\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0430 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u043D\u0430\u044F) \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0430 (\u041D\u0415 \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0439); report_acceptance_check(id, state, evidence) \u2014 \u0437\u0430\u043A\u0440\u043E\u0439 \u041A\u0410\u0416\u0414\u042B\u0419 \u043F\u0443\u043D\u043A\u0442 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 (id \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0438\u0437 \u0431\u043B\u043E\u043A\u0430 \xAB\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438\xBB, state = passed|failed|pending \u043F\u043E \u0444\u0430\u043A\u0442\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, evidence \u2014 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B/\u0441\u0441\u044B\u043B\u043A\u0430/\u0447\u0442\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u043B); \u043F\u0440\u043E\u0439\u0434\u0438\u0441\u044C \u043F\u043E \u043D\u0438\u043C \u041F\u0415\u0420\u0415\u0414 done(), \u0438\u043D\u0430\u0447\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u0440\u0443\u043A\u0430\u043C\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440; done(pr_url) \u2014 \u043A\u043E\u0433\u0434\u0430 \u043E\u0442\u043A\u0440\u043E\u0435\u0448\u044C PR; return_task(reason, missing, readings, assumed, questions) \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0421\u042B\u0420\u0423\u042E \u0437\u0430\u0434\u0430\u0447\u0443 \u0430\u0432\u0442\u043E\u0440\u0443 (\u0441\u043C. \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0438\u0436\u0435); note(text) \u2014 \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \xAB\u0437\u0430\u043C\u0435\u0447\u0435\u043D\u043E \u043F\u043E \u043F\u0443\u0442\u0438\xBB \u0411\u0415\u0417 \u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u043A\u0438: \u043D\u0430\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u043D\u0430 \u043F\u0440\u043E\u0431\u0435\u043B \u0432 \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435, \u043F\u0440\u043E\u0442\u0443\u0445\u0448\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E, \u0431\u0430\u0433 \u0440\u044F\u0434\u043E\u043C \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0434\u0438\u0444\u0444\u043E\u043C \u2014 \u0441\u043A\u0430\u0436\u0438 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0434\u0430\u043B\u044C\u0448\u0435, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0443\u0432\u0438\u0434\u0438\u0442 \u0438 \u0441\u0430\u043C \u0440\u0435\u0448\u0438\u0442, \u0437\u0430\u0432\u043E\u0434\u0438\u0442\u044C \u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 (\u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0439 \u0442\u0430\u043A\u043E\u0435 \u0432 \u0434\u043E\u0440\u043E\u0433\u043E\u0439 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 ask \u0438 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u0438 \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u043E \u0441\u0434\u0430\u0447\u0435).",
16831
17219
  // TASWM-412: третий выход из непонятной задачи. Раньше их было два, и оба плохие —
16832
17220
  // угадать постановку (сделает не то) или ask (слот занят, а ответить может только автор
16833
17221
  // задачи, не оператор роя). Граница здесь ОБЯЗАТЕЛЬНА: дрон, легко возвращающий задачи,
16834
17222
  // становится отписочником, а это хуже, чем дрон, который иногда угадывает неверно.
16835
- "\u0421\u042B\u0420\u0423\u042E \u0417\u0410\u0414\u0410\u0427\u0423 \u041D\u0415 \u0412\u042B\u0414\u0423\u041C\u042B\u0412\u0410\u0419 \u2014 \u0412\u0415\u0420\u041D\u0418 \u0410\u0412\u0422\u041E\u0420\u0423. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0432 \u0437\u0430\u0434\u0430\u0447\u0443, \u043E\u0442\u0432\u0435\u0442\u044C \u0441\u0435\u0431\u0435 \u043D\u0430 \u0434\u0432\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u0430: \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u043B\u0438, \u0427\u0422\u041E \u043D\u0443\u0436\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C, \u0438 \u0435\u0441\u0442\u044C \u043B\u0438 \u0442\u043E, \u041F\u041E \u0427\u0415\u041C\u0423 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u041E\u0431\u0430 \xAB\u043D\u0435\u0442\xBB \u2192 return_task(missing, readings, assumed, questions): \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0430\u0432\u0442\u043E\u0440\u0443, \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u0432 \u0440\u0430\u0431\u043E\u0442\u0443, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u043A\u0430\u043A \xAB\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443\xBB (\u043D\u0435 \xAB\u043F\u0440\u043E\u0432\u0430\u043B\u0435\u043D\u0430\xBB), \u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044C \u041C\u041E\u0416\u041D\u041E, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0438\u043B\u0438 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0422\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0439 \u0434\u0432\u0430 \u0438 \u043E\u0431\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B \u2014 \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043D\u044F\u0442\u043D\u0430, \u043D\u043E \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u044D\u0442\u043E ask, \u0430 \u043D\u0435 \u0432\u043E\u0437\u0432\u0440\u0430\u0442.",
17223
+ "\u0421\u042B\u0420\u0423\u042E \u0417\u0410\u0414\u0410\u0427\u0423 \u041D\u0415 \u0412\u042B\u0414\u0423\u041C\u042B\u0412\u0410\u0419 \u2014 \u0412\u0415\u0420\u041D\u0418 \u0410\u0412\u0422\u041E\u0420\u0423. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0432 \u0437\u0430\u0434\u0430\u0447\u0443, \u043E\u0442\u0432\u0435\u0442\u044C \u0441\u0435\u0431\u0435 \u043D\u0430 \u0434\u0432\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u0430: \u043F\u043E\u043D\u044F\u0442\u043D\u043E \u043B\u0438, \u0427\u0422\u041E \u043D\u0443\u0436\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C, \u0438 \u0435\u0441\u0442\u044C \u043B\u0438 \u0442\u043E, \u041F\u041E \u0427\u0415\u041C\u0423 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u041E\u0431\u0430 \xAB\u043D\u0435\u0442\xBB \u2192 return_task(reason, missing, readings, assumed, questions): \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0443\u0435\u0434\u0443\u0442 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0430\u0432\u0442\u043E\u0440\u0443, \u0437\u0430\u0434\u0430\u0447\u0430 \u0443\u0439\u0434\u0451\u0442 \u043D\u0430 \u043D\u0435\u0433\u043E \u0438 \u0432 \u0440\u0430\u0431\u043E\u0442\u0443, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u043A\u0430\u043A \xAB\u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0435\u043D\u0430 \u0430\u0432\u0442\u043E\u0440\u0443\xBB (\u043D\u0435 \xAB\u043F\u0440\u043E\u0432\u0430\u043B\u0435\u043D\u0430\xBB), \u0441\u043B\u043E\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u0441\u044F. \u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044C \u041C\u041E\u0416\u041D\u041E, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043D\u0438 \u0432 \u043E\u0434\u043D\u043E\u043C \u0440\u0430\u0437\u0443\u043C\u043D\u043E\u043C \u0442\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0438 \u0438\u043B\u0438 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0422\u043E\u043B\u043A\u043E\u0432\u0430\u043D\u0438\u0439 \u0434\u0432\u0430 \u0438 \u043E\u0431\u0430 \u043E\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B \u2014 \u0432\u044B\u0431\u0435\u0440\u0438 \u043E\u0447\u0435\u0432\u0438\u0434\u043D\u043E\u0435, \u0441\u0434\u0435\u043B\u0430\u0439 \u0438 \u043D\u0430\u0437\u043E\u0432\u0438 \u0440\u0430\u0437\u0432\u0438\u043B\u043A\u0443 \u0432 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0438 PR. \u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043D\u044F\u0442\u043D\u0430, \u043D\u043E \u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0443\u043D\u043A\u0442\u0435 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u044D\u0442\u043E ask, \u0430 \u043D\u0435 \u0432\u043E\u0437\u0432\u0440\u0430\u0442. \u041F\u0440\u0438\u0447\u0438\u043D\u0443 \u043D\u0430\u0437\u043E\u0432\u0438 \u041A\u041E\u0414\u041E\u041C (reason): unclear_goal \u2014 \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C; no_acceptance \u2014 \u043D\u0435\u0447\u0435\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442; too_big \u2014 \u0441\u043C. \u043D\u0438\u0436\u0435; contradiction \u2014 \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0441\u0430\u043C\u0430 \u0441\u0435\u0431\u0435. \u041A\u043E\u0434 \u043C\u0435\u0440\u044F\u0435\u0442 \u043D\u0435 \u0442\u0435\u0431\u044F, \u0430 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u043E \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043E\u043A \u043F\u043E \u0431\u044D\u043A\u043B\u043E\u0433\u0443, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0441\u0442\u0430\u0432\u044C \u0447\u0435\u0441\u0442\u043D\u044B\u0439, \u0430 \u043D\u0435 \u0443\u0434\u043E\u0431\u043D\u044B\u0439.",
17224
+ // TASWM-505: ЕДИНСТВЕННЫЙ сорт негодной задачи, который не покрывался ничем. claimPolicy
17225
+ // (TASWM-357) отсеивает родителей с незакрытыми детьми, но лист на 8 часов уезжает дрону
17226
+ // целиком — и он упирается в потолок трат на середине, оставляя недоделанную работу
17227
+ // человеку. Это уже случалось. Подзадачи дрон при этом НЕ заводит: разбиение —
17228
+ // продуктовое решение, а плохое разбиение хуже никакого, потому что выглядит
17229
+ // авторитетно (заведённые подзадачи читаются как согласованный план).
17230
+ "\u0421\u041B\u0418\u0428\u041A\u041E\u041C \u0412\u0415\u041B\u0418\u041A\u0410 \u2014 \u0442\u043E\u0436\u0435 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 (reason=too_big). \u0417\u0430\u0434\u0430\u0447\u0430 \u043F\u043E\u043D\u044F\u0442\u043D\u0430 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u0430, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0437\u0430\u0434\u0430\u0447\u0430, \u0430 \u043F\u0430\u0447\u043A\u0430 \u0437\u0430\u0434\u0430\u0447: \u0437\u0430 \u043E\u0434\u0438\u043D \u043F\u0440\u043E\u0433\u043E\u043D \u0435\u0451 \u043D\u0435 \u0434\u043E\u0432\u0435\u0441\u0442\u0438, \u0430 \u0443\u043F\u0435\u0440\u0435\u0442\u044C\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442 \u043D\u0430 \u0441\u0435\u0440\u0435\u0434\u0438\u043D\u0435 \u0445\u0443\u0436\u0435, \u0447\u0435\u043C \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0441\u0440\u0430\u0437\u0443 \u2014 \u043D\u0435\u0434\u043E\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u0434\u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443. \u041C\u0435\u0440\u0438\u043B\u043E \u043D\u0435 \xAB\u043C\u043D\u043E\u0433\u043E\u0432\u0430\u0442\u043E \u0440\u0430\u0431\u043E\u0442\u044B\xBB, \u0430 \xAB\u0432 \u043E\u0434\u043D\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043A\u0438 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u044C\xBB. \u0420\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418\u0422\u042C \u0430\u0432\u0442\u043E\u0440\u0443 \u0441\u043F\u0438\u0441\u043A\u043E\u043C (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 split \u0443 return_task) \u2014 \u043D\u043E \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438 \u041D\u0415 \u0437\u0430\u0432\u043E\u0434\u0438: \u0440\u0430\u0437\u0431\u0438\u0435\u043D\u0438\u0435 \u0440\u0435\u0448\u0430\u0435\u0442 \u0430\u0432\u0442\u043E\u0440.",
16836
17231
  "\u041F\u0440\u0430\u0432\u0438\u043B\u0430: \u0442\u0440\u043E\u0433\u0430\u0435\u0448\u044C UI \u2014 \u0421\u041D\u0410\u0427\u0410\u041B\u0410 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0438, \u0435\u0441\u0442\u044C \u043B\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0441\u043A\u0438\u043B\u043B \u0441 UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u0433\u043E \u0434\u0438\u0437\u0430\u0439\u043D-\u0441\u0438\u0441\u0442\u0435\u043C\u0435 (\u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u0430 \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443, \u043D\u0435 \u0445\u0430\u0440\u0434\u043A\u043E\u0434\u044C \u043E\u0434\u043D\u043E\u0440\u0430\u0437\u043E\u0432\u043E\u0435); \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u0441\u043E slug \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438; \u043F\u043E\u0441\u043B\u0435 UI-\u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043F\u0440\u0438\u043B\u043E\u0436\u0438 \u0441\u043A\u0440\u0438\u043D\u044B mobile+desktop \u041A \u0417\u0410\u0414\u0410\u0427\u0415 (\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 screenshots \u0443 done); \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0447\u0435\u0440\u0435\u0437 gh \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).",
16837
17232
  // TASWM-377: у сессии есть ПОТОЛОК ТРАТ, и он режет её без предупреждения, прямо посреди
16838
17233
  // работы. Дрон об этом знать обязан: цена сессии растёт от ЧИСЛА ХОДОВ (каждый ход заново
@@ -16890,10 +17285,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
16890
17285
  }
16891
17286
  return parts.join("\n");
16892
17287
  }
16893
- var HERO_CHARTER_PATH = join30(dirname4(THIS_FILE), "..", "docs", "HERO.md");
17288
+ var HERO_CHARTER_PATH = join31(dirname5(THIS_FILE), "..", "docs", "HERO.md");
16894
17289
  function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
16895
17290
  const name = run.name || `#${run.id}`;
16896
- const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
17291
+ const charter = readFileSync21(HERO_CHARTER_PATH, "utf8");
16897
17292
  const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
16898
17293
 
16899
17294
  ${task.description}` : `\u042D\u041F\u0418\u041A ${run.task_slug} (\u0442\u0435\u043B\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E slug \u0438 \u0437\u0434\u0440\u0430\u0432\u043E\u043C\u0443 \u0441\u043C\u044B\u0441\u043B\u0443).`;
@@ -16936,7 +17331,7 @@ function buildResolverPrompt(reviewRun, resolver, conflicts, branch, mainRef) {
16936
17331
  `1. \u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u043C\u0430\u0440\u043A\u0435\u0440\u044B \u0432\u043E \u0432\u0441\u0435\u0445 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u0445 \u0444\u0430\u0439\u043B\u0430\u0445 (${files}); \`git status\` \u043F\u043E\u043A\u0430\u0436\u0435\u0442, \u0435\u0441\u043B\u0438 \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C.`,
16937
17332
  "2. `git add -A && git commit --no-edit` \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 merge-\u043A\u043E\u043C\u043C\u0438\u0442 (\u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u043E\u0441\u0442\u0430\u0432\u044C).",
16938
17333
  "3. \u041F\u0440\u043E\u0433\u043E\u043D\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043A\u0430\u043A \u0432 CI (\u0437\u0430\u0433\u043B\u044F\u043D\u0438 \u0432 .github/workflows \u0438 package.json \u0440\u0435\u043F\u043E \u2014 \u0442\u0435\u0441\u0442\u044B/\u0442\u0438\u043F\u044B/\u0441\u0431\u043E\u0440\u043A\u0430/\u043B\u0438\u043D\u0442) \u0438 \u0443\u0431\u0435\u0434\u0438\u0441\u044C, \u0447\u0442\u043E \u0437\u0435\u043B\u0435\u043D\u043E. \u041A\u0440\u0430\u0441\u043D\u043E\u0435 \u2014 \u043F\u043E\u0447\u0438\u043D\u0438 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435, \u043D\u0435 \u0441\u0430\u043C\u0443 \u0444\u0438\u0447\u0443.",
16939
- `4. \`git push origin HEAD:${branch}\` \u2014 \u041E\u0411\u042B\u0427\u041D\u042B\u0419 push (\u044D\u0442\u043E fast-forward). \`--force\`/\`--force-with-lease\` \u041D\u0415 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439: \u0440\u0438\u0431\u0435\u0439\u0437/\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0441\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0442\u0443\u0442 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u044B \u0438 \u0433\u0435\u0439\u0442\u044F\u0442\u0441\u044F.`,
17334
+ `4. \`git push origin HEAD:refs/heads/${branch}\` \u2014 \u041E\u0411\u042B\u0427\u041D\u042B\u0419 push (\u044D\u0442\u043E fast-forward), \u043F\u043E\u043B\u043D\u044B\u0439 refspec (\u043D\u0435 \u0433\u043E\u043B\u043E\u0435 \`HEAD:${branch}\` \u2014 git \u043E\u0442\u043A\u0430\u0436\u0435\u0442\u0441\u044F \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0442\u044C, \u0435\u0441\u043B\u0438 \u0432\u0435\u0442\u043A\u0443 \u0442\u0435\u043C \u0432\u0440\u0435\u043C\u0435\u043D\u0435\u043C \u0443\u0434\u0430\u043B\u0438\u043B\u0438). \`--force\`/\`--force-with-lease\` \u041D\u0415 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439: \u0440\u0438\u0431\u0435\u0439\u0437/\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0441\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0442\u0443\u0442 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u044B \u0438 \u0433\u0435\u0439\u0442\u044F\u0442\u0441\u044F.`,
16940
17335
  `5. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}) \u2014 \u0440\u043E\u0439 \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442, \u0447\u0442\u043E \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D \u0438 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D.`,
16941
17336
  "",
16942
17337
  `\u0412 \u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0442\u0440\u0435\u0439\u043B\u0435\u0440-\u0441\u0442\u0440\u043E\u043A\u0443 \xABDrone: ${name} \xB7 resolver run #${resolver.id}\xBB. \u041A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043F\u0443\u0448\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0440\u0451\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0434\u0440\u043E\u043D\u043E\u043C. \u0414\u043E\u043B\u0433\u043E\u0435 (\u0442\u0435\u0441\u0442\u044B) \u0434\u0435\u0440\u0436\u0438 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u043C \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u043D\u0435 \u0432 \u0444\u043E\u043D\u0435.`
@@ -17197,7 +17592,7 @@ async function wakeRun(deps, runId) {
17197
17592
  if (!run) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u0440\u043E\u044F` };
17198
17593
  if (run.pid != null && deps.isAlive(run.pid)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 (pid ${run.pid}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
17199
17594
  if (isTerminalStatus(run.status)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435\u043D (${run.status}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
17200
- const worktreeExists = deps.worktreeExists ?? existsSync17;
17595
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
17201
17596
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
17202
17597
  const wt = run.worktree_path;
17203
17598
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
@@ -17303,7 +17698,7 @@ function repoHasCi(deps, run) {
17303
17698
  function repoHasCiWorkflows(worktreePath, repo) {
17304
17699
  for (const base3 of [worktreePath, repo]) {
17305
17700
  if (!base3) continue;
17306
- const dir = join30(base3, ".github", "workflows");
17701
+ const dir = join31(base3, ".github", "workflows");
17307
17702
  try {
17308
17703
  if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
17309
17704
  } catch {
@@ -17489,7 +17884,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
17489
17884
  return true;
17490
17885
  }
17491
17886
  function worktreeAndTranscriptAlive(deps, worktree) {
17492
- const exists = deps.worktreeExists ?? existsSync17;
17887
+ const exists = deps.worktreeExists ?? existsSync18;
17493
17888
  const transcript = deps.hasTranscript ?? executorHasSession;
17494
17889
  return exists(worktree) && transcript(worktree);
17495
17890
  }
@@ -17523,7 +17918,9 @@ async function spawnFreshReviewerAfterStall(deps, run, why) {
17523
17918
  async function wakeAuthorOnChanges(deps, run, now, droneCap) {
17524
17919
  if (!deps.respawn) return false;
17525
17920
  if (run.pid != null && deps.isAlive(run.pid)) return false;
17526
- if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled")) return false;
17921
+ if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled" && !isAwaitingCi(r.status, r.stop_reason))) {
17922
+ return false;
17923
+ }
17527
17924
  if (deps.resourcesOk && !deps.resourcesOk()) return false;
17528
17925
  if (countActiveDrones(deps.db) >= droneCap) return false;
17529
17926
  if (!await projectQuotaGateAllows(deps, run)) return false;
@@ -17638,20 +18035,22 @@ async function envGateAllows(deps, run) {
17638
18035
  return true;
17639
18036
  }
17640
18037
  function effectiveRepo(deps, run) {
17641
- return normalizeRepo(run.repo || deps.repoFallback || "");
18038
+ return effectiveRepoOf(run.repo, deps.repoFallback);
17642
18039
  }
17643
18040
  async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback) {
17644
18041
  const model = await models.forRepo(repo);
17645
18042
  if (!hasZoneModel(model)) return { kind: "unknown" };
17646
- const live = db.listRuns().filter((r) => (r.kind ?? "drone") === "drone" && ACTIVE_DRONE_STATUSES.has(r.status));
17647
- activeFiles.prune(live.map((r) => r.id));
18043
+ const holders = db.listRuns().filter((r) => holdsFiles(r));
18044
+ activeFiles.prune(holders.map((r) => r.id));
17648
18045
  const active = [];
17649
- for (const r of live) {
18046
+ for (const r of holders) {
17650
18047
  if (r.id === run.id) continue;
17651
18048
  if (normalizeRepo(r.repo || repoFallback || "") !== repo) continue;
17652
- const files = await activeFiles.filesOf(r);
18049
+ const snapshot = r.changed_paths ?? [];
18050
+ const files = snapshot.length ? snapshot : await activeFiles.filesOf(r);
18051
+ const prUrl = r.status === "review" ? r.pr_url : null;
17653
18052
  active.push(
17654
- files.length ? { run: r, files, actual: true } : { run: r, files: predictForRun(model, r).map((p) => p.path), actual: false }
18053
+ files.length ? { run: r, files, actual: true, prUrl } : { run: r, files: predictForRun(model, r).map((p) => p.path), actual: false, prUrl }
17655
18054
  );
17656
18055
  }
17657
18056
  return decideConflict({ model, candidate: run, active });
@@ -17797,7 +18196,8 @@ async function trySpawnQueuedRun(deps, run) {
17797
18196
  }
17798
18197
  const spentAttempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
17799
18198
  if (spentAttempts >= PRE_START_ATTEMPT_LIMIT) {
17800
- const why = `${PRE_START_MARK} ${spentAttempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-422)`;
18199
+ const last = lastPreStartAttempt(deps.db.listRuns(), run.task_slug);
18200
+ const why = last && isUnretriableParked(last) ? `${SPAWN_PARK_MARK}: \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043E\u0442\u043A\u0430\u0437\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u043E\u0432\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-485). \u0427\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u2014 \u0432 \u043F\u0440\u043E\u0433\u043E\u043D\u0435 #${last.id}` : `${PRE_START_MARK} ${spentAttempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-422)`;
17801
18201
  deps.db.updateRun(run.id, { status: "stopped", stop_reason: why });
17802
18202
  deps.db.addEvent(run.id, "step", why);
17803
18203
  const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), `pre-start-exhausted:${run.task_slug}`, why, GATE_LOG_HEARTBEAT_TICKS);
@@ -17816,7 +18216,8 @@ async function trySpawnQueuedRun(deps, run) {
17816
18216
  const unmet = depVerdicts.filter((d) => !d.met);
17817
18217
  if (unmet.length) {
17818
18218
  const lost = unmet.filter((d) => d.outcome === "unknown");
17819
- const why = lost.length ? `\u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0435\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u041D\u0415\u0422: ${lost.map((d) => d.slug).join(", ")} \u2014 \u043F\u043E \u044D\u0442\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u0435 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430; \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442, \u0441\u043D\u0438\u043C\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C (swarm_run_upsert{dependsOn:[]}) \u0438\u043B\u0438 \u0437\u0430\u0432\u0435\u0434\u0438 \u0431\u043B\u043E\u043A\u0435\u0440 \u0437\u0430\u043D\u043E\u0432\u043E (TASWM-399)` : `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438: ${unmet.map((d) => d.slug).join(", ")}`;
18219
+ const blind = deps.db.depsUnconfirmed?.(run.depends_on) ?? [];
18220
+ const why = lost.length ? `\u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0435\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u041D\u0415\u0422: ${lost.map((d) => d.slug).join(", ")} \u2014 \u043F\u043E \u044D\u0442\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u0435 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430; \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442, \u0441\u043D\u0438\u043C\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C (swarm_run_upsert{dependsOn:[]}) \u0438\u043B\u0438 \u0437\u0430\u0432\u0435\u0434\u0438 \u0431\u043B\u043E\u043A\u0435\u0440 \u0437\u0430\u043D\u043E\u0432\u043E (TASWM-399)` : blind.length ? `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 ${blind.map((d) => d.slug).join(", ")}, \u043D\u043E \u041F\u0420\u041E\u0414 \u0415\u0401 \u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u0415\u0422: ${blind[0].attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) swarm_deps_status, \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u2014 ${blind[0].error}. \u041F\u043E\u043A\u0430 \u043F\u0440\u043E\u0434 \u043C\u043E\u043B\u0447\u0438\u0442, \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438 \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442 (TASWM-486)` : `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438: ${unmet.map((d) => d.slug).join(", ")}`;
17820
18221
  return deferSpawn(deps, run, why);
17821
18222
  }
17822
18223
  if (!await envGateAllows(deps, run)) return "skipped";
@@ -17841,6 +18242,13 @@ async function trySpawnQueuedRun(deps, run) {
17841
18242
  if (logged.get(run.id) !== e.message) {
17842
18243
  logged.set(run.id, e.message);
17843
18244
  deps.db.addEvent(run.id, "step", `${e.message} \u2014 \u0437\u0430\u0434\u0430\u0447\u0430 \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 (TASWM-348)`);
18245
+ const blocker = deps.db.getRun(e.blockedByRunId);
18246
+ if (blocker)
18247
+ deps.db.addEvent(
18248
+ blocker.id,
18249
+ "step",
18250
+ `${run.task_slug} (#${run.id}) \u0441\u0442\u043E\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438\u0437-\u0437\u0430 \u044D\u0442\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u2014 ${e.message} (TASWM-506)`
18251
+ );
17844
18252
  narrate(deps, `${droneLabel(run)} \u2014 ${e.message}. \u041D\u0435 \u0441\u043F\u0430\u0432\u043D\u044E \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u043E.`);
17845
18253
  }
17846
18254
  return "skipped";
@@ -17853,10 +18261,10 @@ async function trySpawnQueuedRun(deps, run) {
17853
18261
  narrate(deps, `\u0411\u0435\u0440\u0443 ${run.task_slug} \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u0441\u043F\u0430\u0432\u043D\u044E ${droneLabel(run)} (\u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0437\u0430 \u043C\u0451\u0440\u0442\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439).`);
17854
18262
  return "spawned";
17855
18263
  } catch (again) {
17856
- return await failBeforeStart(deps, run, String(again));
18264
+ return await failBeforeStart(deps, run, String(again), spawnRetryOpts(again));
17857
18265
  }
17858
18266
  }
17859
- return await failBeforeStart(deps, run, String(e));
18267
+ return await failBeforeStart(deps, run, String(e), spawnRetryOpts(e));
17860
18268
  }
17861
18269
  }
17862
18270
  function holderStillHolds(deps, holder, now) {
@@ -17887,15 +18295,19 @@ async function healBusyWorktree(deps, run, e) {
17887
18295
  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}`);
17888
18296
  return true;
17889
18297
  }
17890
- async function failBeforeStart(deps, run, reason) {
18298
+ function spawnRetryOpts(e) {
18299
+ if (!(e instanceof TaskBodyUnavailableError)) return {};
18300
+ return { retriable: TOOL_FAILURE_RETRIABLE[e.code] };
18301
+ }
18302
+ async function failBeforeStart(deps, run, reason, opts = {}) {
17891
18303
  deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
17892
18304
  deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
17893
18305
  const attempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
17894
- if (attempts < PRE_START_ATTEMPT_LIMIT) {
18306
+ if (opts.retriable !== false && attempts < PRE_START_ATTEMPT_LIMIT) {
17895
18307
  narrate(deps, `${droneLabel(run)} \u2014 ${PRE_START_MARK} (\u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempts} \u0438\u0437 ${PRE_START_ATTEMPT_LIMIT}): ${reason}`);
17896
18308
  return "failed";
17897
18309
  }
17898
- const parked = parkReason(attempts, reason);
18310
+ const parked = opts.retriable === false ? parkReasonUnretriable(reason) : parkReason(attempts, reason);
17899
18311
  deps.db.updateRun(run.id, { stop_reason: parked });
17900
18312
  deps.db.addEvent(run.id, "step", parked);
17901
18313
  narrate(deps, `${run.task_slug}: ${parked}`);
@@ -17931,7 +18343,7 @@ async function resumeWatchdogParked(deps, droneCap) {
17931
18343
  }
17932
18344
  if (!candidate) return;
17933
18345
  if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
17934
- const worktreeExists = deps.worktreeExists ?? existsSync17;
18346
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
17935
18347
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
17936
18348
  const wt = candidate.worktree_path;
17937
18349
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
@@ -18100,7 +18512,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
18100
18512
  if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
18101
18513
  if (!deps.respawn) return;
18102
18514
  const wt = resumeCandidate.worktree_path;
18103
- const worktreeExists = deps.worktreeExists ?? existsSync17;
18515
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
18104
18516
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
18105
18517
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
18106
18518
  const mode = canContinue ? "continue" : "fresh";
@@ -18378,7 +18790,7 @@ function buildEscalationCandidates(db) {
18378
18790
  }
18379
18791
  async function tickOnce(deps) {
18380
18792
  const now = deps.now ? deps.now() : Date.now();
18381
- const worktreeExists = deps.worktreeExists ?? existsSync17;
18793
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
18382
18794
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
18383
18795
  deps.sampleHost?.();
18384
18796
  if (deps.rehydrate) {
@@ -18632,7 +19044,7 @@ async function tickOnce(deps) {
18632
19044
  } else if (!st.conflicting && deps.ci && deps.respawn && !stormActive) {
18633
19045
  await handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap);
18634
19046
  }
18635
- if ((run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict))) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
19047
+ if (needsFixIteration(run) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
18636
19048
  continue;
18637
19049
  }
18638
19050
  const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
@@ -19140,7 +19552,7 @@ async function resolveConflict(deps, reviewRun) {
19140
19552
  model: reviewRun.model,
19141
19553
  review_model: reviewRun.review_model
19142
19554
  });
19143
- const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19555
+ const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19144
19556
  try {
19145
19557
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19146
19558
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -19175,6 +19587,28 @@ async function resolveConflict(deps, reviewRun) {
19175
19587
  throw e;
19176
19588
  }
19177
19589
  }
19590
+ function makeCheckerAttachmentsReader(tl) {
19591
+ return async (slug, worktree, workspaceId, onEvent) => {
19592
+ const client = tl.forWorkspace(workspaceId);
19593
+ const read = await client.getTaskDetailed(slug);
19594
+ if (!read.ok) {
19595
+ const why = `\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 ${slug} \u043D\u0435 \u0447\u0438\u0442\u0430\u044E\u0442\u0441\u044F: ${describeToolFailure(read.failure)}`;
19596
+ onEvent?.(`${why} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438\u0434\u0451\u0442 \u0411\u0415\u0417 \u043C\u0430\u043A\u0435\u0442\u043E\u0432 \u0438 \u0444\u0430\u0439\u043B\u043E\u0432 \u0437\u0430\u0434\u0430\u0447\u0438`);
19597
+ return [
19598
+ `\u26A0\uFE0F \u0412\u041B\u041E\u0416\u0415\u041D\u0418\u042F \u0417\u0410\u0414\u0410\u0427\u0418 \u041D\u0415\u0414\u041E\u0421\u0422\u0423\u041F\u041D\u042B: ${why}.`,
19599
+ "\u041C\u0430\u043A\u0435\u0442\u044B \u0438 \u0444\u0430\u0439\u043B\u044B \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u044B \u041D\u0415 \u0432\u0438\u0434\u0435\u043B \u2014 \u0438\u0445 \u043C\u043E\u0433\u043B\u043E \u043D\u0435 \u0431\u044B\u0442\u044C \u0432\u043E\u0432\u0441\u0435, \u0430 \u043C\u043E\u0433\u043B\u043E \u0431\u044B\u0442\u044C \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0443\u0433\u043E\u0434\u043D\u043E.",
19600
+ "\u041F\u0443\u043D\u043A\u0442\u044B \u0432\u0438\u0434\u0430 \xAB\u0441\u0434\u0435\u043B\u0430\u043D\u043E \u043B\u0438 \u043A\u0430\u043A \u043D\u0430 \u043C\u0430\u043A\u0435\u0442\u0435\xBB \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0442\u0430\u043A \u0438 \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435, \u0430 \u043D\u0435 \u0441\u0443\u0434\u0438 \u043F\u043E \u043A\u043E\u0434\u0443."
19601
+ ].join("\n");
19602
+ }
19603
+ return prepareTaskAttachments({
19604
+ worktree,
19605
+ refs: read.task.attachments,
19606
+ fetchFile: client.fetchFile.bind(client),
19607
+ // Выложенное и пропущенное — тоже в ленту проверяющего, как у исполнителя (respawnRun).
19608
+ ...onEvent ? { onEvent } : {}
19609
+ });
19610
+ };
19611
+ }
19178
19612
  async function spawnReviewer(deps, reviewRun, files) {
19179
19613
  const repo = normalizeRepo(reviewRun.repo || deps.repoFallback);
19180
19614
  const branch = await worktreeBranchFor(reviewRun, deps.prHeadBranch);
@@ -19197,11 +19631,16 @@ async function spawnReviewer(deps, reviewRun, files) {
19197
19631
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
19198
19632
  workspace_id: reviewRun.workspace_id
19199
19633
  });
19200
- const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19634
+ const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19201
19635
  try {
19202
19636
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19203
19637
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
19204
- const attachments = await deps.taskAttachments?.(reviewRun.task_slug, wt, reviewRun.workspace_id);
19638
+ const attachments = await deps.taskAttachments?.(
19639
+ reviewRun.task_slug,
19640
+ wt,
19641
+ reviewRun.workspace_id,
19642
+ (text) => deps.db.addEvent(reviewer.id, "step", text)
19643
+ );
19205
19644
  const pendingRules = await deps.pendingRepoRules?.(repo, reviewRun.task_slug).catch(() => []) ?? [];
19206
19645
  const prompt = buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments, verifyMode, pendingRules);
19207
19646
  const pid = deps.launch({ worktree: wt, run: reviewer, prompt });
@@ -19273,11 +19712,16 @@ async function spawnVerifier(deps, verifiedRun, files) {
19273
19712
  // штамп цикла: фикс-итерацию проверяет свежий верификатор
19274
19713
  workspace_id: verifiedRun.workspace_id
19275
19714
  });
19276
- const wt = join30(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19715
+ const wt = join31(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19277
19716
  try {
19278
19717
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19279
19718
  await deps.git.setWorktreeIdentity(wt, verifier.name);
19280
- const attachments = await deps.taskAttachments?.(verifiedRun.task_slug, wt, verifiedRun.workspace_id);
19719
+ const attachments = await deps.taskAttachments?.(
19720
+ verifiedRun.task_slug,
19721
+ wt,
19722
+ verifiedRun.workspace_id,
19723
+ (text) => deps.db.addEvent(verifier.id, "step", text)
19724
+ );
19281
19725
  const prompt = buildVerifierPrompt(verifiedRun, verifier, branch, files, attachments, verifyMode);
19282
19726
  const pid = deps.launch({ worktree: wt, run: verifier, prompt });
19283
19727
  deps.db.updateRun(verifier.id, { status: "running", worktree_path: wt, branch, pid, pr_url: verifiedRun.pr_url });
@@ -19314,7 +19758,7 @@ async function respawnRun(deps, run, mode, nudge) {
19314
19758
  ]);
19315
19759
  const base3 = effectiveBaseBranch(run.base_branch, projectBase);
19316
19760
  const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
19317
- if (!existsSync17(wt)) {
19761
+ if (!existsSync18(wt)) {
19318
19762
  const branch = runToSpawn.branch || runToSpawn.task_slug;
19319
19763
  const source = await addWorktree(repo, wt, branch, {
19320
19764
  ...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
@@ -19341,7 +19785,7 @@ async function respawnRun(deps, run, mode, nudge) {
19341
19785
  deps.db.updateRun(run.id, { status: "running", pid });
19342
19786
  }
19343
19787
  async function syncCrashedOnStartup(deps) {
19344
- const worktreeExists = deps.worktreeExists ?? existsSync17;
19788
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
19345
19789
  let orphaned = 0;
19346
19790
  let merged = 0;
19347
19791
  let requeued = 0;
@@ -19404,7 +19848,7 @@ async function makeStore(repo, worktreesDir, repoFlag) {
19404
19848
  console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u044C \u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E (${e instanceof Error ? e.message : e}) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u0441\u043D\u043E\u0432\u0430 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0442 --repo`);
19405
19849
  }
19406
19850
  }
19407
- const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir5(), repoPaths);
19851
+ const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir6(), repoPaths);
19408
19852
  const repoMap = emptyRepoMapState(scanRootsList);
19409
19853
  if (SWARM_BACKEND !== "client") {
19410
19854
  const { SwarmDb: SwarmDb2 } = await loadSqlite();
@@ -19471,7 +19915,11 @@ async function makeStore(repo, worktreesDir, repoFlag) {
19471
19915
  // TASWM-422: что машина обслуживает ПРЯМО СЕЙЧАС — читается из ЖИВОГО реестра (он
19472
19916
  // пересобирается тиком выше), чтобы причина «клона нет на этой машине» называла
19473
19917
  // актуальный список, а не снимок на старте демона.
19474
- reposServed: () => repoRegistry.map((e) => e.repoFullName)
19918
+ reposServed: () => repoRegistry.map((e) => e.repoFullName),
19919
+ // TASWM-492: обратная сторона того же реестра — путь прогона в owner/repo. Читаем из
19920
+ // живого `repoRegistry` (не из снимка на старте): репозиторий могли клонировать уже
19921
+ // после запуска демона, и гейт мержа не должен считать его прогон «неопознанным».
19922
+ repoFullNameOf: (repoPath) => repoRegistry.find((e) => e.path === repoPath)?.repoFullName ?? null
19475
19923
  };
19476
19924
  }
19477
19925
  var crashGuardsInstalled = false;
@@ -19491,7 +19939,7 @@ async function migrateWorktreesAtStartup() {
19491
19939
  legacyDir: WORKTREES_LEGACY_DIR,
19492
19940
  targetDir: WORKTREES_DIR,
19493
19941
  envOverride: process.env.SWARM_WORKTREES || "",
19494
- exists: (d) => existsSync17(d),
19942
+ exists: (d) => existsSync18(d),
19495
19943
  isNonEmptyDir: (d) => {
19496
19944
  try {
19497
19945
  return readdirSync13(d).length > 0;
@@ -19506,7 +19954,7 @@ async function migrateWorktreesAtStartup() {
19506
19954
  });
19507
19955
  } catch (e) {
19508
19956
  console.error("[migrate] \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F worktrees \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u2014 \u0444\u043E\u043B\u0431\u044D\u043A:", e);
19509
- return existsSync17(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
19957
+ return existsSync18(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
19510
19958
  }
19511
19959
  }
19512
19960
  async function reportWorkspaceAccess(tl, db) {
@@ -19547,7 +19995,8 @@ async function runDaemon(opts) {
19547
19995
  repoMap: repoMapState,
19548
19996
  refreshRepoMap: rebuildRepoMap,
19549
19997
  prodMcp,
19550
- reposServed
19998
+ reposServed,
19999
+ repoFullNameOf
19551
20000
  } = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
19552
20001
  await preflightAuth();
19553
20002
  const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
@@ -19869,6 +20318,7 @@ async function runDaemon(opts) {
19869
20318
  ttlMs: 6e4
19870
20319
  });
19871
20320
  }
20321
+ const checkerAttachments = tl?.fetchFile ? makeCheckerAttachmentsReader(tl) : void 0;
19872
20322
  let spawnReviewerFn;
19873
20323
  let verdictCommentFn;
19874
20324
  let reviewCutoffAtValue;
@@ -19892,20 +20342,10 @@ async function runDaemon(opts) {
19892
20342
  ...pendingRepoRules ? { pendingRepoRules } : {},
19893
20343
  // TASWM-283: те же вложения задачи, что у исполнителя — ревьюер без макета
19894
20344
  // не может проверить «сделано ли как просили».
19895
- ...tl?.fetchFile ? {
19896
- // TASWM-288: вложения читаем клиентом ВОРКСПЕЙСА проверяемого прогона —
19897
- // иначе ревьюер задачи из чужого воркспейса остался бы без макетов
19898
- // («сделано ли как просили» проверять нечем), как и исполнитель до фикса.
19899
- taskAttachments: async (slug, worktree, workspaceId) => {
19900
- const client = tl.forWorkspace(workspaceId);
19901
- const task = await client.getTask(slug);
19902
- return prepareTaskAttachments({
19903
- worktree,
19904
- refs: task?.attachments,
19905
- fetchFile: client.fetchFile.bind(client)
19906
- });
19907
- }
19908
- } : {}
20345
+ // TASWM-288: вложения читаем клиентом ВОРКСПЕЙСА проверяемого прогона — иначе
20346
+ // ревьюер задачи из чужого воркспейса остался бы без макетов («сделано ли как
20347
+ // просили» проверять нечем), как и исполнитель до фикса.
20348
+ ...checkerAttachments ? { taskAttachments: checkerAttachments } : {}
19909
20349
  },
19910
20350
  reviewRun,
19911
20351
  files
@@ -19927,17 +20367,7 @@ async function runDaemon(opts) {
19927
20367
  git: { prepareDetachedWorktree, setWorktreeIdentity, removeWorktree },
19928
20368
  prHeadBranch,
19929
20369
  ...verifyModeReader ? { verifyMode: verifyModeReader } : {},
19930
- ...tl?.fetchFile ? {
19931
- taskAttachments: async (slug, worktree, workspaceId) => {
19932
- const client = tl.forWorkspace(workspaceId);
19933
- const task = await client.getTask(slug);
19934
- return prepareTaskAttachments({
19935
- worktree,
19936
- refs: task?.attachments,
19937
- fetchFile: client.fetchFile.bind(client)
19938
- });
19939
- }
19940
- } : {}
20370
+ ...checkerAttachments ? { taskAttachments: checkerAttachments } : {}
19941
20371
  },
19942
20372
  verifiedRun,
19943
20373
  files
@@ -19976,6 +20406,13 @@ async function runDaemon(opts) {
19976
20406
  const mergeParts = makeMergeGateParts({
19977
20407
  listRuns: () => db.listRuns(),
19978
20408
  ...autoReviewProject ? { autoReview: autoReviewProject } : {},
20409
+ // TASWM-492: чей это PR. Номер PR у семи репозиториев роя независим, поэтому гейт
20410
+ // сверяет и репозиторий: owner/repo из pr_url, а короткий `#N` — по репозиторию
20411
+ // КОНТЕКСТА ВЫЗОВА (origin каталога, откуда спросили). repoFallback + репо-реестр
20412
+ // нужны для второй стороны сравнения: у прогона в базе лежит локальный путь, и пустой
20413
+ // он означает дефолт роя, а не «отдельный проект».
20414
+ repoFallback: opts.repo,
20415
+ ...repoFullNameOf ? { repoFullNameOf } : {},
19979
20416
  fallbackAutoReview: SWARM_AUTO_REVIEW,
19980
20417
  autoVerify: SWARM_AUTO_VERIFY,
19981
20418
  // TASWM-344: вторая ось приёмки — вердикт верификатора
@@ -20162,7 +20599,7 @@ async function runDaemon(opts) {
20162
20599
  isDirty: (p) => worktreeDirty(p),
20163
20600
  releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
20164
20601
  pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
20165
- worktreeExists: existsSync17,
20602
+ worktreeExists: existsSync18,
20166
20603
  liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
20167
20604
  narrate: narrator.narrate
20168
20605
  });
@@ -20285,7 +20722,7 @@ async function runDaemon(opts) {
20285
20722
  mode,
20286
20723
  nudge
20287
20724
  ),
20288
- worktreeExists: (p) => existsSync17(p),
20725
+ worktreeExists: (p) => existsSync18(p),
20289
20726
  hasTranscript: executorHasSession,
20290
20727
  // CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
20291
20728
  // (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
@@ -20471,7 +20908,7 @@ async function runDaemon(opts) {
20471
20908
  onStep
20472
20909
  });
20473
20910
  }
20474
- const repo = await repoRoot(dirname4(THIS_FILE)).catch(() => null);
20911
+ const repo = await repoRoot(dirname5(THIS_FILE)).catch(() => null);
20475
20912
  if (!repo) {
20476
20913
  return { ok: false, step: "fetch", message: "\u0431\u0435\u0433\u0443 \u0438\u0437 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u043E\u0432, \u043D\u043E git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0430\u0434 \u043D\u0438\u043C\u0438 \u043D\u0435\u0442 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" };
20477
20914
  }
@@ -20781,6 +21218,7 @@ export {
20781
21218
  lastChangesSummary,
20782
21219
  listDroneProcesses,
20783
21220
  livePpids,
21221
+ makeCheckerAttachmentsReader,
20784
21222
  replayDecision,
20785
21223
  repoHasCiWorkflows,
20786
21224
  resolveConflict,
@@ -20795,5 +21233,6 @@ export {
20795
21233
  tickOnce,
20796
21234
  verdictCommentMark,
20797
21235
  wakeRun,
20798
- worktreeFromCmd
21236
+ worktreeFromCmd,
21237
+ zoneConflictFor
20799
21238
  };