@taskless-app/swarm 0.6.23 → 0.6.25

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
@@ -525,6 +525,56 @@ var init_mergeQueue = __esm({
525
525
  }
526
526
  });
527
527
 
528
+ // ../swarm-core/src/prRef.ts
529
+ function prNumberOf(ref) {
530
+ const s = (ref || "").trim();
531
+ if (!s) return null;
532
+ const url = s.match(/\/pull\/(\d+)/);
533
+ if (url) return Number(url[1]);
534
+ const bare = s.match(/^#?(\d+)$/);
535
+ return bare ? Number(bare[1]) : null;
536
+ }
537
+ function repoKeyOf(raw) {
538
+ const s = (raw ?? "").trim().replace(/\/+$/, "").replace(/\.git$/i, "");
539
+ if (!s) return null;
540
+ const url = s.match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\/(?:pull|tree|compare|commit|issues)\/.*)?$/i);
541
+ if (url) return `${url[1]}/${url[2]}`.toLowerCase();
542
+ const pair = s.match(/^([\w.-]+)\/([\w.-]+)$/);
543
+ return pair ? `${pair[1]}/${pair[2]}`.toLowerCase() : null;
544
+ }
545
+ function newest(rows) {
546
+ let best = null;
547
+ for (const r of rows) if (!best || r.order > best.order) best = r;
548
+ return best;
549
+ }
550
+ function pick(rows) {
551
+ return newest(rows.filter((r) => r.author)) ?? newest(rows);
552
+ }
553
+ function pickRunForPr(rows, ref, ctxRepo) {
554
+ const num = prNumberOf(ref);
555
+ if (num == null) return null;
556
+ const target = repoKeyOf(ref) ?? repoKeyOf(ctxRepo);
557
+ const hits = rows.filter((r) => r.prUrl && prNumberOf(r.prUrl) === num);
558
+ if (hits.length === 0) return null;
559
+ if (target) {
560
+ const mine = hits.filter((r) => keyOf(r) === target);
561
+ const blind = hits.filter((r) => keyOf(r) == null);
562
+ return pick(mine.length ? mine : blind);
563
+ }
564
+ const keys = new Set(hits.map((r) => keyOf(r) ?? ""));
565
+ if (keys.size === 1) return pick(hits);
566
+ const risky = hits.filter((r) => r.reviewVerdict !== "approve");
567
+ return pick(risky.length ? risky : hits);
568
+ }
569
+ function keyOf(r) {
570
+ return repoKeyOf(r.prUrl) ?? repoKeyOf(r.repoKey);
571
+ }
572
+ var init_prRef = __esm({
573
+ "../swarm-core/src/prRef.ts"() {
574
+ "use strict";
575
+ }
576
+ });
577
+
528
578
  // ../swarm-core/src/modelPolicy.ts
529
579
  function findProvider(id) {
530
580
  const key = typeof id === "string" ? id.trim().toLowerCase() : "";
@@ -1632,6 +1682,7 @@ var init_src = __esm({
1632
1682
  init_reviewPhase();
1633
1683
  init_mergeGate();
1634
1684
  init_mergeQueue();
1685
+ init_prRef();
1635
1686
  init_verifyVerdict();
1636
1687
  init_modelPolicy();
1637
1688
  init_status();
@@ -1784,6 +1835,43 @@ var init_swarmFiles = __esm({
1784
1835
  }
1785
1836
  });
1786
1837
 
1838
+ // src/mergeGateHook.ts
1839
+ import { chmodSync, copyFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1840
+ import { homedir } from "os";
1841
+ import { dirname, join as join2 } from "path";
1842
+ import { fileURLToPath } from "url";
1843
+ function packagedHookPath() {
1844
+ return join2(dirname(fileURLToPath(import.meta.url)), "..", "hooks", HOOK_FILE);
1845
+ }
1846
+ function wiredCommands(settings) {
1847
+ const blocks = settings?.hooks?.PreToolUse;
1848
+ if (!Array.isArray(blocks)) return [];
1849
+ return blocks.flatMap((b) => Array.isArray(b?.hooks) ? b.hooks : []).flatMap((h) => isOurHook(h?.command) ? [h.command] : []);
1850
+ }
1851
+ function readSettings(path) {
1852
+ if (!existsSync3(path)) return void 0;
1853
+ try {
1854
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
1855
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
1856
+ } catch {
1857
+ return null;
1858
+ }
1859
+ }
1860
+ function droneMergeGateHookPath(worktree, packaged = packagedHookPath()) {
1861
+ if (!existsSync3(packaged)) return null;
1862
+ const projectSettings = readSettings(join2(worktree, ".claude", "settings.json"));
1863
+ if (projectSettings && wiredCommands(projectSettings).length > 0) return null;
1864
+ return packaged;
1865
+ }
1866
+ var HOOK_FILE, isOurHook;
1867
+ var init_mergeGateHook = __esm({
1868
+ "src/mergeGateHook.ts"() {
1869
+ "use strict";
1870
+ HOOK_FILE = "pre-merge-gate.sh";
1871
+ isOurHook = (command) => typeof command === "string" && command.trim().endsWith(HOOK_FILE);
1872
+ }
1873
+ });
1874
+
1787
1875
  // src/pricing.ts
1788
1876
  function costFromUsage(usage, model, table = MODEL_PRICING) {
1789
1877
  const entry = findModelPricing(model, table);
@@ -1872,9 +1960,9 @@ var init_claudeResult = __esm({
1872
1960
  });
1873
1961
 
1874
1962
  // 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";
1963
+ import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
1964
+ import { homedir as homedir2 } from "os";
1965
+ import { join as join3 } from "path";
1878
1966
  function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
1879
1967
  const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
1880
1968
  const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
@@ -1900,20 +1988,23 @@ function droneDenyRules(paths) {
1900
1988
  const abs = (p) => `//${p.replace(/^\/+/, "")}`;
1901
1989
  return paths.flatMap((p) => [`Read(${abs(p)})`, `Edit(${abs(p)})`]);
1902
1990
  }
1903
- function droneSettingsJson(paths) {
1904
- return JSON.stringify({ permissions: { deny: droneDenyRules(paths) } });
1991
+ function droneSettingsJson(paths, mergeGateHook = null) {
1992
+ const settings = {};
1993
+ if (paths.length > 0) settings.permissions = { deny: droneDenyRules(paths) };
1994
+ if (mergeGateHook) settings.hooks = { PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: mergeGateHook }] }] };
1995
+ return JSON.stringify(settings);
1905
1996
  }
1906
1997
  function transcriptDir(worktree, projectsDir) {
1907
- return join2(projectsDir, worktree.replace(/[/.]/g, "-"));
1998
+ return join3(projectsDir, worktree.replace(/[/.]/g, "-"));
1908
1999
  }
1909
- function sessionTranscriptExists(worktree, projectsDir = join2(homedir(), ".claude", "projects")) {
2000
+ function sessionTranscriptExists(worktree, projectsDir = join3(homedir2(), ".claude", "projects")) {
1910
2001
  try {
1911
2002
  return readdirSync2(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
1912
2003
  } catch {
1913
2004
  return false;
1914
2005
  }
1915
2006
  }
1916
- function transcriptMtimeMs(worktree, projectsDir = join2(homedir(), ".claude", "projects")) {
2007
+ function transcriptMtimeMs(worktree, projectsDir = join3(homedir2(), ".claude", "projects")) {
1917
2008
  const dir = transcriptDir(worktree, projectsDir);
1918
2009
  let files;
1919
2010
  try {
@@ -1925,7 +2016,7 @@ function transcriptMtimeMs(worktree, projectsDir = join2(homedir(), ".claude", "
1925
2016
  for (const f of files) {
1926
2017
  if (!f.endsWith(".jsonl")) continue;
1927
2018
  try {
1928
- const mtime2 = statSync(join2(dir, f)).mtimeMs;
2019
+ const mtime2 = statSync(join3(dir, f)).mtimeMs;
1929
2020
  if (latest === null || mtime2 > latest) latest = mtime2;
1930
2021
  } catch {
1931
2022
  }
@@ -1938,8 +2029,8 @@ function writeMcpConfig(spec) {
1938
2029
  [spec.mcp.name]: { command: spec.mcp.command, args: spec.mcp.args, env: spec.mcp.env }
1939
2030
  }
1940
2031
  };
1941
- const path = join2(spec.worktree, MCP_CONFIG_FILE);
1942
- writeFileSync2(path, JSON.stringify(cfg, null, 2));
2032
+ const path = join3(spec.worktree, MCP_CONFIG_FILE);
2033
+ writeFileSync3(path, JSON.stringify(cfg, null, 2));
1943
2034
  return path;
1944
2035
  }
1945
2036
  function abortField(abort) {
@@ -1951,6 +2042,7 @@ var init_claudeCode = __esm({
1951
2042
  "use strict";
1952
2043
  init_claudeBin();
1953
2044
  init_swarmFiles();
2045
+ init_mergeGateHook();
1954
2046
  init_claudeResult();
1955
2047
  ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
1956
2048
  MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
@@ -1964,7 +2056,8 @@ var init_claudeCode = __esm({
1964
2056
  // проброс колбэком), и `this` тогда уедет в undefined.
1965
2057
  plan: (spec) => {
1966
2058
  const cfgPath = writeMcpConfig(spec);
1967
- const settings = spec.denyPaths.length > 0 ? droneSettingsJson(spec.denyPaths) : void 0;
2059
+ const hook = droneMergeGateHookPath(spec.worktree);
2060
+ const settings = spec.denyPaths.length > 0 || hook ? droneSettingsJson(spec.denyPaths, hook) : void 0;
1968
2061
  return {
1969
2062
  bin: resolveClaudeBin(),
1970
2063
  args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
@@ -2006,35 +2099,35 @@ var init_claudeCode = __esm({
2006
2099
  });
2007
2100
 
2008
2101
  // src/swarmHome.ts
2009
- import { homedir as homedir2 } from "os";
2010
- import { join as join3 } from "path";
2011
- import { mkdirSync } from "fs";
2102
+ import { homedir as homedir3 } from "os";
2103
+ import { join as join4 } from "path";
2104
+ import { mkdirSync as mkdirSync2 } from "fs";
2012
2105
  var SWARM_HOME;
2013
2106
  var init_swarmHome = __esm({
2014
2107
  "src/swarmHome.ts"() {
2015
2108
  "use strict";
2016
- SWARM_HOME = process.env.SWARM_HOME || join3(homedir2(), ".taskless-swarm");
2017
- mkdirSync(SWARM_HOME, { recursive: true });
2109
+ SWARM_HOME = process.env.SWARM_HOME || join4(homedir3(), ".taskless-swarm");
2110
+ mkdirSync2(SWARM_HOME, { recursive: true });
2018
2111
  }
2019
2112
  });
2020
2113
 
2021
2114
  // 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";
2115
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, statSync as statSync2, symlinkSync } from "fs";
2116
+ import { join as join5 } from "path";
2024
2117
  function resolveCodexBin(env = process.env) {
2025
2118
  return (env.SWARM_CODEX_BIN ?? "").trim() || "codex";
2026
2119
  }
2027
2120
  function codexHomePath(worktree, swarmHome = SWARM_HOME) {
2028
- return join4(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
2121
+ return join5(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
2029
2122
  }
2030
2123
  function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWARM_HOME) {
2031
2124
  const home = codexHomePath(worktree, swarmHome);
2032
- mkdirSync2(home, { recursive: true });
2033
- const real = (env.CODEX_HOME ?? "").trim() || join4(realHome, ".codex");
2125
+ mkdirSync3(home, { recursive: true });
2126
+ const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
2034
2127
  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;
2128
+ const target = join5(real, file);
2129
+ const link = join5(home, file);
2130
+ if (!existsSync4(target) || existsSync4(link)) continue;
2038
2131
  try {
2039
2132
  symlinkSync(target, link);
2040
2133
  } catch {
@@ -2098,7 +2191,7 @@ function newestRolloutMtime(dir, depth = 4) {
2098
2191
  }
2099
2192
  let latest = null;
2100
2193
  for (const name of entries) {
2101
- const full = join4(dir, name);
2194
+ const full = join5(dir, name);
2102
2195
  let st;
2103
2196
  try {
2104
2197
  st = statSync2(full);
@@ -2172,8 +2265,8 @@ ${notice}` : spec.prompt;
2172
2265
  return est.warning ? { costUsd: est.costUsd, warning: est.warning } : { costUsd: est.costUsd };
2173
2266
  },
2174
2267
  session: {
2175
- exists: (worktree) => newestRolloutMtime(join4(codexHomePath(worktree), "sessions")) !== null,
2176
- mtimeMs: (worktree) => newestRolloutMtime(join4(codexHomePath(worktree), "sessions"))
2268
+ exists: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions")) !== null,
2269
+ mtimeMs: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions"))
2177
2270
  },
2178
2271
  probe: {
2179
2272
  // Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
@@ -2298,6 +2391,7 @@ __export(config_exports, {
2298
2391
  SWARM_WORKTREE_REAP_INTERVAL_MS: () => SWARM_WORKTREE_REAP_INTERVAL_MS,
2299
2392
  TASKLESS_API: () => TASKLESS_API,
2300
2393
  TASKLESS_MCP_PATH: () => TASKLESS_MCP_PATH,
2394
+ TASKLESS_REST_PATH: () => TASKLESS_REST_PATH,
2301
2395
  TASKLESS_TASK_URL_BASE: () => TASKLESS_TASK_URL_BASE,
2302
2396
  TASKLESS_TOKEN: () => TASKLESS_TOKEN,
2303
2397
  TASKLESS_WORKSPACE: () => TASKLESS_WORKSPACE,
@@ -2309,14 +2403,16 @@ __export(config_exports, {
2309
2403
  WORKTREES_NOINDEX_DIR: () => WORKTREES_NOINDEX_DIR,
2310
2404
  droneWorktreePath: () => droneWorktreePath,
2311
2405
  droneWorktreeRoot: () => droneWorktreeRoot,
2406
+ effectiveRepoOf: () => effectiveRepoOf,
2312
2407
  mcpEndpoint: () => mcpEndpoint,
2313
2408
  migrateWorktreesToNoindex: () => migrateWorktreesToNoindex,
2314
2409
  normalizeRepo: () => normalizeRepo,
2315
2410
  resolveEffectiveWorktreesDir: () => resolveEffectiveWorktreesDir,
2411
+ restEndpoint: () => restEndpoint,
2316
2412
  reviewCutoffAt: () => reviewCutoffAt
2317
2413
  });
2318
- import { basename, join as join5, resolve } from "path";
2319
- import { readFileSync as readFileSync2, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
2414
+ import { basename, join as join6, resolve } from "path";
2415
+ import { readFileSync as readFileSync3, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
2320
2416
  function normalizeRepo(repo) {
2321
2417
  const r = (repo ?? "").trim();
2322
2418
  if (!r) return "";
@@ -2326,11 +2422,14 @@ function normalizeRepo(repo) {
2326
2422
  return resolve(r);
2327
2423
  }
2328
2424
  }
2425
+ function effectiveRepoOf(repo, fallback) {
2426
+ return normalizeRepo(repo || fallback || "");
2427
+ }
2329
2428
  function droneWorktreePath(worktreesDir, repo, slug) {
2330
- return join5(droneWorktreeRoot(worktreesDir, repo), slug);
2429
+ return join6(droneWorktreeRoot(worktreesDir, repo), slug);
2331
2430
  }
2332
2431
  function droneWorktreeRoot(worktreesDir, repo) {
2333
- return join5(worktreesDir, basename(normalizeRepo(repo)));
2432
+ return join6(worktreesDir, basename(normalizeRepo(repo)));
2334
2433
  }
2335
2434
  function resolveEffectiveWorktreesDir(deps) {
2336
2435
  if (deps.envOverride) return deps.envOverride;
@@ -2365,6 +2464,12 @@ function envNum(name, def) {
2365
2464
  const n = Number(raw);
2366
2465
  return Number.isFinite(n) && n >= 0 ? n : def;
2367
2466
  }
2467
+ function restEndpoint(api, route, prefix = TASKLESS_REST_PATH) {
2468
+ const base3 = api.replace(/\/$/, "");
2469
+ const p = prefix === "/" ? "" : prefix.startsWith("/") ? prefix.replace(/\/$/, "") : `/${prefix.replace(/\/$/, "")}`;
2470
+ const r = route.startsWith("/") ? route : `/${route}`;
2471
+ return `${base3}${p}${r}`;
2472
+ }
2368
2473
  function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
2369
2474
  const base3 = api.replace(/\/$/, "");
2370
2475
  const p = path.startsWith("/") ? path : `/${path}`;
@@ -2372,27 +2477,27 @@ function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
2372
2477
  }
2373
2478
  function tokenFromFile() {
2374
2479
  try {
2375
- return readFileSync2(join5(SWARM_HOME, "token"), "utf8").trim();
2480
+ return readFileSync3(join6(SWARM_HOME, "token"), "utf8").trim();
2376
2481
  } catch {
2377
2482
  return "";
2378
2483
  }
2379
2484
  }
2380
2485
  function reviewCutoffAt() {
2381
2486
  try {
2382
- const raw = JSON.parse(readFileSync2(REVIEW_CUTOFF_PATH, "utf8"));
2487
+ const raw = JSON.parse(readFileSync3(REVIEW_CUTOFF_PATH, "utf8"));
2383
2488
  if (raw.at) return raw.at;
2384
2489
  } catch {
2385
2490
  }
2386
2491
  const at = stamp();
2387
2492
  try {
2388
2493
  const tmp = `${REVIEW_CUTOFF_PATH}.tmp`;
2389
- writeFileSync3(tmp, JSON.stringify({ at }));
2494
+ writeFileSync4(tmp, JSON.stringify({ at }));
2390
2495
  renameSync2(tmp, REVIEW_CUTOFF_PATH);
2391
2496
  } catch {
2392
2497
  }
2393
2498
  return at;
2394
2499
  }
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;
2500
+ 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
2501
  var init_config = __esm({
2397
2502
  "src/config.ts"() {
2398
2503
  "use strict";
@@ -2400,22 +2505,22 @@ var init_config = __esm({
2400
2505
  init_executors();
2401
2506
  init_swarmHome();
2402
2507
  init_swarmHome();
2403
- DB_PATH = process.env.SWARM_DB || join5(SWARM_HOME, "swarm.db");
2508
+ DB_PATH = process.env.SWARM_DB || join6(SWARM_HOME, "swarm.db");
2404
2509
  EXECUTOR = resolveExecutor(process.env.SWARM_EXECUTOR);
2405
- DRONE_HOME = process.env.SWARM_DRONE_HOME || join5(SWARM_HOME, "drone-home");
2510
+ DRONE_HOME = process.env.SWARM_DRONE_HOME || join6(SWARM_HOME, "drone-home");
2406
2511
  DRONE_HOME_ISOLATION = process.env.SWARM_DRONE_ISOLATE_HOME !== "0" && process.platform !== "win32";
2407
2512
  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");
2513
+ WORKTREES_NOINDEX_DIR = join6(SWARM_HOME, "worktrees.noindex");
2514
+ WORKTREES_LEGACY_DIR = join6(SWARM_HOME, "worktrees");
2410
2515
  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");
2516
+ RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join6(SWARM_HOME, "run-logs");
2517
+ DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join6(SWARM_HOME, "overmind.log");
2413
2518
  DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
2414
2519
  DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
2415
2520
  DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
2416
2521
  DIST_BUILD = true;
2417
2522
  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");
2523
+ SWARM_OUTBOX = process.env.SWARM_OUTBOX || join6(SWARM_HOME, "outbox.jsonl");
2419
2524
  SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
2420
2525
  SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
2421
2526
  SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
@@ -2445,20 +2550,21 @@ var init_config = __esm({
2445
2550
  DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
2446
2551
  DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
2447
2552
  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");
2553
+ DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET || join6(SWARM_HOME, "drone-channel.key");
2449
2554
  DRONE_CHANNEL_GRACE_MS = envNum("SWARM_DRONE_CHANNEL_GRACE_MS", 12e4);
2450
- HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join5(SWARM_HOME, "overmind.heartbeat");
2555
+ HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join6(SWARM_HOME, "overmind.heartbeat");
2451
2556
  HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
2452
2557
  SWARM_REPO = process.env.SWARM_REPO || "";
2453
2558
  SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
2454
2559
  SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
2455
2560
  SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
2456
- REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join5(SWARM_HOME, "repo-map.json");
2561
+ REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join6(SWARM_HOME, "repo-map.json");
2457
2562
  SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
2458
2563
  SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
2459
2564
  TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
2460
2565
  SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
2461
2566
  TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
2567
+ TASKLESS_REST_PATH = process.env.TASKLESS_REST_PATH || "/api";
2462
2568
  TASKLESS_TOKEN = process.env.TASKLESS_TOKEN || tokenFromFile();
2463
2569
  TASKLESS_WORKSPACE = process.env.TASKLESS_WORKSPACE || "";
2464
2570
  TASKLESS_WORKSPACE_SLUG = process.env.TASKLESS_WORKSPACE_SLUG || "";
@@ -2467,7 +2573,7 @@ var init_config = __esm({
2467
2573
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
2468
2574
  SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
2469
2575
  SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
2470
- REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join5(SWARM_HOME, "review-cutoff.json");
2576
+ REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join6(SWARM_HOME, "review-cutoff.json");
2471
2577
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
2472
2578
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
2473
2579
  WEB_PORT = Number(process.env.SWARM_WEB_PORT) || 7420;
@@ -2507,7 +2613,7 @@ var init_quietExec = __esm({
2507
2613
 
2508
2614
  // src/resources.ts
2509
2615
  import os from "os";
2510
- import { readFileSync as readFileSync3, statfsSync } from "fs";
2616
+ import { readFileSync as readFileSync4, statfsSync } from "fs";
2511
2617
  function isTransientResourceError(e) {
2512
2618
  const code = e?.code;
2513
2619
  return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
@@ -2565,7 +2671,7 @@ function fileDescriptorLoadRatio() {
2565
2671
  }
2566
2672
  if (process.platform === "linux") {
2567
2673
  try {
2568
- const [allocated, , max] = readFileSync3("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
2674
+ const [allocated, , max] = readFileSync4("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
2569
2675
  if (Number.isFinite(allocated ?? NaN) && Number.isFinite(max ?? NaN) && max) return allocated / max;
2570
2676
  } catch {
2571
2677
  }
@@ -2882,15 +2988,15 @@ var init_storm = __esm({
2882
2988
  });
2883
2989
 
2884
2990
  // src/heartbeat.ts
2885
- import { readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
2991
+ import { readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
2886
2992
  function writeHeartbeat(path, now) {
2887
2993
  const tmp = `${path}.tmp`;
2888
- writeFileSync5(tmp, String(now));
2994
+ writeFileSync6(tmp, String(now));
2889
2995
  renameSync4(tmp, path);
2890
2996
  }
2891
2997
  function readHeartbeat(path) {
2892
2998
  try {
2893
- const ms = Number(readFileSync5(path, "utf8").trim());
2999
+ const ms = Number(readFileSync6(path, "utf8").trim());
2894
3000
  return Number.isFinite(ms) ? ms : null;
2895
3001
  } catch {
2896
3002
  return null;
@@ -2946,11 +3052,11 @@ var init_logNoise = __esm({
2946
3052
  });
2947
3053
 
2948
3054
  // src/agentToken.ts
2949
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
2950
- import { join as join8 } from "path";
3055
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
3056
+ import { join as join9 } from "path";
2951
3057
  function loadAgentToken() {
2952
3058
  try {
2953
- const s = JSON.parse(readFileSync6(STORE_PATH, "utf8"));
3059
+ const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
2954
3060
  return s.token ? s : null;
2955
3061
  } catch {
2956
3062
  return null;
@@ -3003,7 +3109,7 @@ var init_agentToken = __esm({
3003
3109
  "src/agentToken.ts"() {
3004
3110
  "use strict";
3005
3111
  init_config();
3006
- STORE_PATH = join8(SWARM_HOME, "agent.json");
3112
+ STORE_PATH = join9(SWARM_HOME, "agent.json");
3007
3113
  base = (api) => api.replace(/\/$/, "");
3008
3114
  TOKEN_PROBE_TIMEOUT_MS = 8e3;
3009
3115
  }
@@ -3441,9 +3547,9 @@ var init_netTimeout = __esm({
3441
3547
  });
3442
3548
 
3443
3549
  // src/git.ts
3444
- import { existsSync as existsSync10, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
3550
+ import { existsSync as existsSync11, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
3445
3551
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
3446
- import { isAbsolute as isAbsolute2, join as join17, sep as sep2 } from "path";
3552
+ import { isAbsolute as isAbsolute2, join as join18, sep as sep2 } from "path";
3447
3553
  import { $ as $2 } from "execa";
3448
3554
  function gitNet(repo, args, timeoutMs) {
3449
3555
  return withHardTimeout(
@@ -3466,7 +3572,7 @@ async function reclaimStaleWorktree(repo, worktree) {
3466
3572
  }
3467
3573
  function isLinkedWorktree(dir) {
3468
3574
  try {
3469
- return statSync8(join17(dir, ".git")).isFile();
3575
+ return statSync8(join18(dir, ".git")).isFile();
3470
3576
  } catch {
3471
3577
  return false;
3472
3578
  }
@@ -3478,7 +3584,7 @@ async function detachWorktree(repo, worktree) {
3478
3584
  }
3479
3585
  }
3480
3586
  async function ensureWorktreeDirGone(worktree) {
3481
- if (!existsSync10(worktree)) return false;
3587
+ if (!existsSync11(worktree)) return false;
3482
3588
  if (!isLinkedWorktree(worktree)) {
3483
3589
  console.error(
3484
3590
  `[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 +3599,7 @@ async function ensureWorktreeDirGone(worktree) {
3493
3599
  );
3494
3600
  return true;
3495
3601
  }
3496
- if (existsSync10(worktree)) {
3602
+ if (existsSync11(worktree)) {
3497
3603
  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
3604
  }
3499
3605
  return true;
@@ -3541,7 +3647,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
3541
3647
  }
3542
3648
  const busyAt = await worktreeForBranch(repo, branch);
3543
3649
  if (busyAt) {
3544
- const exists = existsSync10(busyAt);
3650
+ const exists = existsSync11(busyAt);
3545
3651
  const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
3546
3652
  if (live) throw new WorktreeBusyError(branch, busyAt);
3547
3653
  if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
@@ -3576,8 +3682,8 @@ async function ensureGitExcluded(worktree, pattern) {
3576
3682
  try {
3577
3683
  const commonDir = await gitPath(worktree, "--git-common-dir");
3578
3684
  if (!commonDir) return false;
3579
- const infoDir = join17(commonDir, "info");
3580
- const excludeFile = join17(infoDir, "exclude");
3685
+ const infoDir = join18(commonDir, "info");
3686
+ const excludeFile = join18(infoDir, "exclude");
3581
3687
  let current = "";
3582
3688
  try {
3583
3689
  current = await readFile(excludeFile, "utf8");
@@ -3597,7 +3703,7 @@ async function gitPath(worktree, flag) {
3597
3703
  const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
3598
3704
  const raw = out.stdout.trim();
3599
3705
  if (out.exitCode !== 0 || !raw) return null;
3600
- return isAbsolute2(raw) ? raw : join17(worktree, raw);
3706
+ return isAbsolute2(raw) ? raw : join18(worktree, raw);
3601
3707
  }
3602
3708
  async function ensureWorktreeHygiene(worktree) {
3603
3709
  for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
@@ -3607,12 +3713,12 @@ async function installGuardHooks(worktree) {
3607
3713
  try {
3608
3714
  const gitDir = await gitPath(worktree, "--git-dir");
3609
3715
  if (!gitDir) return false;
3610
- const hooksDir = join17(gitDir, "swarm-hooks");
3716
+ const hooksDir = join18(gitDir, "swarm-hooks");
3611
3717
  const prev = await previousHooksPath(worktree);
3612
3718
  await mkdir(hooksDir, { recursive: true });
3613
3719
  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);
3720
+ await writeFile(join18(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3721
+ await chmod(join18(hooksDir, hook), 493);
3616
3722
  }
3617
3723
  await $2`git -C ${worktree} config extensions.worktreeConfig true`;
3618
3724
  await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
@@ -3631,7 +3737,7 @@ async function previousHooksPath(worktree) {
3631
3737
  if (out.exitCode === 0 && value) return value;
3632
3738
  }
3633
3739
  const commonDir = await gitPath(worktree, "--git-common-dir");
3634
- return commonDir ? join17(commonDir, "hooks") : null;
3740
+ return commonDir ? join18(commonDir, "hooks") : null;
3635
3741
  }
3636
3742
  async function removeWorktree(repo, path) {
3637
3743
  const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
@@ -3656,7 +3762,7 @@ async function worktreeDirty(path) {
3656
3762
  async function repairMovedWorktrees(targetDir) {
3657
3763
  const subdirs = (dir) => {
3658
3764
  try {
3659
- return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join17(dir, d.name));
3765
+ return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join18(dir, d.name));
3660
3766
  } catch {
3661
3767
  return [];
3662
3768
  }
@@ -3717,7 +3823,7 @@ async function mergeRef(worktree, ref) {
3717
3823
  return { clean: false, conflicts: u ? u.split("\n") : [], changed: false };
3718
3824
  }
3719
3825
  async function pushHeadTo(worktree, branch) {
3720
- await gitNetStrict(worktree, ["push", "origin", `HEAD:${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
3826
+ await gitNetStrict(worktree, ["push", "origin", `HEAD:refs/heads/${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
3721
3827
  }
3722
3828
  async function snapshotWorktree(worktree, message) {
3723
3829
  try {
@@ -3785,20 +3891,20 @@ __export(droneChannel_exports, {
3785
3891
  verifyLocalCliToken: () => verifyLocalCliToken
3786
3892
  });
3787
3893
  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";
3894
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
3895
+ import { dirname as dirname4 } from "path";
3790
3896
  import { serve } from "@hono/node-server";
3791
3897
  function loadChannelSecret(path) {
3792
3898
  try {
3793
- const s = readFileSync14(path, "utf8").trim();
3899
+ const s = readFileSync15(path, "utf8").trim();
3794
3900
  if (s.length >= 32) return s;
3795
3901
  } catch {
3796
3902
  }
3797
3903
  const secret = randomBytes(32).toString("hex");
3798
- mkdirSync8(dirname3(path), { recursive: true });
3799
- writeFileSync10(path, secret, { mode: 384 });
3904
+ mkdirSync9(dirname4(path), { recursive: true });
3905
+ writeFileSync11(path, secret, { mode: 384 });
3800
3906
  try {
3801
- chmodSync(path, 384);
3907
+ chmodSync2(path, 384);
3802
3908
  } catch {
3803
3909
  }
3804
3910
  return secret;
@@ -4140,7 +4246,7 @@ var init_droneChannel = __esm({
4140
4246
 
4141
4247
  // src/attachments.ts
4142
4248
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
4143
- import { join as join20 } from "path";
4249
+ import { join as join21 } from "path";
4144
4250
  function attachmentsFromDoc(doc, origin) {
4145
4251
  const out = [];
4146
4252
  const walk2 = (node) => {
@@ -4231,7 +4337,7 @@ async function materializeAttachments(opts) {
4231
4337
  };
4232
4338
  const result = { files: [], skipped: [] };
4233
4339
  if (opts.refs.length === 0) return result;
4234
- const dir = join20(opts.worktree, ATTACHMENTS_DIR);
4340
+ const dir = join21(opts.worktree, ATTACHMENTS_DIR);
4235
4341
  await io.removeDir(dir);
4236
4342
  let dirReady = false;
4237
4343
  let total = 0;
@@ -4277,7 +4383,7 @@ async function materializeAttachments(opts) {
4277
4383
  await io.mkdir(dir);
4278
4384
  dirReady = true;
4279
4385
  }
4280
- await io.writeFile(join20(dir, name), file.bytes);
4386
+ await io.writeFile(join21(dir, name), file.bytes);
4281
4387
  } catch (e) {
4282
4388
  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
4389
  continue;
@@ -4430,7 +4536,7 @@ var init_attachments = __esm({
4430
4536
 
4431
4537
  // src/shotUpload.ts
4432
4538
  import { readFile as fsReadFile } from "fs/promises";
4433
- import { basename as basename4, isAbsolute as isAbsolute3, join as join21 } from "path";
4539
+ import { basename as basename4, isAbsolute as isAbsolute3, join as join22 } from "path";
4434
4540
  function shotMimetype(path) {
4435
4541
  const dot = path.lastIndexOf(".");
4436
4542
  if (dot < 0) return null;
@@ -4456,7 +4562,7 @@ async function readShots(paths, opts) {
4456
4562
  }
4457
4563
  let bytes;
4458
4564
  try {
4459
- bytes = await read(isAbsolute3(path) ? path : join21(opts.worktree, path));
4565
+ bytes = await read(isAbsolute3(path) ? path : join22(opts.worktree, path));
4460
4566
  } catch (e) {
4461
4567
  skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
4462
4568
  continue;
@@ -4466,7 +4572,10 @@ async function readShots(paths, opts) {
4466
4572
  continue;
4467
4573
  }
4468
4574
  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` });
4575
+ skipped.push({
4576
+ path,
4577
+ 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)`
4578
+ });
4470
4579
  continue;
4471
4580
  }
4472
4581
  files.push({ path, fileName: basename4(path), mimetype, bytes });
@@ -4521,7 +4630,7 @@ var init_shotUpload = __esm({
4521
4630
  "src/shotUpload.ts"() {
4522
4631
  "use strict";
4523
4632
  SHOT_MAX_FILES = 6;
4524
- SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
4633
+ SHOT_MAX_FILE_BYTES = 20 * 1024 * 1024;
4525
4634
  MIME_BY_EXT = {
4526
4635
  ".png": "image/png",
4527
4636
  ".jpg": "image/jpeg",
@@ -4534,8 +4643,8 @@ var init_shotUpload = __esm({
4534
4643
 
4535
4644
  // src/oauth.ts
4536
4645
  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";
4646
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
4647
+ import { join as join23 } from "path";
4539
4648
  function makePkce() {
4540
4649
  const verifier = randomBytes2(48).toString("base64url");
4541
4650
  const challenge = createHash2("sha256").update(verifier).digest("base64url");
@@ -4619,13 +4728,13 @@ function toStore(clientId, t, now) {
4619
4728
  }
4620
4729
  function loadStore() {
4621
4730
  try {
4622
- return JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
4731
+ return JSON.parse(readFileSync16(STORE_PATH2, "utf8"));
4623
4732
  } catch {
4624
4733
  return null;
4625
4734
  }
4626
4735
  }
4627
4736
  function saveStore(s) {
4628
- writeFileSync11(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
4737
+ writeFileSync12(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
4629
4738
  }
4630
4739
  function clearStore() {
4631
4740
  try {
@@ -4635,7 +4744,7 @@ function clearStore() {
4635
4744
  }
4636
4745
  function loadClientId() {
4637
4746
  try {
4638
- return JSON.parse(readFileSync15(CLIENT_PATH, "utf8")).clientId;
4747
+ return JSON.parse(readFileSync16(CLIENT_PATH, "utf8")).clientId;
4639
4748
  } catch {
4640
4749
  return null;
4641
4750
  }
@@ -4644,7 +4753,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
4644
4753
  const existing = loadClientId();
4645
4754
  if (existing) return existing;
4646
4755
  const clientId = await registerClient({ api, redirectUri }, fetchFn);
4647
- writeFileSync11(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4756
+ writeFileSync12(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4648
4757
  return clientId;
4649
4758
  }
4650
4759
  async function ensureAccess(store, opts = {}) {
@@ -4670,8 +4779,8 @@ var init_oauth = __esm({
4670
4779
  AUTHORIZE = "/api/oauth/authorize";
4671
4780
  TOKEN = "/api/oauth/token";
4672
4781
  SCOPE = "openid profile email taskless:full";
4673
- STORE_PATH2 = join22(SWARM_HOME, "oauth.json");
4674
- CLIENT_PATH = join22(SWARM_HOME, "oauth-client.json");
4782
+ STORE_PATH2 = join23(SWARM_HOME, "oauth.json");
4783
+ CLIENT_PATH = join23(SWARM_HOME, "oauth-client.json");
4675
4784
  base2 = (api) => api.replace(/\/$/, "");
4676
4785
  }
4677
4786
  });
@@ -4789,11 +4898,11 @@ var init_authState = __esm({
4789
4898
  });
4790
4899
 
4791
4900
  // src/workspace.ts
4792
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
4793
- import { join as join23 } from "path";
4901
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
4902
+ import { join as join24 } from "path";
4794
4903
  function loadWorkspace() {
4795
4904
  try {
4796
- const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
4905
+ const w = JSON.parse(readFileSync17(WORKSPACE_PATH, "utf8"));
4797
4906
  return w && typeof w.id === "string" && w.id ? w : null;
4798
4907
  } catch {
4799
4908
  return null;
@@ -4807,7 +4916,7 @@ var init_workspace = __esm({
4807
4916
  "src/workspace.ts"() {
4808
4917
  "use strict";
4809
4918
  init_config();
4810
- WORKSPACE_PATH = join23(SWARM_HOME, "workspace.json");
4919
+ WORKSPACE_PATH = join24(SWARM_HOME, "workspace.json");
4811
4920
  }
4812
4921
  });
4813
4922
 
@@ -4844,7 +4953,7 @@ function isClosingTaskCategory(category) {
4844
4953
  function taskAuthor(u) {
4845
4954
  return u?.id ? { id: u.id, name: u.displayName || "\u0430\u0432\u0442\u043E\u0440 \u0437\u0430\u0434\u0430\u0447\u0438" } : null;
4846
4955
  }
4847
- function toolErrorText(r) {
4956
+ function toolErrorText2(r) {
4848
4957
  const fromContent = (r.content ?? []).map((c) => c.text ?? "").filter(Boolean).join(" ").trim();
4849
4958
  return fromContent || JSON.stringify(r);
4850
4959
  }
@@ -4856,7 +4965,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
4856
4965
  try {
4857
4966
  const r = await call(name, WS_FREE_TOOLS.has(name) ? args : { ...args, ...ws });
4858
4967
  if (r?.isError) {
4859
- const raw = toolErrorText(r);
4968
+ const raw = toolErrorText2(r);
4860
4969
  const detail = trimDetail(raw);
4861
4970
  console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, detail);
4862
4971
  return { ok: false, failure: { code: classifyToolFailure(raw), detail } };
@@ -5388,7 +5497,7 @@ __export(db_exports, {
5388
5497
  });
5389
5498
  import { createRequire as createRequire2 } from "module";
5390
5499
  import { randomUUID as randomUUID3 } from "crypto";
5391
- import { existsSync as existsSync12 } from "fs";
5500
+ import { existsSync as existsSync13 } from "fs";
5392
5501
  import { resolve as resolve3 } from "path";
5393
5502
  function rowToQuestion(row) {
5394
5503
  return {
@@ -5996,7 +6105,7 @@ CREATE TABLE IF NOT EXISTS imports (
5996
6105
  const repoN = normalizeRepo(repo);
5997
6106
  const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
5998
6107
  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}`);
6108
+ if (!existsSync13(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
6000
6109
  const src = new this.databaseCtor(abs);
6001
6110
  try {
6002
6111
  const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
@@ -6364,10 +6473,10 @@ var init_droneStore = __esm({
6364
6473
  });
6365
6474
 
6366
6475
  // 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";
6476
+ import { homedir as homedir6 } from "os";
6477
+ import { existsSync as existsSync18, readFileSync as readFileSync21, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6478
+ import { basename as basename6, dirname as dirname5, join as join31 } from "path";
6479
+ import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
6371
6480
  import { spawn as spawn3 } from "child_process";
6372
6481
 
6373
6482
  // src/restart.ts
@@ -6446,8 +6555,8 @@ init_config();
6446
6555
  init_resources();
6447
6556
  init_names2();
6448
6557
  init_storm();
6449
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
6450
- import { dirname } from "path";
6558
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
6559
+ import { dirname as dirname2 } from "path";
6451
6560
  import { randomUUID } from "crypto";
6452
6561
 
6453
6562
  // src/repoRules.ts
@@ -6499,12 +6608,12 @@ function renderPendingRulesBlock(rules) {
6499
6608
  }
6500
6609
 
6501
6610
  // 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";
6611
+ import { closeSync, existsSync as existsSync6, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
6612
+ import { join as join8 } from "path";
6504
6613
 
6505
6614
  // 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";
6615
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync3, statSync as statSync3 } from "fs";
6616
+ import { join as join7 } from "path";
6508
6617
  var LEGACY_RUN_LOGS_SUBDIR = "legacy";
6509
6618
  function runLogDirName(runLogKey) {
6510
6619
  const safe2 = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
@@ -6512,12 +6621,12 @@ function runLogDirName(runLogKey) {
6512
6621
  return /^\d+$/.test(safe2) ? `k-${safe2}` : safe2;
6513
6622
  }
6514
6623
  function runLogDirPath(runLogsDir, runLogKey) {
6515
- return join6(runLogsDir, runLogDirName(runLogKey));
6624
+ return join7(runLogsDir, runLogDirName(runLogKey));
6516
6625
  }
6517
6626
  function findRunLogDir(runLogsDir, keys) {
6518
6627
  for (const key of keys) {
6519
6628
  const dir = runLogDirPath(runLogsDir, key);
6520
- if (existsSync4(dir)) return dir;
6629
+ if (existsSync5(dir)) return dir;
6521
6630
  }
6522
6631
  return null;
6523
6632
  }
@@ -6527,7 +6636,7 @@ function runLogDirForWrite(runLogsDir, keys) {
6527
6636
  return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
6528
6637
  }
6529
6638
  function sweepLegacyRunLogs(runLogsDir) {
6530
- const legacyDir = join6(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
6639
+ const legacyDir = join7(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
6531
6640
  let entries;
6532
6641
  try {
6533
6642
  entries = readdirSync4(runLogsDir);
@@ -6538,12 +6647,12 @@ function sweepLegacyRunLogs(runLogsDir) {
6538
6647
  let failed = 0;
6539
6648
  for (const name of entries) {
6540
6649
  if (!/^\d+$/.test(name)) continue;
6541
- const from = join6(runLogsDir, name);
6650
+ const from = join7(runLogsDir, name);
6542
6651
  try {
6543
6652
  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}`);
6653
+ mkdirSync5(legacyDir, { recursive: true });
6654
+ let to = join7(legacyDir, name);
6655
+ for (let n = 2; existsSync5(to) && n < 100; n++) to = join7(legacyDir, `${name}-${n}`);
6547
6656
  renameSync3(from, to);
6548
6657
  moved++;
6549
6658
  } catch {
@@ -6609,8 +6718,8 @@ function latestRunLaunch(runLogsDir, runLogKeys) {
6609
6718
  }
6610
6719
  if (indexes.size === 0) return null;
6611
6720
  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;
6721
+ const out = [join8(dir, `${index}.json`), join8(dir, `${index}.json.done`)].find((p) => existsSync6(p)) ?? null;
6722
+ const err = existsSync6(join8(dir, `${index}.err.log`)) ? join8(dir, `${index}.err.log`) : null;
6614
6723
  return { index, total: indexes.size, out, err };
6615
6724
  }
6616
6725
  function runLogTail(runLogsDir, runLogKeys, opts) {
@@ -6692,6 +6801,11 @@ async function serveLogRequests(deps, commands) {
6692
6801
  init_src();
6693
6802
  var RESOLVER_TERMINAL = /* @__PURE__ */ new Set([...TERMINAL_STATUSES, "stalled"]);
6694
6803
  var SLUG_TERMINAL = new Set(TERMINAL_STATUSES);
6804
+ function toolErrorText(res) {
6805
+ const text = (res?.content ?? []).map((c) => c.text ?? "").join(" ").replace(/\s+/g, " ").trim();
6806
+ 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";
6807
+ return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
6808
+ }
6695
6809
  function runRef(runKey) {
6696
6810
  return { __runRef: runKey };
6697
6811
  }
@@ -6777,6 +6891,17 @@ var SwarmClient = class _SwarmClient {
6777
6891
  // нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
6778
6892
  depSlug = /* @__PURE__ */ new Map();
6779
6893
  static DEPS_TTL_MS = 6e4;
6894
+ // TASWM-486: ссылки, по которым прод НЕ ОТВЕТИЛ (отверг вызов или не дозвонились) —
6895
+ // с какого времени, сколько попыток и чем именно отказал. Отказ ≠ ответ: вердикта в
6896
+ // `depsProd` не появляется, `depVerdicts` отдаёт `pending`, и ждущий прогон стоит,
6897
+ // пока человек не разбудит его руками (21.08 — дважды за час). Отсюда две вещи:
6898
+ // короткий backoff вместо полного TTL (переспросить, а не ждать минуту) и
6899
+ // `depsUnconfirmed` — чтобы «стою вслепую» было ВИДНО в причине отказа и в аномалии.
6900
+ depsUnanswered = /* @__PURE__ */ new Map();
6901
+ // Первый переспрос — через 5 с (тик ~4 с, то есть буквально «на следующем тике»),
6902
+ // дальше удвоением до потолка TTL: устойчиво отвергающий прод (старее демона, скоуп
6903
+ // токена) не должен получать вопрос каждые четыре секунды до конца своей жизни.
6904
+ static DEPS_RETRY_BASE_MS = 5e3;
6780
6905
  // TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
6781
6906
  // ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
6782
6907
  // каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
@@ -6835,26 +6960,26 @@ var SwarmClient = class _SwarmClient {
6835
6960
  this.repoFullNames = registry.map((e) => e.repoFullName);
6836
6961
  this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
6837
6962
  this.host = opts.host ?? null;
6838
- this.repoExists = opts.repoExists ?? existsSync6;
6963
+ this.repoExists = opts.repoExists ?? existsSync7;
6839
6964
  this.questionSyncMs = opts.questionSyncMs ?? 3e3;
6840
6965
  this.executorId = opts.executorId ?? EXECUTOR.id;
6841
6966
  this.outboxPath = opts.outboxPath;
6842
6967
  this.mapPath = `${opts.outboxPath}.prodmap.json`;
6843
6968
  this.stormPath = `${opts.outboxPath}.storm.jsonl`;
6844
- mkdirSync5(dirname(this.outboxPath), { recursive: true });
6969
+ mkdirSync6(dirname2(this.outboxPath), { recursive: true });
6845
6970
  this.recover();
6846
6971
  }
6847
6972
  // ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
6848
6973
  recover() {
6849
- if (existsSync6(this.mapPath)) {
6974
+ if (existsSync7(this.mapPath)) {
6850
6975
  try {
6851
- const obj = JSON.parse(readFileSync4(this.mapPath, "utf8"));
6976
+ const obj = JSON.parse(readFileSync5(this.mapPath, "utf8"));
6852
6977
  for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
6853
6978
  } catch {
6854
6979
  }
6855
6980
  }
6856
- if (existsSync6(this.outboxPath)) {
6857
- const txt = readFileSync4(this.outboxPath, "utf8");
6981
+ if (existsSync7(this.outboxPath)) {
6982
+ const txt = readFileSync5(this.outboxPath, "utf8");
6858
6983
  for (const line of txt.split("\n")) {
6859
6984
  const s = line.trim();
6860
6985
  if (!s) continue;
@@ -6874,10 +6999,10 @@ var SwarmClient = class _SwarmClient {
6874
6999
  // eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
6875
7000
  // переприсваиваем локально (id событий — только порядок, не корреляция с продом).
6876
7001
  replayGlobalEvents() {
6877
- if (!existsSync6(this.stormPath)) return;
7002
+ if (!existsSync7(this.stormPath)) return;
6878
7003
  let txt;
6879
7004
  try {
6880
- txt = readFileSync4(this.stormPath, "utf8");
7005
+ txt = readFileSync5(this.stormPath, "utf8");
6881
7006
  } catch {
6882
7007
  return;
6883
7008
  }
@@ -7354,27 +7479,41 @@ var SwarmClient = class _SwarmClient {
7354
7479
  * Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
7355
7480
  * бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
7356
7481
  * дороже.
7482
+ *
7483
+ * TASWM-486: ОТКАЗ ПРОДА — НЕ ОТВЕТ. Раньше `isError` засчитывался как «спросили», и
7484
+ * следующая попытка шла только через TTL; при УСТОЙЧИВОМ отказе (прод старее демона,
7485
+ * просроченный скоуп токена демона, сбой на батче) вердикта не появлялось никогда —
7486
+ * зависимость навсегда оставалась `pending`, а ждущий прогон стоял при свободной машине
7487
+ * до ручного `wake_run`. Теперь отказ помнится отдельно (`depsUnanswered`): переспрос идёт
7488
+ * по короткому нарастающему backoff, а сам факт «прод молчит» становится видимым —
7489
+ * причина отказа спавна называет его вслух, и на затянувшуюся слепоту звенит аномалия.
7357
7490
  */
7358
7491
  async refreshDeps(refs, nowMs = Date.now()) {
7359
7492
  const ask = [...new Set(refs)].filter((ref) => {
7360
7493
  if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
7361
7494
  const asked = this.depsAskedAt.get(ref);
7362
- return asked === void 0 || nowMs - asked >= _SwarmClient.DEPS_TTL_MS;
7495
+ if (asked === void 0) return true;
7496
+ return nowMs - asked >= this.depsRetryDelay(ref);
7363
7497
  });
7364
7498
  if (!ask.length) return;
7365
7499
  for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
7366
7500
  let res;
7367
7501
  try {
7368
7502
  res = await this.call("swarm_deps_status", { refs: ask });
7369
- } catch {
7503
+ } catch (e) {
7370
7504
  for (const ref of ask) this.depsAskedAt.delete(ref);
7505
+ this.noteDepsUnanswered(ask, nowMs, String(e));
7506
+ return;
7507
+ }
7508
+ if (res?.isError) {
7509
+ this.noteDepsUnanswered(ask, nowMs, toolErrorText(res));
7371
7510
  return;
7372
7511
  }
7373
- if (res?.isError) return;
7374
7512
  const deps = res.structuredContent?.deps ?? [];
7375
7513
  for (const d of deps) {
7376
7514
  const ref = typeof d.ref === "string" ? d.ref : null;
7377
7515
  if (!ref) continue;
7516
+ this.depsUnanswered.delete(ref);
7378
7517
  const outcome = String(d.outcome ?? "pending");
7379
7518
  this.depsProd.set(ref, {
7380
7519
  outcome,
@@ -7386,6 +7525,75 @@ var SwarmClient = class _SwarmClient {
7386
7525
  if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
7387
7526
  }
7388
7527
  }
7528
+ /**
7529
+ * Через сколько переспрашивать ссылку (TASWM-486). Пауза зависит от того, ЧЕГО мы ждём —
7530
+ * и сколько стоит опоздать с ответом:
7531
+ *
7532
+ * • ПРОД ОТКАЗАЛ (isError / не дозвонились) — короткий backoff с удвоением (5с →
7533
+ * потолок TTL). Ждём, что прод вообще начнёт отвечать; до тех пор вердикта нет вовсе.
7534
+ * • БЛОКЕР ЖИВ либо вердикта ещё не было (`pending`) — КАЖДЫЙ ТИК. Это единственное
7535
+ * состояние, в котором ждущий прогон ЖЖЁТ ВРЕМЯ: блокер может закрыться в любую
7536
+ * секунду, и до TASWM-486 минута TTL превращалась в минуту простоя при свободной
7537
+ * машине (21.08: «стартует сам в пределах одного-двух тиков» не выполнялось даже
7538
+ * когда прод отвечал исправно). Цена — ОДИН батч-вызов на тик (все ссылки очереди
7539
+ * идут одним запросом, см. refreshDeps), и только пока кто-то реально ждёт.
7540
+ * • ЗАВИСИМОСТЬ УЖЕ СНЯТА неуспехом (`exhausted`) или блокера не знает никто
7541
+ * (`unknown`) — штатный TTL: прогон этими вердиктами больше не задерживается
7542
+ * (`exhausted`) либо не поедет в любом случае (`unknown`, там своя аномалия), и
7543
+ * переспрос нужен только на случай, что оператор переиграет блокер новым прогоном.
7544
+ * • `done` сюда не доходит вовсе — монотонен, отфильтрован раньше.
7545
+ */
7546
+ depsRetryDelay(ref) {
7547
+ const fail = this.depsUnanswered.get(ref);
7548
+ if (fail) {
7549
+ const backoff = _SwarmClient.DEPS_RETRY_BASE_MS * 2 ** Math.min(fail.attempts - 1, 10);
7550
+ return Math.min(backoff, _SwarmClient.DEPS_TTL_MS);
7551
+ }
7552
+ const known = this.depsProd.get(ref);
7553
+ if (!known || known.outcome === "pending") return 0;
7554
+ return _SwarmClient.DEPS_TTL_MS;
7555
+ }
7556
+ /** Запомнить, что прод по этим ссылкам не ответил (см. depsUnanswered / TASWM-486). */
7557
+ noteDepsUnanswered(refs, nowMs, error) {
7558
+ for (const ref of refs) {
7559
+ const prev = this.depsUnanswered.get(ref);
7560
+ this.depsUnanswered.set(ref, {
7561
+ ref,
7562
+ slug: this.depSlug.get(ref) ?? ref,
7563
+ // `since` — начало ЭПИЗОДА слепоты, а не последняя попытка: именно возраст эпизода
7564
+ // отвечает на вопрос «сколько прогон уже стоит вслепую».
7565
+ since: prev?.since ?? nowMs,
7566
+ attempts: (prev?.attempts ?? 0) + 1,
7567
+ error
7568
+ });
7569
+ }
7570
+ }
7571
+ /**
7572
+ * TASWM-486: ссылки `dependsOn`, по которым ВЕРДИКТА ПРОДА НЕТ — не «блокер жив», а «мы
7573
+ * не знаем, жив ли он». Снаружи оба выглядели одинаково (`pending`) и вели себя одинаково
7574
+ * молча, а разница ровно в том, дождётся ли ждущий чего-нибудь сам: живой блокер
7575
+ * закончится, а неотвечающий прод — нет, и прогон стоит до ручной побудки.
7576
+ *
7577
+ * Что сюда НЕ попадает и почему:
7578
+ * • прод отвечает — слепоты нет вовсе (запись стирается первым же ответом);
7579
+ * • последний вердикт уже СНЯЛ зависимость (`met`) — прогон ею не задерживается,
7580
+ * свежесть ответа ничего не решает;
7581
+ * • последний вердикт — `unknown`: это территория `dep_missing`, у неё и текст
7582
+ * точнее («блокера не знает никто»), и лечение другое.
7583
+ * А вот УСТАРЕВШИЙ `pending` при замолчавшем проде — как раз слепота: когда-то блокер
7584
+ * был жив, с тех пор он мог закрыться, и узнать это не у кого.
7585
+ */
7586
+ depsUnconfirmed(refs) {
7587
+ const out = [];
7588
+ for (const ref of new Set(refs)) {
7589
+ const known = this.depsProd.get(ref);
7590
+ if (known?.met || known?.outcome === "unknown") continue;
7591
+ const fail = this.depsUnanswered.get(ref);
7592
+ if (!fail) continue;
7593
+ out.push({ ...fail, slug: this.depSlug.get(ref) ?? fail.slug });
7594
+ }
7595
+ return out;
7596
+ }
7389
7597
  /**
7390
7598
  * TASWM-300: точечно перечитать прогон из прода — см. докблок SwarmStore.refreshRun.
7391
7599
  * `swarm_list_runs` фильтруем по задаче прогона (taskId принимает человеко-слаг) и берём
@@ -7702,7 +7910,7 @@ var SwarmClient = class _SwarmClient {
7702
7910
  * транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
7703
7911
  */
7704
7912
  reconcileRuntime(opts) {
7705
- const wtExists = opts.worktreeExists ?? existsSync6;
7913
+ const wtExists = opts.worktreeExists ?? existsSync7;
7706
7914
  const pidByWorktree = /* @__PURE__ */ new Map();
7707
7915
  try {
7708
7916
  for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
@@ -8895,13 +9103,13 @@ var SwarmClient = class _SwarmClient {
8895
9103
  }
8896
9104
  persistPending() {
8897
9105
  try {
8898
- writeFileSync4(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
9106
+ writeFileSync5(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
8899
9107
  } catch {
8900
9108
  }
8901
9109
  }
8902
9110
  persistMap() {
8903
9111
  try {
8904
- writeFileSync4(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
9112
+ writeFileSync5(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
8905
9113
  } catch {
8906
9114
  }
8907
9115
  }
@@ -8971,7 +9179,7 @@ function tickPulse(health, now, intervalMs) {
8971
9179
  }
8972
9180
 
8973
9181
  // src/logRotate.ts
8974
- import { copyFileSync, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
9182
+ import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
8975
9183
  var realIo = {
8976
9184
  size: (path) => {
8977
9185
  try {
@@ -8980,11 +9188,11 @@ var realIo = {
8980
9188
  return null;
8981
9189
  }
8982
9190
  },
8983
- copy: copyFileSync,
9191
+ copy: copyFileSync2,
8984
9192
  rename: renameSync5,
8985
9193
  truncate: (path) => truncateSync(path, 0),
8986
9194
  unlink: unlinkSync2,
8987
- exists: existsSync7
9195
+ exists: existsSync8
8988
9196
  };
8989
9197
  var realOwnerIo = {
8990
9198
  statFd: (fd) => {
@@ -9111,13 +9319,13 @@ async function buildRepoRegistry(paths) {
9111
9319
  }
9112
9320
 
9113
9321
  // src/repoMemory.ts
9114
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
9115
- import { join as join9 } from "path";
9322
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
9323
+ import { join as join10 } from "path";
9116
9324
  var FILE = "repos.json";
9117
9325
  function loadRememberedRepos(home) {
9118
9326
  let raw;
9119
9327
  try {
9120
- raw = readFileSync7(join9(home, FILE), "utf8");
9328
+ raw = readFileSync8(join10(home, FILE), "utf8");
9121
9329
  } catch {
9122
9330
  return [];
9123
9331
  }
@@ -9132,7 +9340,7 @@ function loadRememberedRepos(home) {
9132
9340
  function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
9133
9341
  const clean = paths.filter((p) => p.trim().length > 0);
9134
9342
  if (clean.length === 0) return;
9135
- writeFileSync7(join9(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
9343
+ writeFileSync8(join10(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
9136
9344
  `);
9137
9345
  }
9138
9346
  function pickRepoPaths(args) {
@@ -9146,13 +9354,13 @@ function pickRepoPaths(args) {
9146
9354
  init_src();
9147
9355
  init_config();
9148
9356
  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";
9357
+ import { homedir as homedir4 } from "os";
9358
+ import { readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
9359
+ import { isAbsolute, join as join12, resolve as resolve2 } from "path";
9152
9360
 
9153
9361
  // src/repoScan.ts
9154
- import { readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
9155
- import { join as join10 } from "path";
9362
+ import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
9363
+ import { join as join11 } from "path";
9156
9364
  var SKIP = /* @__PURE__ */ new Set([
9157
9365
  "node_modules",
9158
9366
  "vendor",
@@ -9182,7 +9390,7 @@ function parseOriginFromGitConfig(text) {
9182
9390
  }
9183
9391
  function originOf(dir) {
9184
9392
  try {
9185
- return parseOriginFromGitConfig(readFileSync8(join10(dir, ".git", "config"), "utf8"));
9393
+ return parseOriginFromGitConfig(readFileSync9(join11(dir, ".git", "config"), "utf8"));
9186
9394
  } catch {
9187
9395
  return null;
9188
9396
  }
@@ -9217,7 +9425,7 @@ function walk(dir, depth, maxDepth, found) {
9217
9425
  }
9218
9426
  for (const name of entries) {
9219
9427
  if (name.startsWith(".") || SKIP.has(name)) continue;
9220
- const child = join10(dir, name);
9428
+ const child = join11(dir, name);
9221
9429
  try {
9222
9430
  if (!statSync6(child).isDirectory()) continue;
9223
9431
  } catch {
@@ -9239,13 +9447,13 @@ function defaultScanRoots(home, knownPaths = []) {
9239
9447
  if (parent && parent !== p) add(parent);
9240
9448
  }
9241
9449
  for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
9242
- add(join10(home, name));
9450
+ add(join11(home, name));
9243
9451
  }
9244
9452
  return out;
9245
9453
  }
9246
9454
 
9247
9455
  // src/repoDiscovery.ts
9248
- function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
9456
+ function resolveScanRoots(raw, home = homedir4(), knownPaths = []) {
9249
9457
  const v = (raw ?? "").trim();
9250
9458
  if (!v) return defaultScanRoots(home, knownPaths);
9251
9459
  if (["off", "none", "0"].includes(v.toLowerCase())) return [];
@@ -9253,7 +9461,7 @@ function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
9253
9461
  for (const part of v.split(",")) {
9254
9462
  const p = part.trim();
9255
9463
  if (!p) continue;
9256
- const abs = p === "~" ? home : p.startsWith("~/") ? join11(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
9464
+ const abs = p === "~" ? home : p.startsWith("~/") ? join12(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
9257
9465
  if (!out.includes(abs)) out.push(abs);
9258
9466
  }
9259
9467
  return out;
@@ -9467,7 +9675,7 @@ function repoMapRows(state) {
9467
9675
  }
9468
9676
  function loadRepoMapCache(path) {
9469
9677
  try {
9470
- const raw = JSON.parse(readFileSync9(path, "utf8"));
9678
+ const raw = JSON.parse(readFileSync10(path, "utf8"));
9471
9679
  if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
9472
9680
  return {
9473
9681
  scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
@@ -9482,7 +9690,7 @@ function loadRepoMapCache(path) {
9482
9690
  function writeRepoMapCache(path, entries, scannedAt = stamp()) {
9483
9691
  try {
9484
9692
  const tmp = `${path}.tmp`;
9485
- writeFileSync8(tmp, JSON.stringify({ at: stamp(), scannedAt, entries }, null, 2));
9693
+ writeFileSync9(tmp, JSON.stringify({ at: stamp(), scannedAt, entries }, null, 2));
9486
9694
  renameSync6(tmp, path);
9487
9695
  } catch {
9488
9696
  }
@@ -9490,17 +9698,17 @@ function writeRepoMapCache(path, entries, scannedAt = stamp()) {
9490
9698
 
9491
9699
  // src/hostIdentity.ts
9492
9700
  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";
9701
+ import { existsSync as existsSync9, readFileSync as readFileSync11, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
9702
+ import { join as join13 } from "path";
9495
9703
  import { randomUUID as randomUUID2 } from "crypto";
9496
9704
  function hostIdentityPath(home) {
9497
- return join12(home, "host.json");
9705
+ return join13(home, "host.json");
9498
9706
  }
9499
9707
  function loadHostIdentity(deps) {
9500
- const exists = deps.exists ?? existsSync8;
9501
- const readFile2 = deps.readFile ?? ((p) => readFileSync10(p, "utf8"));
9708
+ const exists = deps.exists ?? existsSync9;
9709
+ const readFile2 = deps.readFile ?? ((p) => readFileSync11(p, "utf8"));
9502
9710
  const writeFile3 = deps.writeFile ?? ((p, data) => {
9503
- writeFileSync9(`${p}.tmp`, data);
9711
+ writeFileSync10(`${p}.tmp`, data);
9504
9712
  renameSync7(`${p}.tmp`, p);
9505
9713
  });
9506
9714
  const host = deps.hostname ?? hostname;
@@ -9683,15 +9891,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
9683
9891
 
9684
9892
  // src/launcher.ts
9685
9893
  import { spawn } from "child_process";
9686
- import { closeSync as closeSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
9894
+ import { closeSync as closeSync2, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
9687
9895
  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";
9896
+ import { homedir as homedir5 } from "os";
9897
+ import { dirname as dirname3, join as join15 } from "path";
9898
+ import { fileURLToPath as fileURLToPath2 } from "url";
9691
9899
 
9692
9900
  // 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";
9901
+ import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
9902
+ import { join as join14, relative, sep } from "path";
9695
9903
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
9696
9904
  function droneHomeDeny(env = process.env) {
9697
9905
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -9709,26 +9917,26 @@ function buildDroneHome(opts) {
9709
9917
  const { realHome, droneHome } = opts;
9710
9918
  const deny = opts.deny ?? droneHomeDeny();
9711
9919
  const platform = opts.platform ?? process.platform;
9712
- mkdirSync6(droneHome, { recursive: true, mode: 448 });
9920
+ mkdirSync7(droneHome, { recursive: true, mode: 448 });
9713
9921
  const wanted = new Set(mirroredEntries(readdirSync7(realHome), deny, platform));
9714
9922
  const rel = relative(realHome, droneHome);
9715
9923
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
9716
9924
  for (const existing of readdirSync7(droneHome)) {
9717
9925
  if (wanted.has(existing)) continue;
9718
- const p = join13(droneHome, existing);
9926
+ const p = join14(droneHome, existing);
9719
9927
  try {
9720
9928
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
9721
9929
  } catch {
9722
9930
  }
9723
9931
  }
9724
9932
  for (const name of wanted) {
9725
- const link = join13(droneHome, name);
9933
+ const link = join14(droneHome, name);
9726
9934
  try {
9727
9935
  lstatSync(link);
9728
9936
  continue;
9729
9937
  } catch {
9730
9938
  }
9731
- const target = join13(realHome, name);
9939
+ const target = join14(realHome, name);
9732
9940
  let isDir;
9733
9941
  try {
9734
9942
  isDir = statSync7(target).isDirectory();
@@ -9749,7 +9957,7 @@ var REAL_PATH_ENV_PINS = [
9749
9957
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
9750
9958
  ];
9751
9959
  function realPathEnvPins(realHome) {
9752
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join13(realHome, ...segments)]));
9960
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join14(realHome, ...segments)]));
9753
9961
  }
9754
9962
  function dronePrivatePaths(realHome, swarmHome) {
9755
9963
  return [
@@ -9805,7 +10013,7 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
9805
10013
 
9806
10014
  // src/swarmModels.ts
9807
10015
  init_src();
9808
- function pick(v) {
10016
+ function pick2(v) {
9809
10017
  const s = typeof v === "string" ? v.trim() : "";
9810
10018
  return s.length > 0 ? s : null;
9811
10019
  }
@@ -9821,10 +10029,10 @@ function forKind(kind, swarm, norm) {
9821
10029
  }
9822
10030
  function resolveRunModelRef(run, swarm, env = process.env) {
9823
10031
  const configured = parseModelRef(
9824
- pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL)
10032
+ pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
9825
10033
  );
9826
10034
  if (configured) return configured;
9827
- const providerId = pick(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
10035
+ const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
9828
10036
  return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
9829
10037
  }
9830
10038
  function resolveRunModel(run, swarm, env = process.env) {
@@ -9861,7 +10069,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
9861
10069
  const env = { ...base3 };
9862
10070
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
9863
10071
  if (isolate && !base3.TURBO_CACHE_DIR) {
9864
- env.TURBO_CACHE_DIR = join14(worktree, ".turbo", "cache");
10072
+ env.TURBO_CACHE_DIR = join15(worktree, ".turbo", "cache");
9865
10073
  }
9866
10074
  Object.assign(env, droneTestEnv(droneCap, base3));
9867
10075
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -9889,18 +10097,18 @@ var DAEMON_STATE_ENV = [
9889
10097
  "TASKLESS_TOKEN"
9890
10098
  ];
9891
10099
  function bundledRoleDir(kind) {
9892
- return join14(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
10100
+ return join15(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
9893
10101
  }
9894
10102
  function roleDir(worktree, kind) {
9895
- return join14(worktree, ".claude", "roles", kind);
10103
+ return join15(worktree, ".claude", "roles", kind);
9896
10104
  }
9897
10105
  function roleDirs(worktree, kind) {
9898
- return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync9(dir));
10106
+ return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync10(dir));
9899
10107
  }
9900
10108
  function nextRunLogPath(runLogsDir, runLogKeys) {
9901
10109
  const dir = runLogDirForWrite(runLogsDir, runLogKeys);
9902
10110
  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 });
10111
+ mkdirSync8(dir, { recursive: true });
9904
10112
  let files = [];
9905
10113
  try {
9906
10114
  files = readdirSync8(dir);
@@ -9911,7 +10119,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
9911
10119
  const m = /^(\d+)\.json/.exec(f);
9912
10120
  if (m) next = Math.max(next, Number(m[1]) + 1);
9913
10121
  }
9914
- return join14(dir, `${next}.json`);
10122
+ return join15(dir, `${next}.json`);
9915
10123
  }
9916
10124
  function openNextRunLog(runLogsDir, runLogKeys) {
9917
10125
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -9992,11 +10200,11 @@ function makeLauncher(cfg) {
9992
10200
  } catch (e) {
9993
10201
  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
10202
  }
9995
- const realHome = cfg.droneIsolation?.realHome ?? homedir4();
10203
+ const realHome = cfg.droneIsolation?.realHome ?? homedir5();
9996
10204
  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"))) {
10205
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync10(join15(cfg.droneIsolation.swarmHome, "token"))) {
9998
10206
  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.`
10207
+ `[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
10208
  );
10001
10209
  }
10002
10210
  const executor = cfg.executor ?? resolveExecutor();
@@ -10057,8 +10265,8 @@ function makeLauncher(cfg) {
10057
10265
  init_procCleanup();
10058
10266
 
10059
10267
  // src/cost.ts
10060
- import { readFileSync as readFileSync11, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10061
- import { join as join15 } from "path";
10268
+ import { readFileSync as readFileSync12, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10269
+ import { join as join16 } from "path";
10062
10270
  init_executors();
10063
10271
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
10064
10272
  function unprocessedLogFiles(dir) {
@@ -10080,10 +10288,10 @@ function accountRunCost(deps, run) {
10080
10288
  let durationMs = 0;
10081
10289
  const processed = [];
10082
10290
  for (const f of files) {
10083
- const full = join15(dir, f);
10291
+ const full = join16(dir, f);
10084
10292
  let raw = null;
10085
10293
  try {
10086
- raw = readFileSync11(full, "utf8");
10294
+ raw = readFileSync12(full, "utf8");
10087
10295
  } catch {
10088
10296
  continue;
10089
10297
  }
@@ -10132,8 +10340,8 @@ function makeCostSweeper(deps) {
10132
10340
  }
10133
10341
 
10134
10342
  // src/runOutcome.ts
10135
- import { readdirSync as readdirSync10, readFileSync as readFileSync12 } from "fs";
10136
- import { join as join16 } from "path";
10343
+ import { readdirSync as readdirSync10, readFileSync as readFileSync13 } from "fs";
10344
+ import { join as join17 } from "path";
10137
10345
  init_executors();
10138
10346
  function endedItself(outcome) {
10139
10347
  return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
@@ -10160,7 +10368,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
10160
10368
  }
10161
10369
  if (!latest) return null;
10162
10370
  try {
10163
- return readFileSync12(join16(dir, latest.name), "utf8");
10371
+ return readFileSync13(join17(dir, latest.name), "utf8");
10164
10372
  } catch {
10165
10373
  return null;
10166
10374
  }
@@ -10193,20 +10401,20 @@ init_src();
10193
10401
  init_git();
10194
10402
 
10195
10403
  // src/deps.ts
10196
- import { existsSync as existsSync11, readdirSync as readdirSync12, readFileSync as readFileSync13 } from "fs";
10197
- import { join as join19 } from "path";
10404
+ import { existsSync as existsSync12, readdirSync as readdirSync12, readFileSync as readFileSync14 } from "fs";
10405
+ import { join as join20 } from "path";
10198
10406
  import { execa } from "execa";
10199
10407
 
10200
10408
  // src/prepareLock.ts
10201
10409
  import { createHash } from "crypto";
10202
10410
  import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
10203
- import { join as join18 } from "path";
10411
+ import { join as join19 } from "path";
10204
10412
  import { tmpdir } from "os";
10205
10413
  var WAIT_TIMEOUT_MS = 5 * 6e4;
10206
10414
  var POLL_MS = 300;
10207
10415
  var STALE_MS = 20 * 6e4;
10208
10416
  function machineLockPath(key, dir = tmpdir()) {
10209
- return join18(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
10417
+ return join19(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
10210
10418
  }
10211
10419
  function defaultTryCreate(path, content) {
10212
10420
  try {
@@ -10280,7 +10488,7 @@ async function runCommand(cmd, worktree) {
10280
10488
  }
10281
10489
  function safeReadText(p) {
10282
10490
  try {
10283
- return readFileSync13(p, "utf8");
10491
+ return readFileSync14(p, "utf8");
10284
10492
  } catch {
10285
10493
  return null;
10286
10494
  }
@@ -10320,21 +10528,21 @@ function overridePlan(override) {
10320
10528
  };
10321
10529
  }
10322
10530
  function detectPackageManager(worktree, io = {}) {
10323
- const exists = io.exists ?? existsSync11;
10531
+ const exists = io.exists ?? existsSync12;
10324
10532
  const readText = io.readText ?? safeReadText;
10325
- const hasPkgJson = exists(join19(worktree, "package.json"));
10533
+ const hasPkgJson = exists(join20(worktree, "package.json"));
10326
10534
  if (!hasPkgJson) {
10327
- if (exists(join19(worktree, "deno.json")) || exists(join19(worktree, "deno.jsonc")) || exists(join19(worktree, "deno.lock"))) return "deno";
10535
+ if (exists(join20(worktree, "deno.json")) || exists(join20(worktree, "deno.jsonc")) || exists(join20(worktree, "deno.lock"))) return "deno";
10328
10536
  return null;
10329
10537
  }
10330
- const pkg = readJson(readText, join19(worktree, "package.json"));
10538
+ const pkg = readJson(readText, join20(worktree, "package.json"));
10331
10539
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
10332
10540
  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";
10541
+ if (exists(join20(worktree, "bun.lock")) || exists(join20(worktree, "bun.lockb"))) return "bun";
10542
+ if (exists(join20(worktree, "pnpm-lock.yaml"))) return "pnpm";
10543
+ if (exists(join20(worktree, "yarn.lock"))) return "yarn";
10544
+ if (exists(join20(worktree, "package-lock.json"))) return "npm";
10545
+ if (exists(join20(worktree, "deno.lock"))) return "deno";
10338
10546
  return "npm";
10339
10547
  }
10340
10548
  function jsInstallCommand(pm) {
@@ -10359,7 +10567,10 @@ var ECOSYSTEM_MARKERS = [
10359
10567
  // pyproject сам по себе не говорит, кем ставить — решает лок-файл рядом.
10360
10568
  command: null,
10361
10569
  refine: [
10362
- { file: "uv.lock", ecosystem: "python/uv", command: "uv sync" },
10570
+ // --frozen: ставим ровно по локу и не переписываем его (TAS-572). Без
10571
+ // флага uv доресолвливает лок под pyproject прямо в worktree дрона, и
10572
+ // чужой дифф на полторы сотни строк уезжает в PR по несвязанной задаче.
10573
+ { file: "uv.lock", ecosystem: "python/uv", command: "uv sync --frozen" },
10363
10574
  { file: "poetry.lock", ecosystem: "python/poetry", command: "poetry install" },
10364
10575
  { file: "requirements.txt", ecosystem: "python/pip", command: "pip install -r requirements.txt" }
10365
10576
  ]
@@ -10387,7 +10598,7 @@ var ECOSYSTEM_MARKERS = [
10387
10598
  // конфигурация — дело сборки, не подготовки
10388
10599
  ];
10389
10600
  function detectPlan(worktree, io) {
10390
- const exists = io.exists ?? existsSync11;
10601
+ const exists = io.exists ?? existsSync12;
10391
10602
  const listFiles = io.listFiles ?? safeListFiles;
10392
10603
  const pm = detectPackageManager(worktree, io);
10393
10604
  if (pm) {
@@ -10395,7 +10606,7 @@ function detectPlan(worktree, io) {
10395
10606
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
10396
10607
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
10397
10608
  // заново, но он дешёвый: тянет из того же общего кэша.
10398
- { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join19(worktree, "node_modules"), source: "detect" }
10609
+ { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join20(worktree, "node_modules"), source: "detect" }
10399
10610
  ];
10400
10611
  for (const target of workspaceBuildTargets(worktree, io)) {
10401
10612
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -10404,12 +10615,12 @@ function detectPlan(worktree, io) {
10404
10615
  return { source: "detect", ecosystem: `node/${pm}`, commands };
10405
10616
  }
10406
10617
  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));
10618
+ const hit = marker.files.some((f) => exists(join20(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
10408
10619
  if (!hit) continue;
10409
10620
  let ecosystem = marker.ecosystem;
10410
10621
  let command = marker.command;
10411
10622
  for (const refine of marker.refine ?? []) {
10412
- if (!exists(join19(worktree, refine.file))) continue;
10623
+ if (!exists(join20(worktree, refine.file))) continue;
10413
10624
  ecosystem = refine.ecosystem;
10414
10625
  command = refine.command;
10415
10626
  break;
@@ -10466,12 +10677,12 @@ var CI_INSTALL_PATTERNS = [
10466
10677
  function ciWorkflowPlan(worktree, io) {
10467
10678
  const readText = io.readText ?? safeReadText;
10468
10679
  const listFiles = io.listFiles ?? safeListFiles;
10469
- const dir = join19(worktree, ".github", "workflows");
10680
+ const dir = join20(worktree, ".github", "workflows");
10470
10681
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
10471
10682
  const seen = /* @__PURE__ */ new Set();
10472
10683
  const commands = [];
10473
10684
  for (const name of files.sort()) {
10474
- const yaml = readText(join19(dir, name));
10685
+ const yaml = readText(join20(dir, name));
10475
10686
  if (yaml === null) continue;
10476
10687
  for (const raw of workflowRunLines(yaml)) {
10477
10688
  const line = raw.replace(/\s+/g, " ").trim();
@@ -10486,7 +10697,7 @@ function ciWorkflowPlan(worktree, io) {
10486
10697
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
10487
10698
  }
10488
10699
  function workspaceGlobs(worktree, readText) {
10489
- const yaml = readText(join19(worktree, "pnpm-workspace.yaml"));
10700
+ const yaml = readText(join20(worktree, "pnpm-workspace.yaml"));
10490
10701
  if (yaml !== null) {
10491
10702
  const globs = [];
10492
10703
  let inPackages = false;
@@ -10506,7 +10717,7 @@ function workspaceGlobs(worktree, readText) {
10506
10717
  }
10507
10718
  if (globs.length) return globs;
10508
10719
  }
10509
- const pkg = readJson(readText, join19(worktree, "package.json"));
10720
+ const pkg = readJson(readText, join20(worktree, "package.json"));
10510
10721
  const ws = pkg?.workspaces;
10511
10722
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
10512
10723
  return list.filter((g) => typeof g === "string");
@@ -10517,14 +10728,14 @@ function workspaceDirs(worktree, globs, io) {
10517
10728
  if (glob.startsWith("!")) continue;
10518
10729
  const star = glob.indexOf("*");
10519
10730
  if (star === -1) {
10520
- const dir = join19(worktree, glob);
10521
- if (io.exists(join19(dir, "package.json"))) dirs.add(dir);
10731
+ const dir = join20(worktree, glob);
10732
+ if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
10522
10733
  continue;
10523
10734
  }
10524
- const base3 = join19(worktree, glob.slice(0, star).replace(/\/$/, ""));
10735
+ const base3 = join20(worktree, glob.slice(0, star).replace(/\/$/, ""));
10525
10736
  for (const name of io.listDirs(base3)) {
10526
- const dir = join19(base3, name);
10527
- if (io.exists(join19(dir, "package.json"))) dirs.add(dir);
10737
+ const dir = join20(base3, name);
10738
+ if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
10528
10739
  }
10529
10740
  }
10530
10741
  return [...dirs];
@@ -10545,18 +10756,18 @@ function entryPaths(pkg) {
10545
10756
  return out;
10546
10757
  }
10547
10758
  function workspaceBuildTargets(worktree, io = {}) {
10548
- const exists = io.exists ?? existsSync11;
10759
+ const exists = io.exists ?? existsSync12;
10549
10760
  const readText = io.readText ?? safeReadText;
10550
10761
  const listDirs = io.listDirs ?? safeListDirs;
10551
10762
  const globs = workspaceGlobs(worktree, readText);
10552
10763
  if (!globs.length) return [];
10553
10764
  const pkgs = [];
10554
10765
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
10555
- const pkg = readJson(readText, join19(dir, "package.json"));
10766
+ const pkg = readJson(readText, join20(dir, "package.json"));
10556
10767
  if (!pkg || typeof pkg.name !== "string") continue;
10557
10768
  const scripts = pkg.scripts ?? {};
10558
10769
  const entries = entryPaths(pkg);
10559
- const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join19(dir, e)));
10770
+ const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join20(dir, e)));
10560
10771
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
10561
10772
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
10562
10773
  }
@@ -10584,7 +10795,7 @@ function devDatabaseUrl(worktree, io = {}) {
10584
10795
  const fromEnv = (io.env ?? process.env).DATABASE_URL;
10585
10796
  if (fromEnv && fromEnv.trim()) return fromEnv.trim();
10586
10797
  for (const file of [".env", ".env.example"]) {
10587
- const raw = readText(join19(worktree, file));
10798
+ const raw = readText(join20(worktree, file));
10588
10799
  if (raw === null) continue;
10589
10800
  const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
10590
10801
  const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
@@ -10600,11 +10811,11 @@ function isLocalDatabase(url) {
10600
10811
  }
10601
10812
  }
10602
10813
  function prismaPackages(worktree, io) {
10603
- const exists = io.exists ?? existsSync11;
10814
+ const exists = io.exists ?? existsSync12;
10604
10815
  const readText = io.readText ?? safeReadText;
10605
10816
  const listDirs = io.listDirs ?? safeListDirs;
10606
10817
  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);
10818
+ return dirs.filter((dir) => exists(join20(dir, "prisma", "schema.prisma")) && listDirs(join20(dir, "prisma", "migrations")).length > 0);
10608
10819
  }
10609
10820
  function jsExecBin(pm, command) {
10610
10821
  switch (pm) {
@@ -10637,7 +10848,7 @@ function planPrepare(worktree, io = {}) {
10637
10848
  return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
10638
10849
  }
10639
10850
  async function ensureWorktreeDeps(worktree, io = {}) {
10640
- const exists = io.exists ?? existsSync11;
10851
+ const exists = io.exists ?? existsSync12;
10641
10852
  const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
10642
10853
  const approve = io.approve ?? (() => true);
10643
10854
  const plan = planPrepare(worktree, io);
@@ -10833,6 +11044,10 @@ function isPreStartFailure(run) {
10833
11044
  function isSpawnParked(run) {
10834
11045
  return (run.stop_reason ?? "").includes(SPAWN_PARK_MARK);
10835
11046
  }
11047
+ var UNRETRIABLE_PARK_MARK = "\u043F\u043E\u0432\u0442\u043E\u0440 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442";
11048
+ function isUnretriableParked(run) {
11049
+ return isPreStartFailure(run) && (run.stop_reason ?? "").includes(UNRETRIABLE_PARK_MARK);
11050
+ }
10836
11051
  var SPAWN_ATTEMPT_WINDOW_MS = 60 * 6e4;
10837
11052
  function preStartAttempts(runs, taskSlug, opts = {}) {
10838
11053
  const windowMs = opts.windowMs ?? SPAWN_ATTEMPT_WINDOW_MS;
@@ -10846,9 +11061,13 @@ function preStartAttempts(runs, taskSlug, opts = {}) {
10846
11061
  const at = run.created_at ? stampMs(run.created_at) : Number.NaN;
10847
11062
  if (Number.isFinite(at) && now - at >= windowMs) break;
10848
11063
  n += 1;
11064
+ if (isUnretriableParked(run)) return Math.max(n, PRE_START_ATTEMPT_LIMIT);
10849
11065
  }
10850
11066
  return n;
10851
11067
  }
11068
+ function lastPreStartAttempt(runs, taskSlug) {
11069
+ return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort((a, b) => b.id - a.id)[0] ?? null;
11070
+ }
10852
11071
  function unresolvedRepoReason(repo, served) {
10853
11072
  const raw = repo.trim();
10854
11073
  if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
@@ -10858,6 +11077,9 @@ function unresolvedRepoReason(repo, served) {
10858
11077
  function parkReason(attempts, reason) {
10859
11078
  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
11079
  }
11080
+ function parkReasonUnretriable(reason) {
11081
+ 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}`;
11082
+ }
10861
11083
 
10862
11084
  // src/worktreeReap.ts
10863
11085
  init_src();
@@ -11046,7 +11268,7 @@ async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
11046
11268
  return APPROVE_RE.test(m[1]) ? "approve" : "changes";
11047
11269
  });
11048
11270
  }
11049
- async function scanPrComments(prUrl, sinceIso, run, pick3) {
11271
+ async function scanPrComments(prUrl, sinceIso, run, pick4) {
11050
11272
  if (!isGithubPrUrl(prUrl)) return null;
11051
11273
  let out;
11052
11274
  try {
@@ -11066,7 +11288,7 @@ async function scanPrComments(prUrl, sinceIso, run, pick3) {
11066
11288
  const c = comments[i];
11067
11289
  if (!c?.body || !c.createdAt) continue;
11068
11290
  if (Number.isFinite(sinceMs) && Date.parse(c.createdAt) < sinceMs) continue;
11069
- const verdict = pick3(c.body);
11291
+ const verdict = pick4(c.body);
11070
11292
  if (verdict == null) continue;
11071
11293
  return { verdict, createdAt: c.createdAt };
11072
11294
  }
@@ -11243,6 +11465,7 @@ ${logs.join("\n---\n")}` : header;
11243
11465
  // src/droneServer.ts
11244
11466
  init_droneChannel();
11245
11467
  init_taskless();
11468
+ init_toolFailure();
11246
11469
  init_attachments();
11247
11470
  init_shotUpload();
11248
11471
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -11300,8 +11523,11 @@ init_src();
11300
11523
 
11301
11524
  // src/reviewCycle.ts
11302
11525
  init_src();
11526
+ function needsFixIteration(run) {
11527
+ return run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
11528
+ }
11303
11529
  function reviewTransitionPatch(run, prUrl) {
11304
- const isFixIteration = run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
11530
+ const isFixIteration = needsFixIteration(run);
11305
11531
  if (isFixIteration) {
11306
11532
  const cycle = (run.review_cycle ?? 0) + 1;
11307
11533
  return {
@@ -11336,6 +11562,18 @@ function applyReviewTransition(db, run, prUrl) {
11336
11562
 
11337
11563
  // src/droneServer.ts
11338
11564
  init_src();
11565
+ function readTaskFailureInstruction(slug, failure) {
11566
+ const why = describeToolFailure(failure);
11567
+ if (TOOL_FAILURE_RETRIABLE[failure.code]) {
11568
+ 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).`;
11569
+ }
11570
+ return [
11571
+ `\u26D4 \u0417\u0410\u0414\u0410\u0427\u0423 ${slug} \u041F\u0420\u041E\u0427\u0418\u0422\u0410\u0422\u042C \u041D\u0415\u041B\u042C\u0417\u042F: ${why}.`,
11572
+ "\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.",
11573
+ "\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).",
11574
+ "\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."
11575
+ ].join("\n");
11576
+ }
11339
11577
  function freezeInstruction(branch, reason = "\u0435\u0433\u043E \u043F\u0443\u043B\u044C\u0441 (heartbeat) \u0443\u0441\u0442\u0430\u0440\u0435\u043B") {
11340
11578
  return [
11341
11579
  `\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 +11671,17 @@ async function buildDroneServer(store, opts = {}) {
11433
11671
  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
11672
  }
11435
11673
  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.`;
11674
+ const read = await opts.taskless.getTaskDetailed(slug);
11675
+ if (!read.ok) {
11676
+ const failText = readTaskFailureInstruction(slug, read.failure);
11439
11677
  return {
11440
11678
  content: [{ type: "text", text: mismatchNote ? `${mismatchNote}
11441
11679
 
11442
- ${notFoundText}` : notFoundText }],
11680
+ ${failText}` : failText }],
11443
11681
  isError: true
11444
11682
  };
11445
11683
  }
11684
+ const task = read.task;
11446
11685
  const context = await fetchTaskContext(opts.taskless, task);
11447
11686
  const attachments = await prepareTaskAttachments({
11448
11687
  worktree: opts.worktree ?? process.cwd(),
@@ -12062,8 +12301,9 @@ ${report}${shots.warning}`,
12062
12301
  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
12302
  const hero = await heroRun();
12064
12303
  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.`);
12304
+ const read = await taskless.getTaskDetailed(slug);
12305
+ 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)}.`);
12306
+ const task = read.task;
12067
12307
  const ep = await epic(hero.task_slug);
12068
12308
  if (!ep || task.parentId !== ep.id) {
12069
12309
  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 +12816,31 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
12576
12816
  })
12577
12817
  });
12578
12818
  }
12819
+ const GROUP_DEP_UNCONFIRMED = {
12820
+ id: "dep_unconfirmed",
12821
+ 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`
12822
+ };
12823
+ for (const run of allRuns) {
12824
+ if (run.status !== "queued") continue;
12825
+ if (!run.depends_on || run.depends_on.length === 0) continue;
12826
+ const blind = db.depsUnconfirmed?.(run.depends_on) ?? [];
12827
+ if (!blind.length) continue;
12828
+ candidates.push({
12829
+ key: `dep_unconfirmed:${run.task_slug}`,
12830
+ // по СЛАГУ, а не по id зеркала (см. dep_missing)
12831
+ minDurationMs: thresholds.depMissingMs,
12832
+ group: GROUP_DEP_UNCONFIRMED,
12833
+ build: () => ({
12834
+ runId: run.id,
12835
+ // Пуш: сам рой из этого не выйдет — вердикт приходит только от прода, а прод
12836
+ // отказывает устойчиво. Лечит человек (обновить прод / перевыдать токен демона).
12837
+ level: "decision",
12838
+ 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"),
12839
+ 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.`,
12840
+ ...hrefOf(run.task_url)
12841
+ })
12842
+ });
12843
+ }
12579
12844
  if (opts.gateReason && queuedCount > 0) {
12580
12845
  const reason = opts.gateReason;
12581
12846
  candidates.push({
@@ -12892,6 +13157,7 @@ function resilientSwarmMcp(opts = {}) {
12892
13157
 
12893
13158
  // src/overmind.ts
12894
13159
  init_taskless();
13160
+ init_toolFailure();
12895
13161
 
12896
13162
  // src/workspaceAccess.ts
12897
13163
  function workspaceAccessLines(input) {
@@ -13123,29 +13389,27 @@ function makeDroneCapReader(opts) {
13123
13389
 
13124
13390
  // src/mergeGate.ts
13125
13391
  init_src();
13392
+ init_config();
13126
13393
 
13127
13394
  // src/mergeQueue.ts
13128
13395
  init_src();
13396
+ init_config();
13129
13397
 
13130
13398
  // 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
- }
13399
+ init_src();
13139
13400
 
13140
13401
  // src/mergeQueue.ts
13141
13402
  var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
13142
13403
  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;
13404
+ return !!r.pr_url && isPrAuthorRun(r) && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
13405
+ }
13406
+ function isPrAuthorRun(r) {
13407
+ return r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null;
13144
13408
  }
13145
- function mergeQueueCandidates(runs, repo) {
13409
+ function mergeQueueCandidates(runs, repo, repoFallback) {
13146
13410
  const byPr = /* @__PURE__ */ new Map();
13147
13411
  for (const r of runs) {
13148
- if (r.repo !== repo || !isMergeCandidate(r)) continue;
13412
+ if (effectiveRepoOf(r.repo, repoFallback) !== repo || !isMergeCandidate(r)) continue;
13149
13413
  const num = prNumberOf(r.pr_url);
13150
13414
  const seen = byPr.get(num);
13151
13415
  if (!seen || r.id > seen.id) byPr.set(num, r);
@@ -13175,7 +13439,7 @@ function makeMergeQueueResolver(deps) {
13175
13439
  if (!deps.baseSync || !run.pr_url) return null;
13176
13440
  const num = prNumberOf(run.pr_url);
13177
13441
  if (num == null) return null;
13178
- const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
13442
+ const candidates = mergeQueueCandidates(deps.listRuns(), effectiveRepoOf(run.repo, deps.repoFallback), deps.repoFallback);
13179
13443
  const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
13180
13444
  if (!mine) return null;
13181
13445
  const ready = [];
@@ -13209,15 +13473,16 @@ function needsBaseRefresh(d) {
13209
13473
  }
13210
13474
 
13211
13475
  // 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;
13476
+ function findRunByPr(runs, ref, scope = {}) {
13477
+ const rows = runs.map((run) => ({
13478
+ run,
13479
+ prUrl: run.pr_url,
13480
+ repoKey: scope.repoOfRun ? scope.repoOfRun(run) : null,
13481
+ author: isPrAuthorRun(run),
13482
+ reviewVerdict: run.review_verdict,
13483
+ order: run.id
13484
+ }));
13485
+ return pickRunForPr(rows, ref, scope.repo ?? null)?.run ?? null;
13221
13486
  }
13222
13487
  function makeMergeGateResolver(deps) {
13223
13488
  return makeMergeGateParts(deps).gate;
@@ -13233,15 +13498,17 @@ function makeMergeGateParts(deps) {
13233
13498
  verifyVerdict: run.verify_verdict ?? null
13234
13499
  });
13235
13500
  };
13501
+ const repoOfRun = deps.repoFullNameOf ? (r) => deps.repoFullNameOf(effectiveRepoOf(r.repo, deps.repoFallback)) : void 0;
13236
13502
  const queueFor = makeMergeQueueResolver({
13237
13503
  listRuns: deps.listRuns,
13238
13504
  gateAllows: async (r) => (await gateFor(r)).allowed,
13505
+ ...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
13239
13506
  ...deps.baseSync ? { baseSync: deps.baseSync } : {},
13240
13507
  ...deps.checks ? { checks: deps.checks } : {},
13241
13508
  ...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
13242
13509
  });
13243
- const gate = async (ref) => {
13244
- const run = findRunByPr(deps.listRuns(), ref);
13510
+ const gate = async (ref, ctxRepo) => {
13511
+ const run = findRunByPr(deps.listRuns(), ref, { repo: ctxRepo ?? null, ...repoOfRun ? { repoOfRun } : {} });
13245
13512
  if (!run) {
13246
13513
  return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
13247
13514
  }
@@ -13264,22 +13531,22 @@ function makeMergeGateParts(deps) {
13264
13531
  }
13265
13532
 
13266
13533
  // src/envGate.ts
13267
- import { existsSync as existsSync14 } from "fs";
13268
- import { join as join25 } from "path";
13534
+ import { existsSync as existsSync15 } from "fs";
13535
+ import { join as join26 } from "path";
13269
13536
  import { createConnection } from "net";
13270
13537
  import { execa as execa2 } from "execa";
13271
13538
 
13272
13539
  // src/platform.ts
13273
- import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
13274
- import { delimiter, join as join24 } from "path";
13540
+ import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
13541
+ import { delimiter, join as join25 } from "path";
13275
13542
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
13276
- if (name.includes("/") || name.includes("\\")) return existsSync13(name) ? name : null;
13543
+ if (name.includes("/") || name.includes("\\")) return existsSync14(name) ? name : null;
13277
13544
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
13278
13545
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
13279
13546
  for (const dir of dirs) {
13280
13547
  for (const ext of exts) {
13281
- const candidate = join24(dir, name + ext);
13282
- if (existsSync13(candidate)) return candidate;
13548
+ const candidate = join25(dir, name + ext);
13549
+ if (existsSync14(candidate)) return candidate;
13283
13550
  }
13284
13551
  }
13285
13552
  return null;
@@ -13362,8 +13629,8 @@ var COMPOSE_MARKERS = [
13362
13629
  "compose.yaml"
13363
13630
  ];
13364
13631
  function detectRequirements(repo, io = {}) {
13365
- const exists = io.exists ?? existsSync14;
13366
- const marker = COMPOSE_MARKERS.find((f) => exists(join25(repo, f)));
13632
+ const exists = io.exists ?? existsSync15;
13633
+ const marker = COMPOSE_MARKERS.find((f) => exists(join26(repo, f)));
13367
13634
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
13368
13635
  }
13369
13636
  function resolveRequirements(repo, declared, io = {}) {
@@ -13500,7 +13767,7 @@ import { serve as serve2 } from "@hono/node-server";
13500
13767
 
13501
13768
  // src/diff.ts
13502
13769
  import { statSync as statSync10 } from "fs";
13503
- import { join as join26 } from "path";
13770
+ import { join as join27 } from "path";
13504
13771
  import { $ as $4 } from "execa";
13505
13772
  var MAX_FILES = 200;
13506
13773
  var MAX_LINES_PER_FILE = 1500;
@@ -13668,7 +13935,7 @@ async function collectRunDiff(worktree, opts = {}) {
13668
13935
  files.push(pf);
13669
13936
  }
13670
13937
  }
13671
- files.sort((a, b) => mtime(join26(worktree, b.path)) - mtime(join26(worktree, a.path)));
13938
+ files.sort((a, b) => mtime(join27(worktree, b.path)) - mtime(join27(worktree, a.path)));
13672
13939
  const truncatedFiles = files.length > MAX_FILES;
13673
13940
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
13674
13941
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -13701,7 +13968,7 @@ async function changedPaths(worktree, opts = {}) {
13701
13968
  // package.json
13702
13969
  var package_default = {
13703
13970
  name: "@taskless-app/swarm",
13704
- version: "0.6.23",
13971
+ version: "0.6.25",
13705
13972
  type: "module",
13706
13973
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13707
13974
  keywords: [
@@ -13731,6 +13998,7 @@ var package_default = {
13731
13998
  files: [
13732
13999
  "dist",
13733
14000
  "docs/HERO.md",
14001
+ "hooks",
13734
14002
  "roles"
13735
14003
  ],
13736
14004
  scripts: {
@@ -13751,7 +14019,7 @@ var package_default = {
13751
14019
  execa: "^10.0.0",
13752
14020
  zod: "3.25.76"
13753
14021
  },
13754
- gitHead: "af6c4af0f523891b5db805d9b4583021688ed1f5"
14022
+ gitHead: "36aaed0414a46ea272ad762b81e051fecec40eaa"
13755
14023
  };
13756
14024
 
13757
14025
  // src/version.ts
@@ -15199,13 +15467,15 @@ function makeHandler(deps) {
15199
15467
  if (p === "/merge-gate") {
15200
15468
  const ref = url.searchParams.get("pr") ?? "";
15201
15469
  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 });
15470
+ const ctxRepo = url.searchParams.get("repo");
15202
15471
  const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
15203
15472
  listRuns: () => deps.db.listRuns(),
15204
15473
  fallbackAutoReview: SWARM_AUTO_REVIEW,
15205
- autoVerify: SWARM_AUTO_VERIFY
15474
+ autoVerify: SWARM_AUTO_VERIFY,
15206
15475
  // TASWM-344
15476
+ ...defaultRepo ? { repoFallback: defaultRepo } : {}
15207
15477
  });
15208
- return Response.json(await resolve4(ref));
15478
+ return Response.json(await resolve4(ref, ctxRepo));
15209
15479
  }
15210
15480
  if (p === "/resources") return Response.json(hostResourceSnapshot());
15211
15481
  if (p === "/timeline") return Response.json(timelineState(deps.db, Date.now(), readNarratorLog(deps.narratorLog)));
@@ -15384,21 +15654,21 @@ async function checkDaemonVersion(start, opts = {}) {
15384
15654
 
15385
15655
  // src/hostCommands.ts
15386
15656
  init_src();
15387
- import { readFileSync as readFileSync18, renameSync as renameSync9, writeFileSync as writeFileSync13 } from "fs";
15388
- import { join as join27 } from "path";
15657
+ import { readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
15658
+ import { join as join28 } from "path";
15389
15659
  function hostCommandStatePath(home) {
15390
- return join27(home, "host-command.json");
15660
+ return join28(home, "host-command.json");
15391
15661
  }
15392
15662
  function readState(path) {
15393
15663
  try {
15394
- return JSON.parse(readFileSync18(path, "utf8"));
15664
+ return JSON.parse(readFileSync19(path, "utf8"));
15395
15665
  } catch {
15396
15666
  return {};
15397
15667
  }
15398
15668
  }
15399
15669
  function writeState(path, state) {
15400
15670
  try {
15401
- writeFileSync13(`${path}.tmp`, JSON.stringify(state));
15671
+ writeFileSync14(`${path}.tmp`, JSON.stringify(state));
15402
15672
  renameSync9(`${path}.tmp`, path);
15403
15673
  } catch {
15404
15674
  }
@@ -15561,8 +15831,8 @@ async function doRestart(cmd, opts, note) {
15561
15831
 
15562
15832
  // src/selfUpdate.ts
15563
15833
  init_git();
15564
- import { existsSync as existsSync15 } from "fs";
15565
- import { join as join28 } from "path";
15834
+ import { existsSync as existsSync16 } from "fs";
15835
+ import { join as join29 } from "path";
15566
15836
  import { $ as $5 } from "execa";
15567
15837
  var DAEMON_PACKAGE = "@taskless-app/swarm";
15568
15838
  var defaultRun = async (cmd, args, cwd) => {
@@ -15575,7 +15845,7 @@ function tail(text, max = 400) {
15575
15845
  }
15576
15846
  async function runSelfUpdate(io) {
15577
15847
  const run = io.run ?? defaultRun;
15578
- const exists = io.exists ?? existsSync15;
15848
+ const exists = io.exists ?? existsSync16;
15579
15849
  const repo = io.repo;
15580
15850
  await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
15581
15851
  const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
@@ -15604,7 +15874,7 @@ async function runSelfUpdate(io) {
15604
15874
  };
15605
15875
  }
15606
15876
  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"))) {
15877
+ if (!exists(join29(repo, "pnpm-lock.yaml"))) {
15608
15878
  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
15879
  }
15610
15880
  const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
@@ -15763,19 +16033,19 @@ async function fetchExternalStorm(url, opts = {}) {
15763
16033
  }
15764
16034
 
15765
16035
  // 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";
16036
+ import { existsSync as existsSync17, readFileSync as readFileSync20, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
16037
+ import { join as join30 } from "path";
15768
16038
  var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
15769
16039
  var MAX_RESOLVED = 200;
15770
16040
  function emptyState() {
15771
16041
  return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
15772
16042
  }
15773
16043
  function anomalyStatePath(home) {
15774
- return join29(home, "anomaly-state.json");
16044
+ return join30(home, "anomaly-state.json");
15775
16045
  }
15776
16046
  function loadAnomalyState(home, now = Date.now(), io = {}) {
15777
- const exists = io.exists ?? existsSync16;
15778
- const readFile2 = io.readFile ?? ((p) => readFileSync19(p, "utf8"));
16047
+ const exists = io.exists ?? existsSync17;
16048
+ const readFile2 = io.readFile ?? ((p) => readFileSync20(p, "utf8"));
15779
16049
  const path = anomalyStatePath(home);
15780
16050
  if (!exists(path)) return emptyState();
15781
16051
  try {
@@ -15789,9 +16059,9 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
15789
16059
  )
15790
16060
  );
15791
16061
  return {
15792
- since: pick2(since, fresh),
15793
- lastFiredAt: pick2(lastFiredAt, fresh),
15794
- fires: pick2(fires, fresh),
16062
+ since: pick3(since, fresh),
16063
+ lastFiredAt: pick3(lastFiredAt, fresh),
16064
+ fires: pick3(fires, fresh),
15795
16065
  // TAS-485: очередь гашения НЕ фильтруем тем же TTL, что ключи. Она про
15796
16066
  // уведомление, которое УЖЕ висит у человека в инбоксе: пролежав неделю
15797
16067
  // недоставленным, гашение не становится лишним — оно становится нужнее.
@@ -15805,7 +16075,7 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
15805
16075
  function saveAnomalyState(home, state, io = {}) {
15806
16076
  const path = anomalyStatePath(home);
15807
16077
  const writeFile3 = io.writeFile ?? ((p, data) => {
15808
- writeFileSync14(`${p}.tmp`, data);
16078
+ writeFileSync15(`${p}.tmp`, data);
15809
16079
  renameSync10(`${p}.tmp`, p);
15810
16080
  });
15811
16081
  writeFile3(path, JSON.stringify(state, null, 2));
@@ -15875,7 +16145,7 @@ function strings(raw) {
15875
16145
  if (!Array.isArray(raw)) return [];
15876
16146
  return raw.filter((v) => typeof v === "string" && v.length > 0);
15877
16147
  }
15878
- function pick2(src, keys) {
16148
+ function pick3(src, keys) {
15879
16149
  const out = {};
15880
16150
  for (const k of keys) if (src[k] != null) out[k] = src[k];
15881
16151
  return out;
@@ -16227,7 +16497,7 @@ function withManualAreas(history, areas) {
16227
16497
 
16228
16498
  // src/overmind.ts
16229
16499
  var ANOMALY_RESOLVE_BATCH = 50;
16230
- var THIS_FILE = fileURLToPath2(import.meta.url);
16500
+ var THIS_FILE = fileURLToPath3(import.meta.url);
16231
16501
  var executorHasSession = (p) => EXECUTOR.session.exists(p);
16232
16502
  var OVERMIND_ENTRY = THIS_FILE;
16233
16503
  var STALL_MS = Number(process.env.SWARM_STALL_MS) || 10 * 60 * 1e3;
@@ -16890,10 +17160,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
16890
17160
  }
16891
17161
  return parts.join("\n");
16892
17162
  }
16893
- var HERO_CHARTER_PATH = join30(dirname4(THIS_FILE), "..", "docs", "HERO.md");
17163
+ var HERO_CHARTER_PATH = join31(dirname5(THIS_FILE), "..", "docs", "HERO.md");
16894
17164
  function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
16895
17165
  const name = run.name || `#${run.id}`;
16896
- const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
17166
+ const charter = readFileSync21(HERO_CHARTER_PATH, "utf8");
16897
17167
  const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
16898
17168
 
16899
17169
  ${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 +17206,7 @@ function buildResolverPrompt(reviewRun, resolver, conflicts, branch, mainRef) {
16936
17206
  `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
17207
  "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
17208
  "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.`,
17209
+ `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
17210
  `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
17211
  "",
16942
17212
  `\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 +17467,7 @@ async function wakeRun(deps, runId) {
17197
17467
  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
17468
  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
17469
  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;
17470
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
17201
17471
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
17202
17472
  const wt = run.worktree_path;
17203
17473
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
@@ -17303,7 +17573,7 @@ function repoHasCi(deps, run) {
17303
17573
  function repoHasCiWorkflows(worktreePath, repo) {
17304
17574
  for (const base3 of [worktreePath, repo]) {
17305
17575
  if (!base3) continue;
17306
- const dir = join30(base3, ".github", "workflows");
17576
+ const dir = join31(base3, ".github", "workflows");
17307
17577
  try {
17308
17578
  if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
17309
17579
  } catch {
@@ -17489,7 +17759,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
17489
17759
  return true;
17490
17760
  }
17491
17761
  function worktreeAndTranscriptAlive(deps, worktree) {
17492
- const exists = deps.worktreeExists ?? existsSync17;
17762
+ const exists = deps.worktreeExists ?? existsSync18;
17493
17763
  const transcript = deps.hasTranscript ?? executorHasSession;
17494
17764
  return exists(worktree) && transcript(worktree);
17495
17765
  }
@@ -17523,7 +17793,9 @@ async function spawnFreshReviewerAfterStall(deps, run, why) {
17523
17793
  async function wakeAuthorOnChanges(deps, run, now, droneCap) {
17524
17794
  if (!deps.respawn) return false;
17525
17795
  if (run.pid != null && deps.isAlive(run.pid)) return false;
17526
- if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled")) return false;
17796
+ if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled" && !isAwaitingCi(r.status, r.stop_reason))) {
17797
+ return false;
17798
+ }
17527
17799
  if (deps.resourcesOk && !deps.resourcesOk()) return false;
17528
17800
  if (countActiveDrones(deps.db) >= droneCap) return false;
17529
17801
  if (!await projectQuotaGateAllows(deps, run)) return false;
@@ -17638,7 +17910,7 @@ async function envGateAllows(deps, run) {
17638
17910
  return true;
17639
17911
  }
17640
17912
  function effectiveRepo(deps, run) {
17641
- return normalizeRepo(run.repo || deps.repoFallback || "");
17913
+ return effectiveRepoOf(run.repo, deps.repoFallback);
17642
17914
  }
17643
17915
  async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback) {
17644
17916
  const model = await models.forRepo(repo);
@@ -17797,7 +18069,8 @@ async function trySpawnQueuedRun(deps, run) {
17797
18069
  }
17798
18070
  const spentAttempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
17799
18071
  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)`;
18072
+ const last = lastPreStartAttempt(deps.db.listRuns(), run.task_slug);
18073
+ 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
18074
  deps.db.updateRun(run.id, { status: "stopped", stop_reason: why });
17802
18075
  deps.db.addEvent(run.id, "step", why);
17803
18076
  const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), `pre-start-exhausted:${run.task_slug}`, why, GATE_LOG_HEARTBEAT_TICKS);
@@ -17816,7 +18089,8 @@ async function trySpawnQueuedRun(deps, run) {
17816
18089
  const unmet = depVerdicts.filter((d) => !d.met);
17817
18090
  if (unmet.length) {
17818
18091
  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(", ")}`;
18092
+ const blind = deps.db.depsUnconfirmed?.(run.depends_on) ?? [];
18093
+ 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
18094
  return deferSpawn(deps, run, why);
17821
18095
  }
17822
18096
  if (!await envGateAllows(deps, run)) return "skipped";
@@ -17853,10 +18127,10 @@ async function trySpawnQueuedRun(deps, run) {
17853
18127
  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
18128
  return "spawned";
17855
18129
  } catch (again) {
17856
- return await failBeforeStart(deps, run, String(again));
18130
+ return await failBeforeStart(deps, run, String(again), spawnRetryOpts(again));
17857
18131
  }
17858
18132
  }
17859
- return await failBeforeStart(deps, run, String(e));
18133
+ return await failBeforeStart(deps, run, String(e), spawnRetryOpts(e));
17860
18134
  }
17861
18135
  }
17862
18136
  function holderStillHolds(deps, holder, now) {
@@ -17887,15 +18161,19 @@ async function healBusyWorktree(deps, run, e) {
17887
18161
  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
18162
  return true;
17889
18163
  }
17890
- async function failBeforeStart(deps, run, reason) {
18164
+ function spawnRetryOpts(e) {
18165
+ if (!(e instanceof TaskBodyUnavailableError)) return {};
18166
+ return { retriable: TOOL_FAILURE_RETRIABLE[e.code] };
18167
+ }
18168
+ async function failBeforeStart(deps, run, reason, opts = {}) {
17891
18169
  deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
17892
18170
  deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
17893
18171
  const attempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
17894
- if (attempts < PRE_START_ATTEMPT_LIMIT) {
18172
+ if (opts.retriable !== false && attempts < PRE_START_ATTEMPT_LIMIT) {
17895
18173
  narrate(deps, `${droneLabel(run)} \u2014 ${PRE_START_MARK} (\u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempts} \u0438\u0437 ${PRE_START_ATTEMPT_LIMIT}): ${reason}`);
17896
18174
  return "failed";
17897
18175
  }
17898
- const parked = parkReason(attempts, reason);
18176
+ const parked = opts.retriable === false ? parkReasonUnretriable(reason) : parkReason(attempts, reason);
17899
18177
  deps.db.updateRun(run.id, { stop_reason: parked });
17900
18178
  deps.db.addEvent(run.id, "step", parked);
17901
18179
  narrate(deps, `${run.task_slug}: ${parked}`);
@@ -17931,7 +18209,7 @@ async function resumeWatchdogParked(deps, droneCap) {
17931
18209
  }
17932
18210
  if (!candidate) return;
17933
18211
  if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
17934
- const worktreeExists = deps.worktreeExists ?? existsSync17;
18212
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
17935
18213
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
17936
18214
  const wt = candidate.worktree_path;
17937
18215
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
@@ -18100,7 +18378,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
18100
18378
  if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
18101
18379
  if (!deps.respawn) return;
18102
18380
  const wt = resumeCandidate.worktree_path;
18103
- const worktreeExists = deps.worktreeExists ?? existsSync17;
18381
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
18104
18382
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
18105
18383
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
18106
18384
  const mode = canContinue ? "continue" : "fresh";
@@ -18378,7 +18656,7 @@ function buildEscalationCandidates(db) {
18378
18656
  }
18379
18657
  async function tickOnce(deps) {
18380
18658
  const now = deps.now ? deps.now() : Date.now();
18381
- const worktreeExists = deps.worktreeExists ?? existsSync17;
18659
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
18382
18660
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
18383
18661
  deps.sampleHost?.();
18384
18662
  if (deps.rehydrate) {
@@ -18632,7 +18910,7 @@ async function tickOnce(deps) {
18632
18910
  } else if (!st.conflicting && deps.ci && deps.respawn && !stormActive) {
18633
18911
  await handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap);
18634
18912
  }
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)) {
18913
+ if (needsFixIteration(run) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
18636
18914
  continue;
18637
18915
  }
18638
18916
  const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
@@ -19140,7 +19418,7 @@ async function resolveConflict(deps, reviewRun) {
19140
19418
  model: reviewRun.model,
19141
19419
  review_model: reviewRun.review_model
19142
19420
  });
19143
- const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19421
+ const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19144
19422
  try {
19145
19423
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19146
19424
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -19175,6 +19453,28 @@ async function resolveConflict(deps, reviewRun) {
19175
19453
  throw e;
19176
19454
  }
19177
19455
  }
19456
+ function makeCheckerAttachmentsReader(tl) {
19457
+ return async (slug, worktree, workspaceId, onEvent) => {
19458
+ const client = tl.forWorkspace(workspaceId);
19459
+ const read = await client.getTaskDetailed(slug);
19460
+ if (!read.ok) {
19461
+ 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)}`;
19462
+ 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`);
19463
+ return [
19464
+ `\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}.`,
19465
+ "\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.",
19466
+ "\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."
19467
+ ].join("\n");
19468
+ }
19469
+ return prepareTaskAttachments({
19470
+ worktree,
19471
+ refs: read.task.attachments,
19472
+ fetchFile: client.fetchFile.bind(client),
19473
+ // Выложенное и пропущенное — тоже в ленту проверяющего, как у исполнителя (respawnRun).
19474
+ ...onEvent ? { onEvent } : {}
19475
+ });
19476
+ };
19477
+ }
19178
19478
  async function spawnReviewer(deps, reviewRun, files) {
19179
19479
  const repo = normalizeRepo(reviewRun.repo || deps.repoFallback);
19180
19480
  const branch = await worktreeBranchFor(reviewRun, deps.prHeadBranch);
@@ -19197,11 +19497,16 @@ async function spawnReviewer(deps, reviewRun, files) {
19197
19497
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
19198
19498
  workspace_id: reviewRun.workspace_id
19199
19499
  });
19200
- const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19500
+ const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19201
19501
  try {
19202
19502
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19203
19503
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
19204
- const attachments = await deps.taskAttachments?.(reviewRun.task_slug, wt, reviewRun.workspace_id);
19504
+ const attachments = await deps.taskAttachments?.(
19505
+ reviewRun.task_slug,
19506
+ wt,
19507
+ reviewRun.workspace_id,
19508
+ (text) => deps.db.addEvent(reviewer.id, "step", text)
19509
+ );
19205
19510
  const pendingRules = await deps.pendingRepoRules?.(repo, reviewRun.task_slug).catch(() => []) ?? [];
19206
19511
  const prompt = buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments, verifyMode, pendingRules);
19207
19512
  const pid = deps.launch({ worktree: wt, run: reviewer, prompt });
@@ -19273,11 +19578,16 @@ async function spawnVerifier(deps, verifiedRun, files) {
19273
19578
  // штамп цикла: фикс-итерацию проверяет свежий верификатор
19274
19579
  workspace_id: verifiedRun.workspace_id
19275
19580
  });
19276
- const wt = join30(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19581
+ const wt = join31(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19277
19582
  try {
19278
19583
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19279
19584
  await deps.git.setWorktreeIdentity(wt, verifier.name);
19280
- const attachments = await deps.taskAttachments?.(verifiedRun.task_slug, wt, verifiedRun.workspace_id);
19585
+ const attachments = await deps.taskAttachments?.(
19586
+ verifiedRun.task_slug,
19587
+ wt,
19588
+ verifiedRun.workspace_id,
19589
+ (text) => deps.db.addEvent(verifier.id, "step", text)
19590
+ );
19281
19591
  const prompt = buildVerifierPrompt(verifiedRun, verifier, branch, files, attachments, verifyMode);
19282
19592
  const pid = deps.launch({ worktree: wt, run: verifier, prompt });
19283
19593
  deps.db.updateRun(verifier.id, { status: "running", worktree_path: wt, branch, pid, pr_url: verifiedRun.pr_url });
@@ -19314,7 +19624,7 @@ async function respawnRun(deps, run, mode, nudge) {
19314
19624
  ]);
19315
19625
  const base3 = effectiveBaseBranch(run.base_branch, projectBase);
19316
19626
  const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
19317
- if (!existsSync17(wt)) {
19627
+ if (!existsSync18(wt)) {
19318
19628
  const branch = runToSpawn.branch || runToSpawn.task_slug;
19319
19629
  const source = await addWorktree(repo, wt, branch, {
19320
19630
  ...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
@@ -19341,7 +19651,7 @@ async function respawnRun(deps, run, mode, nudge) {
19341
19651
  deps.db.updateRun(run.id, { status: "running", pid });
19342
19652
  }
19343
19653
  async function syncCrashedOnStartup(deps) {
19344
- const worktreeExists = deps.worktreeExists ?? existsSync17;
19654
+ const worktreeExists = deps.worktreeExists ?? existsSync18;
19345
19655
  let orphaned = 0;
19346
19656
  let merged = 0;
19347
19657
  let requeued = 0;
@@ -19404,7 +19714,7 @@ async function makeStore(repo, worktreesDir, repoFlag) {
19404
19714
  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
19715
  }
19406
19716
  }
19407
- const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir5(), repoPaths);
19717
+ const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir6(), repoPaths);
19408
19718
  const repoMap = emptyRepoMapState(scanRootsList);
19409
19719
  if (SWARM_BACKEND !== "client") {
19410
19720
  const { SwarmDb: SwarmDb2 } = await loadSqlite();
@@ -19471,7 +19781,11 @@ async function makeStore(repo, worktreesDir, repoFlag) {
19471
19781
  // TASWM-422: что машина обслуживает ПРЯМО СЕЙЧАС — читается из ЖИВОГО реестра (он
19472
19782
  // пересобирается тиком выше), чтобы причина «клона нет на этой машине» называла
19473
19783
  // актуальный список, а не снимок на старте демона.
19474
- reposServed: () => repoRegistry.map((e) => e.repoFullName)
19784
+ reposServed: () => repoRegistry.map((e) => e.repoFullName),
19785
+ // TASWM-492: обратная сторона того же реестра — путь прогона в owner/repo. Читаем из
19786
+ // живого `repoRegistry` (не из снимка на старте): репозиторий могли клонировать уже
19787
+ // после запуска демона, и гейт мержа не должен считать его прогон «неопознанным».
19788
+ repoFullNameOf: (repoPath) => repoRegistry.find((e) => e.path === repoPath)?.repoFullName ?? null
19475
19789
  };
19476
19790
  }
19477
19791
  var crashGuardsInstalled = false;
@@ -19491,7 +19805,7 @@ async function migrateWorktreesAtStartup() {
19491
19805
  legacyDir: WORKTREES_LEGACY_DIR,
19492
19806
  targetDir: WORKTREES_DIR,
19493
19807
  envOverride: process.env.SWARM_WORKTREES || "",
19494
- exists: (d) => existsSync17(d),
19808
+ exists: (d) => existsSync18(d),
19495
19809
  isNonEmptyDir: (d) => {
19496
19810
  try {
19497
19811
  return readdirSync13(d).length > 0;
@@ -19506,7 +19820,7 @@ async function migrateWorktreesAtStartup() {
19506
19820
  });
19507
19821
  } catch (e) {
19508
19822
  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;
19823
+ return existsSync18(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
19510
19824
  }
19511
19825
  }
19512
19826
  async function reportWorkspaceAccess(tl, db) {
@@ -19547,7 +19861,8 @@ async function runDaemon(opts) {
19547
19861
  repoMap: repoMapState,
19548
19862
  refreshRepoMap: rebuildRepoMap,
19549
19863
  prodMcp,
19550
- reposServed
19864
+ reposServed,
19865
+ repoFullNameOf
19551
19866
  } = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
19552
19867
  await preflightAuth();
19553
19868
  const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
@@ -19869,6 +20184,7 @@ async function runDaemon(opts) {
19869
20184
  ttlMs: 6e4
19870
20185
  });
19871
20186
  }
20187
+ const checkerAttachments = tl?.fetchFile ? makeCheckerAttachmentsReader(tl) : void 0;
19872
20188
  let spawnReviewerFn;
19873
20189
  let verdictCommentFn;
19874
20190
  let reviewCutoffAtValue;
@@ -19892,20 +20208,10 @@ async function runDaemon(opts) {
19892
20208
  ...pendingRepoRules ? { pendingRepoRules } : {},
19893
20209
  // TASWM-283: те же вложения задачи, что у исполнителя — ревьюер без макета
19894
20210
  // не может проверить «сделано ли как просили».
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
- } : {}
20211
+ // TASWM-288: вложения читаем клиентом ВОРКСПЕЙСА проверяемого прогона — иначе
20212
+ // ревьюер задачи из чужого воркспейса остался бы без макетов («сделано ли как
20213
+ // просили» проверять нечем), как и исполнитель до фикса.
20214
+ ...checkerAttachments ? { taskAttachments: checkerAttachments } : {}
19909
20215
  },
19910
20216
  reviewRun,
19911
20217
  files
@@ -19927,17 +20233,7 @@ async function runDaemon(opts) {
19927
20233
  git: { prepareDetachedWorktree, setWorktreeIdentity, removeWorktree },
19928
20234
  prHeadBranch,
19929
20235
  ...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
- } : {}
20236
+ ...checkerAttachments ? { taskAttachments: checkerAttachments } : {}
19941
20237
  },
19942
20238
  verifiedRun,
19943
20239
  files
@@ -19976,6 +20272,13 @@ async function runDaemon(opts) {
19976
20272
  const mergeParts = makeMergeGateParts({
19977
20273
  listRuns: () => db.listRuns(),
19978
20274
  ...autoReviewProject ? { autoReview: autoReviewProject } : {},
20275
+ // TASWM-492: чей это PR. Номер PR у семи репозиториев роя независим, поэтому гейт
20276
+ // сверяет и репозиторий: owner/repo из pr_url, а короткий `#N` — по репозиторию
20277
+ // КОНТЕКСТА ВЫЗОВА (origin каталога, откуда спросили). repoFallback + репо-реестр
20278
+ // нужны для второй стороны сравнения: у прогона в базе лежит локальный путь, и пустой
20279
+ // он означает дефолт роя, а не «отдельный проект».
20280
+ repoFallback: opts.repo,
20281
+ ...repoFullNameOf ? { repoFullNameOf } : {},
19979
20282
  fallbackAutoReview: SWARM_AUTO_REVIEW,
19980
20283
  autoVerify: SWARM_AUTO_VERIFY,
19981
20284
  // TASWM-344: вторая ось приёмки — вердикт верификатора
@@ -20162,7 +20465,7 @@ async function runDaemon(opts) {
20162
20465
  isDirty: (p) => worktreeDirty(p),
20163
20466
  releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
20164
20467
  pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
20165
- worktreeExists: existsSync17,
20468
+ worktreeExists: existsSync18,
20166
20469
  liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
20167
20470
  narrate: narrator.narrate
20168
20471
  });
@@ -20285,7 +20588,7 @@ async function runDaemon(opts) {
20285
20588
  mode,
20286
20589
  nudge
20287
20590
  ),
20288
- worktreeExists: (p) => existsSync17(p),
20591
+ worktreeExists: (p) => existsSync18(p),
20289
20592
  hasTranscript: executorHasSession,
20290
20593
  // CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
20291
20594
  // (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
@@ -20471,7 +20774,7 @@ async function runDaemon(opts) {
20471
20774
  onStep
20472
20775
  });
20473
20776
  }
20474
- const repo = await repoRoot(dirname4(THIS_FILE)).catch(() => null);
20777
+ const repo = await repoRoot(dirname5(THIS_FILE)).catch(() => null);
20475
20778
  if (!repo) {
20476
20779
  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
20780
  }
@@ -20781,6 +21084,7 @@ export {
20781
21084
  lastChangesSummary,
20782
21085
  listDroneProcesses,
20783
21086
  livePpids,
21087
+ makeCheckerAttachmentsReader,
20784
21088
  replayDecision,
20785
21089
  repoHasCiWorkflows,
20786
21090
  resolveConflict,