@taskless-app/swarm 0.6.30 → 0.6.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +82 -17
  2. package/dist/overmind.js +865 -457
  3. package/package.json +2 -2
package/dist/overmind.js CHANGED
@@ -1447,7 +1447,7 @@ function buildZoneModel(entries, opts = {}) {
1447
1447
  if (!m) labelVotes.set(z2, m = /* @__PURE__ */ new Map());
1448
1448
  bump(m, manual);
1449
1449
  }
1450
- const rename = /* @__PURE__ */ new Map();
1450
+ const rename2 = /* @__PURE__ */ new Map();
1451
1451
  const takenNames = /* @__PURE__ */ new Set([SPINE_ZONE]);
1452
1452
  const clustersByEvidence = [...labelVotes.entries()].sort((a, b) => {
1453
1453
  const sum = (m) => [...m.values()].reduce((x, y) => x + y, 0);
@@ -1456,11 +1456,11 @@ function buildZoneModel(entries, opts = {}) {
1456
1456
  for (const [zone, votes] of clustersByEvidence) {
1457
1457
  const top = topKey(votes);
1458
1458
  if (!top || top.share < 0.6 || takenNames.has(top.key) || top.key === zone) continue;
1459
- rename.set(zone, top.key);
1459
+ rename2.set(zone, top.key);
1460
1460
  takenNames.add(top.key);
1461
1461
  }
1462
- const renamed = (z2) => rename.get(z2) ?? z2;
1463
- if (rename.size) {
1462
+ const renamed = (z2) => rename2.get(z2) ?? z2;
1463
+ if (rename2.size) {
1464
1464
  for (const [f, z2] of fileZone) fileZone.set(f, renamed(z2));
1465
1465
  for (const [s, z2] of taskZone) taskZone.set(s, renamed(z2));
1466
1466
  }
@@ -1566,8 +1566,8 @@ function predictFiles(model, opts) {
1566
1566
  }
1567
1567
  const zone = normalizeZoneName(opts.zone) ?? (opts.text ? suggestZonesFromText(model, opts.text)[0]?.zone ?? null : null);
1568
1568
  if (zone) {
1569
- const stat = model.zones.find((z2) => z2.zone === zone);
1570
- if (stat) stat.files.slice(0, limit).forEach((f, i) => put(f, Math.max(0.12, 0.45 - i * 0.02), `\u0437\u043E\u043D\u0430 ${zone}`));
1569
+ const stat2 = model.zones.find((z2) => z2.zone === zone);
1570
+ if (stat2) stat2.files.slice(0, limit).forEach((f, i) => put(f, Math.max(0.12, 0.45 - i * 0.02), `\u0437\u043E\u043D\u0430 ${zone}`));
1571
1571
  }
1572
1572
  return [...best.values()].sort((a, b) => b.weight - a.weight || a.path.localeCompare(b.path)).slice(0, limit);
1573
1573
  }
@@ -2231,10 +2231,10 @@ function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWAR
2231
2231
  const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
2232
2232
  for (const file of ["auth.json", "config.toml"]) {
2233
2233
  const target = join5(real, file);
2234
- const link = join5(home, file);
2235
- if (!existsSync4(target) || existsSync4(link)) continue;
2234
+ const link2 = join5(home, file);
2235
+ if (!existsSync4(target) || existsSync4(link2)) continue;
2236
2236
  try {
2237
- symlinkSync(target, link);
2237
+ symlinkSync(target, link2);
2238
2238
  } catch {
2239
2239
  }
2240
2240
  }
@@ -2433,6 +2433,9 @@ __export(config_exports, {
2433
2433
  DRONE_HOME: () => DRONE_HOME,
2434
2434
  DRONE_HOME_ISOLATION: () => DRONE_HOME_ISOLATION,
2435
2435
  DRONE_ISOLATION: () => DRONE_ISOLATION,
2436
+ ENV_CACHE_DIR: () => ENV_CACHE_DIR,
2437
+ ENV_CACHE_ENABLED: () => ENV_CACHE_ENABLED,
2438
+ ENV_CACHE_KEEP: () => ENV_CACHE_KEEP,
2436
2439
  EXECUTOR: () => EXECUTOR,
2437
2440
  HEARTBEAT_PATH: () => HEARTBEAT_PATH,
2438
2441
  HEARTBEAT_STALE_MS: () => HEARTBEAT_STALE_MS,
@@ -2602,7 +2605,7 @@ function reviewCutoffAt() {
2602
2605
  }
2603
2606
  return at;
2604
2607
  }
2605
- 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;
2608
+ 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, ENV_CACHE_DIR, ENV_CACHE_ENABLED, ENV_CACHE_KEEP, 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;
2606
2609
  var init_config = __esm({
2607
2610
  "src/config.ts"() {
2608
2611
  "use strict";
@@ -2628,6 +2631,9 @@ var init_config = __esm({
2628
2631
  SWARM_OUTBOX = process.env.SWARM_OUTBOX || join6(SWARM_HOME, "outbox.jsonl");
2629
2632
  SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
2630
2633
  SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
2634
+ ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR || join6(SWARM_HOME, "env-cache");
2635
+ ENV_CACHE_ENABLED = process.env.SWARM_ENV_CACHE !== "0";
2636
+ ENV_CACHE_KEEP = Math.trunc(envNum("SWARM_ENV_CACHE_KEEP", 4));
2631
2637
  SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
2632
2638
  SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
2633
2639
  SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
@@ -3327,6 +3333,118 @@ var init_logNoise = __esm({
3327
3333
  }
3328
3334
  });
3329
3335
 
3336
+ // src/netTimeout.ts
3337
+ import { basename as basename2 } from "path";
3338
+ function isTimedOut(outcome) {
3339
+ return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
3340
+ }
3341
+ function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
3342
+ const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
3343
+ if (!isTimedOut(outcome)) {
3344
+ timeoutNoise.forget(noiseKey(scope, call));
3345
+ return false;
3346
+ }
3347
+ const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
3348
+ if (say) warn(say);
3349
+ return true;
3350
+ }
3351
+ function noiseKey(scope, call) {
3352
+ return `timeout:${scope}:${call}`;
3353
+ }
3354
+ function callFailureWord(f) {
3355
+ switch (f.reason) {
3356
+ case "timeout":
3357
+ return "\u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 \u043E\u0442\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F (\u043F\u0440\u0435\u0440\u0432\u0430\u043D)";
3358
+ case "unreadable":
3359
+ return "\u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043D\u043E \u0432\u044B\u0432\u043E\u0434 \u043D\u0435 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u043D";
3360
+ case "crashed":
3361
+ return "\u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F";
3362
+ default:
3363
+ return "\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u044B\u0432\u043E\u0434\u0430";
3364
+ }
3365
+ }
3366
+ function classifyCallFailure(outcome) {
3367
+ if (isTimedOut(outcome)) return { reason: "timeout", detail: firstLineOf(errText(outcome)) };
3368
+ const code = outcome?.code;
3369
+ if (code === "ENOENT") return { reason: "crashed", detail: firstLineOf(errText(outcome)) };
3370
+ return { reason: "empty", detail: firstLineOf(errText(outcome)) };
3371
+ }
3372
+ function errText(outcome) {
3373
+ const o = outcome;
3374
+ for (const v of [o?.stderr, o?.shortMessage, o?.message]) if (typeof v === "string" && v.trim()) return v;
3375
+ return "";
3376
+ }
3377
+ function firstLineOf(s) {
3378
+ return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
3379
+ }
3380
+ function noteCallFailure(scope, call, outcome, warn = console.warn) {
3381
+ const f = classifyCallFailure(outcome);
3382
+ if (f.reason === "timeout") return f;
3383
+ const line = `[${scope}] \u2716 \u0432\u044B\u0437\u043E\u0432 ${callFailureWord(f)}: ${call}${f.detail ? ` \u2014 ${f.detail}` : ""} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
3384
+ const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
3385
+ if (say) warn(say);
3386
+ return f;
3387
+ }
3388
+ function humanMs(ms) {
3389
+ return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
3390
+ }
3391
+ function destroyPipes(child) {
3392
+ for (const pipe of [child.stdout, child.stderr, child.stdin]) {
3393
+ try {
3394
+ pipe?.destroy?.();
3395
+ } catch {
3396
+ }
3397
+ }
3398
+ }
3399
+ async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
3400
+ let timer;
3401
+ const capped = new Promise((_, reject) => {
3402
+ timer = setTimeout(() => {
3403
+ child.kill("SIGKILL");
3404
+ destroyPipes(child);
3405
+ reject(new CallTimedOutError(call, timeoutMs));
3406
+ }, timeoutMs + HARD_CAP_GRACE_MS);
3407
+ timer.unref?.();
3408
+ });
3409
+ try {
3410
+ const res = await Promise.race([child, capped]);
3411
+ noteTimedOut(scope, call, timeoutMs, res, warn);
3412
+ return res;
3413
+ } catch (e) {
3414
+ noteTimedOut(scope, call, timeoutMs, e, warn);
3415
+ throw e;
3416
+ } finally {
3417
+ if (timer) clearTimeout(timer);
3418
+ }
3419
+ }
3420
+ function callLabel(cmd, args, head = 4) {
3421
+ const shown = args.slice(0, head).join(" ");
3422
+ return `${cmd} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
3423
+ }
3424
+ function gitCallLabel(repo, args) {
3425
+ return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
3426
+ }
3427
+ var GH_CALL_TIMEOUT_MS, GIT_NET_TIMEOUT_MS, GIT_LOCAL_TIMEOUT_MS, GIT_HEAVY_NET_TIMEOUT_MS, HARD_CAP_GRACE_MS, CallTimedOutError, timeoutNoise;
3428
+ var init_netTimeout = __esm({
3429
+ "src/netTimeout.ts"() {
3430
+ "use strict";
3431
+ init_logNoise();
3432
+ GH_CALL_TIMEOUT_MS = 15e3;
3433
+ GIT_NET_TIMEOUT_MS = 15e3;
3434
+ GIT_LOCAL_TIMEOUT_MS = 5e3;
3435
+ GIT_HEAVY_NET_TIMEOUT_MS = 6e4;
3436
+ HARD_CAP_GRACE_MS = 2e3;
3437
+ CallTimedOutError = class extends Error {
3438
+ timedOut = true;
3439
+ constructor(call, timeoutMs) {
3440
+ super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
3441
+ this.name = "CallTimedOutError";
3442
+ }
3443
+ };
3444
+ timeoutNoise = makeRepeatFilter();
3445
+ }
3446
+ });
3447
+
3330
3448
  // src/procList.ts
3331
3449
  function parsePsRows(out) {
3332
3450
  const rows = [];
@@ -3645,84 +3763,6 @@ var init_procCleanup = __esm({
3645
3763
  }
3646
3764
  });
3647
3765
 
3648
- // src/netTimeout.ts
3649
- import { basename as basename2 } from "path";
3650
- function isTimedOut(outcome) {
3651
- return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
3652
- }
3653
- function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
3654
- const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
3655
- if (!isTimedOut(outcome)) {
3656
- timeoutNoise.forget(noiseKey(scope, call));
3657
- return false;
3658
- }
3659
- const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
3660
- if (say) warn(say);
3661
- return true;
3662
- }
3663
- function noiseKey(scope, call) {
3664
- return `timeout:${scope}:${call}`;
3665
- }
3666
- function humanMs(ms) {
3667
- return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
3668
- }
3669
- function destroyPipes(child) {
3670
- for (const pipe of [child.stdout, child.stderr, child.stdin]) {
3671
- try {
3672
- pipe?.destroy?.();
3673
- } catch {
3674
- }
3675
- }
3676
- }
3677
- async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
3678
- let timer;
3679
- const capped = new Promise((_, reject) => {
3680
- timer = setTimeout(() => {
3681
- child.kill("SIGKILL");
3682
- destroyPipes(child);
3683
- reject(new CallTimedOutError(call, timeoutMs));
3684
- }, timeoutMs + HARD_CAP_GRACE_MS);
3685
- timer.unref?.();
3686
- });
3687
- try {
3688
- const res = await Promise.race([child, capped]);
3689
- noteTimedOut(scope, call, timeoutMs, res, warn);
3690
- return res;
3691
- } catch (e) {
3692
- noteTimedOut(scope, call, timeoutMs, e, warn);
3693
- throw e;
3694
- } finally {
3695
- if (timer) clearTimeout(timer);
3696
- }
3697
- }
3698
- function callLabel(cmd, args, head = 4) {
3699
- const shown = args.slice(0, head).join(" ");
3700
- return `${cmd} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
3701
- }
3702
- function gitCallLabel(repo, args) {
3703
- return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
3704
- }
3705
- var GH_CALL_TIMEOUT_MS, GIT_NET_TIMEOUT_MS, GIT_LOCAL_TIMEOUT_MS, GIT_HEAVY_NET_TIMEOUT_MS, HARD_CAP_GRACE_MS, CallTimedOutError, timeoutNoise;
3706
- var init_netTimeout = __esm({
3707
- "src/netTimeout.ts"() {
3708
- "use strict";
3709
- init_logNoise();
3710
- GH_CALL_TIMEOUT_MS = 15e3;
3711
- GIT_NET_TIMEOUT_MS = 15e3;
3712
- GIT_LOCAL_TIMEOUT_MS = 5e3;
3713
- GIT_HEAVY_NET_TIMEOUT_MS = 6e4;
3714
- HARD_CAP_GRACE_MS = 2e3;
3715
- CallTimedOutError = class extends Error {
3716
- timedOut = true;
3717
- constructor(call, timeoutMs) {
3718
- super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
3719
- this.name = "CallTimedOutError";
3720
- }
3721
- };
3722
- timeoutNoise = makeRepeatFilter();
3723
- }
3724
- });
3725
-
3726
3766
  // src/git.ts
3727
3767
  import { existsSync as existsSync11, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
3728
3768
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
@@ -4068,12 +4108,12 @@ __export(droneChannel_exports, {
4068
4108
  verifyLocalCliToken: () => verifyLocalCliToken
4069
4109
  });
4070
4110
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
4071
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
4111
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
4072
4112
  import { dirname as dirname4 } from "path";
4073
4113
  import { serve } from "@hono/node-server";
4074
4114
  function loadChannelSecret(path) {
4075
4115
  try {
4076
- const s = readFileSync15(path, "utf8").trim();
4116
+ const s = readFileSync17(path, "utf8").trim();
4077
4117
  if (s.length >= 32) return s;
4078
4118
  } catch {
4079
4119
  }
@@ -4436,8 +4476,8 @@ var init_droneChannel = __esm({
4436
4476
  });
4437
4477
 
4438
4478
  // src/attachments.ts
4439
- import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
4440
- import { join as join21 } from "path";
4479
+ import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
4480
+ import { join as join22 } from "path";
4441
4481
  function attachmentsFromDoc(doc, origin) {
4442
4482
  const out = [];
4443
4483
  const walk2 = (node) => {
@@ -4522,13 +4562,13 @@ async function materializeAttachments(opts) {
4522
4562
  const maxTotal = opts.maxTotalBytes ?? ATTACH_MAX_TOTAL_BYTES;
4523
4563
  const maxFile = opts.maxFileBytes ?? ATTACH_MAX_FILE_BYTES;
4524
4564
  const io = opts.io ?? {
4525
- removeDir: async (dir2) => rm2(dir2, { recursive: true, force: true }),
4526
- mkdir: async (dir2) => void await mkdir2(dir2, { recursive: true }),
4527
- writeFile: async (path, bytes) => writeFile2(path, bytes)
4565
+ removeDir: async (dir2) => rm3(dir2, { recursive: true, force: true }),
4566
+ mkdir: async (dir2) => void await mkdir3(dir2, { recursive: true }),
4567
+ writeFile: async (path, bytes) => writeFile3(path, bytes)
4528
4568
  };
4529
4569
  const result = { files: [], skipped: [] };
4530
4570
  if (opts.refs.length === 0) return result;
4531
- const dir = join21(opts.worktree, ATTACHMENTS_DIR);
4571
+ const dir = join22(opts.worktree, ATTACHMENTS_DIR);
4532
4572
  await io.removeDir(dir);
4533
4573
  let dirReady = false;
4534
4574
  let total = 0;
@@ -4553,7 +4593,7 @@ async function materializeAttachments(opts) {
4553
4593
  try {
4554
4594
  file = await opts.fetchFile(ref.url);
4555
4595
  } catch (e) {
4556
- result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${errText(e)})`);
4596
+ result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${errText2(e)})`);
4557
4597
  continue;
4558
4598
  }
4559
4599
  if (!file) {
@@ -4574,9 +4614,9 @@ async function materializeAttachments(opts) {
4574
4614
  await io.mkdir(dir);
4575
4615
  dirReady = true;
4576
4616
  }
4577
- await io.writeFile(join21(dir, name), file.bytes);
4617
+ await io.writeFile(join22(dir, name), file.bytes);
4578
4618
  } catch (e) {
4579
- 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)})`);
4619
+ result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText2(e)})`);
4580
4620
  continue;
4581
4621
  }
4582
4622
  total += file.bytes.byteLength;
@@ -4630,7 +4670,7 @@ async function prepareTaskAttachments(opts) {
4630
4670
  }
4631
4671
  return renderAttachmentsBlock(result);
4632
4672
  } catch (e) {
4633
- opts.onEvent?.(`\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0432\u044B\u043B\u043E\u0436\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${errText(e)} \u2014 \u0434\u0440\u043E\u043D \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0431\u0435\u0437 \u043D\u0438\u0445`);
4673
+ opts.onEvent?.(`\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0432\u044B\u043B\u043E\u0436\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${errText2(e)} \u2014 \u0434\u0440\u043E\u043D \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0431\u0435\u0437 \u043D\u0438\u0445`);
4634
4674
  return void 0;
4635
4675
  }
4636
4676
  }
@@ -4653,7 +4693,7 @@ function mb(bytes) {
4653
4693
  if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} \u041C\u0411`;
4654
4694
  return `${bytes === 0 ? 0 : Math.max(1, Math.round(bytes / 1024))} \u041A\u0411`;
4655
4695
  }
4656
- function errText(e) {
4696
+ function errText2(e) {
4657
4697
  return e instanceof Error ? e.message : String(e);
4658
4698
  }
4659
4699
  var ATTACHMENTS_DIR, ATTACH_MAX_FILES, ATTACH_MAX_TOTAL_BYTES, ATTACH_MAX_FILE_BYTES, IMAGE_EXT, DOC_EXT, TEXT_EXT, KNOWN_EXT, TRANSLIT, EXT_BY_MIME;
@@ -4727,7 +4767,7 @@ var init_attachments = __esm({
4727
4767
 
4728
4768
  // src/shotUpload.ts
4729
4769
  import { readFile as fsReadFile } from "fs/promises";
4730
- import { basename as basename4, isAbsolute as isAbsolute3, join as join22 } from "path";
4770
+ import { basename as basename4, isAbsolute as isAbsolute3, join as join23 } from "path";
4731
4771
  function shotMimetype(path) {
4732
4772
  const dot = path.lastIndexOf(".");
4733
4773
  if (dot < 0) return null;
@@ -4753,7 +4793,7 @@ async function readShots(paths, opts) {
4753
4793
  }
4754
4794
  let bytes;
4755
4795
  try {
4756
- bytes = await read(isAbsolute3(path) ? path : join22(opts.worktree, path));
4796
+ bytes = await read(isAbsolute3(path) ? path : join23(opts.worktree, path));
4757
4797
  } catch (e) {
4758
4798
  skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
4759
4799
  continue;
@@ -4833,12 +4873,12 @@ var init_shotUpload = __esm({
4833
4873
  });
4834
4874
 
4835
4875
  // src/oauth.ts
4836
- import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
4837
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
4838
- import { join as join23 } from "path";
4876
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
4877
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
4878
+ import { join as join24 } from "path";
4839
4879
  function makePkce() {
4840
4880
  const verifier = randomBytes2(48).toString("base64url");
4841
- const challenge = createHash2("sha256").update(verifier).digest("base64url");
4881
+ const challenge = createHash3("sha256").update(verifier).digest("base64url");
4842
4882
  return { verifier, challenge };
4843
4883
  }
4844
4884
  async function registerClient(args, fetchFn = fetch) {
@@ -4919,7 +4959,7 @@ function toStore(clientId, t, now) {
4919
4959
  }
4920
4960
  function loadStore() {
4921
4961
  try {
4922
- return JSON.parse(readFileSync16(STORE_PATH2, "utf8"));
4962
+ return JSON.parse(readFileSync18(STORE_PATH2, "utf8"));
4923
4963
  } catch {
4924
4964
  return null;
4925
4965
  }
@@ -4935,7 +4975,7 @@ function clearStore() {
4935
4975
  }
4936
4976
  function loadClientId() {
4937
4977
  try {
4938
- return JSON.parse(readFileSync16(CLIENT_PATH, "utf8")).clientId;
4978
+ return JSON.parse(readFileSync18(CLIENT_PATH, "utf8")).clientId;
4939
4979
  } catch {
4940
4980
  return null;
4941
4981
  }
@@ -4970,8 +5010,8 @@ var init_oauth = __esm({
4970
5010
  AUTHORIZE = "/api/oauth/authorize";
4971
5011
  TOKEN = "/api/oauth/token";
4972
5012
  SCOPE = "openid profile email taskless:full";
4973
- STORE_PATH2 = join23(SWARM_HOME, "oauth.json");
4974
- CLIENT_PATH = join23(SWARM_HOME, "oauth-client.json");
5013
+ STORE_PATH2 = join24(SWARM_HOME, "oauth.json");
5014
+ CLIENT_PATH = join24(SWARM_HOME, "oauth-client.json");
4975
5015
  base2 = (api) => api.replace(/\/$/, "");
4976
5016
  }
4977
5017
  });
@@ -5017,11 +5057,11 @@ var init_authState = __esm({
5017
5057
  });
5018
5058
 
5019
5059
  // src/workspace.ts
5020
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
5021
- import { join as join24 } from "path";
5060
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
5061
+ import { join as join25 } from "path";
5022
5062
  function loadWorkspace() {
5023
5063
  try {
5024
- const w = JSON.parse(readFileSync17(WORKSPACE_PATH, "utf8"));
5064
+ const w = JSON.parse(readFileSync19(WORKSPACE_PATH, "utf8"));
5025
5065
  return w && typeof w.id === "string" && w.id ? w : null;
5026
5066
  } catch {
5027
5067
  return null;
@@ -5035,7 +5075,7 @@ var init_workspace = __esm({
5035
5075
  "src/workspace.ts"() {
5036
5076
  "use strict";
5037
5077
  init_config();
5038
- WORKSPACE_PATH = join24(SWARM_HOME, "workspace.json");
5078
+ WORKSPACE_PATH = join25(SWARM_HOME, "workspace.json");
5039
5079
  }
5040
5080
  });
5041
5081
 
@@ -5616,7 +5656,7 @@ __export(db_exports, {
5616
5656
  });
5617
5657
  import { createRequire as createRequire2 } from "module";
5618
5658
  import { randomUUID as randomUUID3 } from "crypto";
5619
- import { existsSync as existsSync13 } from "fs";
5659
+ import { existsSync as existsSync14 } from "fs";
5620
5660
  import { resolve as resolve3 } from "path";
5621
5661
  function rowToQuestion(row) {
5622
5662
  return {
@@ -6239,7 +6279,7 @@ CREATE TABLE IF NOT EXISTS imports (
6239
6279
  const repoN = normalizeRepo(repo);
6240
6280
  const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
6241
6281
  if (already) return { imported: false, runs: already.run_count };
6242
- if (!existsSync13(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
6282
+ if (!existsSync14(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
6243
6283
  const src = new this.databaseCtor(abs);
6244
6284
  try {
6245
6285
  const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
@@ -6608,10 +6648,10 @@ var init_droneStore = __esm({
6608
6648
 
6609
6649
  // src/overmind.ts
6610
6650
  import { homedir as homedir6 } from "os";
6611
- import { existsSync as existsSync18, readFileSync as readFileSync21, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6612
- import { basename as basename6, dirname as dirname5, join as join31 } from "path";
6651
+ import { existsSync as existsSync19, readFileSync as readFileSync23, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6652
+ import { basename as basename6, dirname as dirname5, join as join32 } from "path";
6613
6653
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
6614
- import { spawn as spawn3 } from "child_process";
6654
+ import { spawn as spawn4 } from "child_process";
6615
6655
 
6616
6656
  // src/restart.ts
6617
6657
  init_src();
@@ -6654,11 +6694,11 @@ function savePauseState(home, state, io = {}) {
6654
6694
  remove(path);
6655
6695
  return;
6656
6696
  }
6657
- const writeFile3 = io.writeFile ?? ((p, data) => {
6697
+ const writeFile4 = io.writeFile ?? ((p, data) => {
6658
6698
  writeFileSync(`${p}.tmp`, data);
6659
6699
  renameSync(`${p}.tmp`, p);
6660
6700
  });
6661
- writeFile3(path, JSON.stringify(state, null, 2));
6701
+ writeFile4(path, JSON.stringify(state, null, 2));
6662
6702
  }
6663
6703
  function makePauseState(source, reason, now = Date.now()) {
6664
6704
  return {
@@ -9415,6 +9455,26 @@ function rotateDaemonLog(path, opts, io = realIo) {
9415
9455
  return "rotated";
9416
9456
  }
9417
9457
 
9458
+ // src/daemonLaunch.ts
9459
+ import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync8 } from "fs";
9460
+ import { spawn } from "child_process";
9461
+ var realRespawnIo = {
9462
+ isTty: () => process.stdout.isTTY === true,
9463
+ open: (path) => openSync2(path, "a")
9464
+ };
9465
+ function respawnStdio(logPath, io = realRespawnIo) {
9466
+ if (io.isTty()) return "inherit";
9467
+ try {
9468
+ const fd = io.open(logPath);
9469
+ return ["ignore", fd, fd];
9470
+ } catch {
9471
+ return "inherit";
9472
+ }
9473
+ }
9474
+
9475
+ // src/overmind.ts
9476
+ init_netTimeout();
9477
+
9418
9478
  // src/logStamp.ts
9419
9479
  import { format } from "util";
9420
9480
  function logStamp(at) {
@@ -9496,13 +9556,13 @@ async function buildRepoRegistry(paths) {
9496
9556
  }
9497
9557
 
9498
9558
  // src/repoMemory.ts
9499
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
9559
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
9500
9560
  import { join as join10 } from "path";
9501
9561
  var FILE = "repos.json";
9502
9562
  function loadRememberedRepos(home) {
9503
9563
  let raw;
9504
9564
  try {
9505
- raw = readFileSync8(join10(home, FILE), "utf8");
9565
+ raw = readFileSync9(join10(home, FILE), "utf8");
9506
9566
  } catch {
9507
9567
  return [];
9508
9568
  }
@@ -9532,11 +9592,11 @@ init_src();
9532
9592
  init_config();
9533
9593
  init_linkFailure();
9534
9594
  import { homedir as homedir4 } from "os";
9535
- import { readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
9595
+ import { readFileSync as readFileSync11, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
9536
9596
  import { isAbsolute, join as join12, resolve as resolve2 } from "path";
9537
9597
 
9538
9598
  // src/repoScan.ts
9539
- import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
9599
+ import { readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
9540
9600
  import { join as join11 } from "path";
9541
9601
  var SKIP = /* @__PURE__ */ new Set([
9542
9602
  "node_modules",
@@ -9567,7 +9627,7 @@ function parseOriginFromGitConfig(text) {
9567
9627
  }
9568
9628
  function originOf(dir) {
9569
9629
  try {
9570
- return parseOriginFromGitConfig(readFileSync9(join11(dir, ".git", "config"), "utf8"));
9630
+ return parseOriginFromGitConfig(readFileSync10(join11(dir, ".git", "config"), "utf8"));
9571
9631
  } catch {
9572
9632
  return null;
9573
9633
  }
@@ -9852,7 +9912,7 @@ function repoMapRows(state) {
9852
9912
  }
9853
9913
  function loadRepoMapCache(path) {
9854
9914
  try {
9855
- const raw = JSON.parse(readFileSync10(path, "utf8"));
9915
+ const raw = JSON.parse(readFileSync11(path, "utf8"));
9856
9916
  if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
9857
9917
  return {
9858
9918
  scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
@@ -9875,7 +9935,7 @@ function writeRepoMapCache(path, entries, scannedAt = stamp()) {
9875
9935
 
9876
9936
  // src/hostIdentity.ts
9877
9937
  import { hostname } from "os";
9878
- import { existsSync as existsSync9, readFileSync as readFileSync11, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
9938
+ import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
9879
9939
  import { join as join13 } from "path";
9880
9940
  import { randomUUID as randomUUID2 } from "crypto";
9881
9941
  function hostIdentityPath(home) {
@@ -9883,8 +9943,8 @@ function hostIdentityPath(home) {
9883
9943
  }
9884
9944
  function loadHostIdentity(deps) {
9885
9945
  const exists = deps.exists ?? existsSync9;
9886
- const readFile2 = deps.readFile ?? ((p) => readFileSync11(p, "utf8"));
9887
- const writeFile3 = deps.writeFile ?? ((p, data) => {
9946
+ const readFile2 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
9947
+ const writeFile4 = deps.writeFile ?? ((p, data) => {
9888
9948
  writeFileSync10(`${p}.tmp`, data);
9889
9949
  renameSync7(`${p}.tmp`, p);
9890
9950
  });
@@ -9904,7 +9964,7 @@ function loadHostIdentity(deps) {
9904
9964
  }
9905
9965
  const hostId = newId();
9906
9966
  try {
9907
- writeFile3(path, JSON.stringify({ hostId, hostName, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
9967
+ writeFile4(path, JSON.stringify({ hostId, hostName, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
9908
9968
  } catch {
9909
9969
  console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C ${path} \u2014 hostId \u0436\u0438\u0432\u0451\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043E \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430`);
9910
9970
  }
@@ -10067,8 +10127,8 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
10067
10127
  }
10068
10128
 
10069
10129
  // src/launcher.ts
10070
- import { spawn } from "child_process";
10071
- import { closeSync as closeSync2, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
10130
+ import { spawn as spawn2 } from "child_process";
10131
+ import { closeSync as closeSync3, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
10072
10132
  import { createRequire } from "module";
10073
10133
  import { homedir as homedir5 } from "os";
10074
10134
  import { dirname as dirname3, join as join15 } from "path";
@@ -10107,9 +10167,9 @@ function buildDroneHome(opts) {
10107
10167
  }
10108
10168
  }
10109
10169
  for (const name of wanted) {
10110
- const link = join14(droneHome, name);
10170
+ const link2 = join14(droneHome, name);
10111
10171
  try {
10112
- lstatSync(link);
10172
+ lstatSync(link2);
10113
10173
  continue;
10114
10174
  } catch {
10115
10175
  }
@@ -10121,7 +10181,7 @@ function buildDroneHome(opts) {
10121
10181
  continue;
10122
10182
  }
10123
10183
  try {
10124
- symlinkSync2(target, link, isDir ? "junction" : "file");
10184
+ symlinkSync2(target, link2, isDir ? "junction" : "file");
10125
10185
  } catch (e) {
10126
10186
  if (e.code !== "EEXIST") throw e;
10127
10187
  }
@@ -10303,14 +10363,14 @@ function openNextRunLog(runLogsDir, runLogKeys) {
10303
10363
  const path = nextRunLogPath(runLogsDir, runLogKeys);
10304
10364
  let out;
10305
10365
  try {
10306
- out = openSync2(path, "wx");
10366
+ out = openSync3(path, "wx");
10307
10367
  } catch (e) {
10308
10368
  if (e.code !== "EEXIST") throw e;
10309
10369
  continue;
10310
10370
  }
10311
10371
  let err = null;
10312
10372
  try {
10313
- err = openSync2(`${path.slice(0, -".json".length)}.err.log`, "w");
10373
+ err = openSync3(`${path.slice(0, -".json".length)}.err.log`, "w");
10314
10374
  } catch {
10315
10375
  err = null;
10316
10376
  }
@@ -10419,7 +10479,7 @@ function makeLauncher(cfg) {
10419
10479
  errFd = fds.err ?? void 0;
10420
10480
  stdio = ["ignore", outFd, errFd ?? "ignore"];
10421
10481
  }
10422
- const child = spawn(cfg.bin ?? plan.bin, plan.args, {
10482
+ const child = spawn2(cfg.bin ?? plan.bin, plan.args, {
10423
10483
  cwd: worktree,
10424
10484
  detached: true,
10425
10485
  stdio,
@@ -10430,8 +10490,8 @@ function makeLauncher(cfg) {
10430
10490
  // они про его собственное состояние, и перебивать их ройным нечем.
10431
10491
  env: { ...droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null), ...plan.env }
10432
10492
  });
10433
- if (outFd !== void 0) closeSync2(outFd);
10434
- if (errFd !== void 0) closeSync2(errFd);
10493
+ if (outFd !== void 0) closeSync3(outFd);
10494
+ if (errFd !== void 0) closeSync3(errFd);
10435
10495
  child.unref();
10436
10496
  if (child.pid == null) throw new Error("\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430 (\u043D\u0435\u0442 pid)");
10437
10497
  return child.pid;
@@ -10442,7 +10502,7 @@ function makeLauncher(cfg) {
10442
10502
  init_procCleanup();
10443
10503
 
10444
10504
  // src/cost.ts
10445
- import { readFileSync as readFileSync12, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10505
+ import { readFileSync as readFileSync13, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
10446
10506
  import { join as join16 } from "path";
10447
10507
  init_executors();
10448
10508
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
@@ -10468,7 +10528,7 @@ function accountRunCost(deps, run) {
10468
10528
  const full = join16(dir, f);
10469
10529
  let raw = null;
10470
10530
  try {
10471
- raw = readFileSync12(full, "utf8");
10531
+ raw = readFileSync13(full, "utf8");
10472
10532
  } catch {
10473
10533
  continue;
10474
10534
  }
@@ -10517,7 +10577,7 @@ function makeCostSweeper(deps) {
10517
10577
  }
10518
10578
 
10519
10579
  // src/runOutcome.ts
10520
- import { readdirSync as readdirSync10, readFileSync as readFileSync13 } from "fs";
10580
+ import { readdirSync as readdirSync10, readFileSync as readFileSync14 } from "fs";
10521
10581
  import { join as join17 } from "path";
10522
10582
  init_executors();
10523
10583
  function endedItself(outcome) {
@@ -10545,7 +10605,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
10545
10605
  }
10546
10606
  if (!latest) return null;
10547
10607
  try {
10548
- return readFileSync13(join17(dir, latest.name), "utf8");
10608
+ return readFileSync14(join17(dir, latest.name), "utf8");
10549
10609
  } catch {
10550
10610
  return null;
10551
10611
  }
@@ -10578,13 +10638,13 @@ init_src();
10578
10638
  init_git();
10579
10639
 
10580
10640
  // src/deps.ts
10581
- import { existsSync as existsSync12, readdirSync as readdirSync12, readFileSync as readFileSync14 } from "fs";
10582
- import { join as join20 } from "path";
10641
+ import { existsSync as existsSync13, readdirSync as readdirSync12, readFileSync as readFileSync16 } from "fs";
10642
+ import { join as join21 } from "path";
10583
10643
  import { execa } from "execa";
10584
10644
 
10585
10645
  // src/prepareLock.ts
10586
10646
  import { createHash } from "crypto";
10587
- import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
10647
+ import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
10588
10648
  import { join as join19 } from "path";
10589
10649
  import { tmpdir } from "os";
10590
10650
  var WAIT_TIMEOUT_MS = 5 * 6e4;
@@ -10595,11 +10655,11 @@ function machineLockPath(key, dir = tmpdir()) {
10595
10655
  }
10596
10656
  function defaultTryCreate(path, content) {
10597
10657
  try {
10598
- const fd = openSync3(path, "wx");
10658
+ const fd = openSync4(path, "wx");
10599
10659
  try {
10600
10660
  writeSync(fd, content);
10601
10661
  } finally {
10602
- closeSync3(fd);
10662
+ closeSync4(fd);
10603
10663
  }
10604
10664
  return true;
10605
10665
  } catch {
@@ -10645,6 +10705,153 @@ async function withMachineLock(key, fn, io = {}) {
10645
10705
  }
10646
10706
  }
10647
10707
 
10708
+ // src/deps.ts
10709
+ init_config();
10710
+
10711
+ // src/envCache.ts
10712
+ init_config();
10713
+ import { createHash as createHash2 } from "crypto";
10714
+ import { cp, link, lstat, mkdir as mkdir2, readdir, readlink, rename, rm as rm2, stat, symlink, utimes, writeFile as writeFile2 } from "fs/promises";
10715
+ import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
10716
+ import { join as join20 } from "path";
10717
+ function fingerprint(parts) {
10718
+ const h = createHash2("sha256");
10719
+ for (const p of parts) {
10720
+ h.update(p);
10721
+ h.update("\0");
10722
+ }
10723
+ return h.digest("hex").slice(0, 32);
10724
+ }
10725
+ async function cloneTree(src, dst) {
10726
+ await mkdir2(dst, { recursive: true });
10727
+ const entries = await readdir(src, { withFileTypes: true });
10728
+ await Promise.all(
10729
+ entries.map(async (e) => {
10730
+ const from = join20(src, e.name);
10731
+ const to = join20(dst, e.name);
10732
+ if (e.isDirectory()) return cloneTree(from, to);
10733
+ if (e.isSymbolicLink()) {
10734
+ return symlink(await readlink(from), to).catch(() => cp(from, to, { recursive: true, verbatimSymlinks: true }));
10735
+ }
10736
+ return link(from, to).catch(() => cp(from, to));
10737
+ })
10738
+ );
10739
+ }
10740
+ function cacheRoot(io) {
10741
+ return io.root ?? ENV_CACHE_DIR;
10742
+ }
10743
+ function snapshotDir(io, key) {
10744
+ return join20(cacheRoot(io), key);
10745
+ }
10746
+ function readManifest(dir) {
10747
+ try {
10748
+ const parsed = JSON.parse(readFileSync15(join20(dir, "manifest.json"), "utf8"));
10749
+ if (!parsed || typeof parsed !== "object") return null;
10750
+ const m = parsed;
10751
+ if (typeof m.key !== "string" || !Array.isArray(m.dirs)) return null;
10752
+ const dirs = m.dirs.filter((d) => typeof d === "string" && d.length > 0 && !d.startsWith("/") && !d.includes(".."));
10753
+ if (!dirs.length) return null;
10754
+ return { key: m.key, dirs, createdAt: typeof m.createdAt === "string" ? m.createdAt : "" };
10755
+ } catch {
10756
+ return null;
10757
+ }
10758
+ }
10759
+ function hasEnvSnapshot(key, io = {}) {
10760
+ const exists = io.exists ?? existsSync12;
10761
+ return exists(join20(snapshotDir(io, key), "manifest.json"));
10762
+ }
10763
+ async function restoreEnvSnapshot(worktree, key, io = {}) {
10764
+ const exists = io.exists ?? existsSync12;
10765
+ const clone = io.clone ?? cloneTree;
10766
+ const dir = snapshotDir(io, key);
10767
+ const manifest = readManifest(dir);
10768
+ if (!manifest) return null;
10769
+ const made = [];
10770
+ try {
10771
+ for (let i = 0; i < manifest.dirs.length; i++) {
10772
+ const rel = manifest.dirs[i] ?? "";
10773
+ const target = join20(worktree, rel);
10774
+ if (exists(target)) continue;
10775
+ await clone(join20(dir, "trees", String(i)), target);
10776
+ made.push(rel);
10777
+ }
10778
+ } catch {
10779
+ for (const rel of made) await rm2(join20(worktree, rel), { recursive: true, force: true }).catch(() => {
10780
+ });
10781
+ return null;
10782
+ }
10783
+ await touch(dir);
10784
+ return made;
10785
+ }
10786
+ async function touch(dir) {
10787
+ const now = /* @__PURE__ */ new Date();
10788
+ await utimes(dir, now, now).catch(() => {
10789
+ });
10790
+ }
10791
+ async function saveEnvSnapshot(worktree, key, dirs, io = {}) {
10792
+ const exists = io.exists ?? existsSync12;
10793
+ const clone = io.clone ?? cloneTree;
10794
+ if (hasEnvSnapshot(key, io)) return true;
10795
+ const present = dirs.filter((rel) => exists(join20(worktree, rel)));
10796
+ if (!present.length) return false;
10797
+ const root = cacheRoot(io);
10798
+ const tmp = join20(root, `.tmp-${key}-${process.pid}`);
10799
+ try {
10800
+ await rm2(tmp, { recursive: true, force: true });
10801
+ await mkdir2(join20(tmp, "trees"), { recursive: true });
10802
+ for (let i = 0; i < present.length; i++) await clone(join20(worktree, present[i] ?? ""), join20(tmp, "trees", String(i)));
10803
+ const manifest = { key, dirs: present, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
10804
+ await writeFile2(join20(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
10805
+ await rename(tmp, snapshotDir(io, key));
10806
+ return true;
10807
+ } catch {
10808
+ await rm2(tmp, { recursive: true, force: true }).catch(() => {
10809
+ });
10810
+ return hasEnvSnapshot(key, io);
10811
+ }
10812
+ }
10813
+ async function pruneEnvSnapshots(io = {}) {
10814
+ const root = cacheRoot(io);
10815
+ const keep = io.keep ?? ENV_CACHE_KEEP;
10816
+ let names;
10817
+ try {
10818
+ names = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
10819
+ } catch {
10820
+ return 0;
10821
+ }
10822
+ const withTime = [];
10823
+ for (const name of names) {
10824
+ const s = await stat(join20(root, name)).catch(() => null);
10825
+ if (s) withTime.push({ name, mtime: s.mtimeMs });
10826
+ }
10827
+ withTime.sort((a, b) => b.mtime - a.mtime);
10828
+ let removed = 0;
10829
+ for (const { name } of withTime.slice(Math.max(0, keep))) {
10830
+ await rm2(join20(root, name), { recursive: true, force: true }).catch(() => {
10831
+ });
10832
+ removed++;
10833
+ }
10834
+ return removed;
10835
+ }
10836
+ async function sweepEnvCacheTemp(io = {}) {
10837
+ const root = cacheRoot(io);
10838
+ let names;
10839
+ try {
10840
+ names = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory() && e.name.startsWith(".tmp-")).map((e) => e.name);
10841
+ } catch {
10842
+ return 0;
10843
+ }
10844
+ let removed = 0;
10845
+ for (const name of names) {
10846
+ const s = await lstat(join20(root, name)).catch(() => null);
10847
+ if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
10848
+ await rm2(join20(root, name), { recursive: true, force: true }).catch(() => {
10849
+ });
10850
+ removed++;
10851
+ }
10852
+ return removed;
10853
+ }
10854
+
10648
10855
  // src/deps.ts
10649
10856
  var PREPARE_TIMEOUT_MS = 15 * 6e4;
10650
10857
  var MAX_PLAN_COMMANDS = 12;
@@ -10665,7 +10872,7 @@ async function runCommand(cmd, worktree) {
10665
10872
  }
10666
10873
  function safeReadText(p) {
10667
10874
  try {
10668
- return readFileSync14(p, "utf8");
10875
+ return readFileSync16(p, "utf8");
10669
10876
  } catch {
10670
10877
  return null;
10671
10878
  }
@@ -10705,21 +10912,21 @@ function overridePlan(override) {
10705
10912
  };
10706
10913
  }
10707
10914
  function detectPackageManager(worktree, io = {}) {
10708
- const exists = io.exists ?? existsSync12;
10915
+ const exists = io.exists ?? existsSync13;
10709
10916
  const readText = io.readText ?? safeReadText;
10710
- const hasPkgJson = exists(join20(worktree, "package.json"));
10917
+ const hasPkgJson = exists(join21(worktree, "package.json"));
10711
10918
  if (!hasPkgJson) {
10712
- if (exists(join20(worktree, "deno.json")) || exists(join20(worktree, "deno.jsonc")) || exists(join20(worktree, "deno.lock"))) return "deno";
10919
+ if (exists(join21(worktree, "deno.json")) || exists(join21(worktree, "deno.jsonc")) || exists(join21(worktree, "deno.lock"))) return "deno";
10713
10920
  return null;
10714
10921
  }
10715
- const pkg = readJson(readText, join20(worktree, "package.json"));
10922
+ const pkg = readJson(readText, join21(worktree, "package.json"));
10716
10923
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
10717
10924
  if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
10718
- if (exists(join20(worktree, "bun.lock")) || exists(join20(worktree, "bun.lockb"))) return "bun";
10719
- if (exists(join20(worktree, "pnpm-lock.yaml"))) return "pnpm";
10720
- if (exists(join20(worktree, "yarn.lock"))) return "yarn";
10721
- if (exists(join20(worktree, "package-lock.json"))) return "npm";
10722
- if (exists(join20(worktree, "deno.lock"))) return "deno";
10925
+ if (exists(join21(worktree, "bun.lock")) || exists(join21(worktree, "bun.lockb"))) return "bun";
10926
+ if (exists(join21(worktree, "pnpm-lock.yaml"))) return "pnpm";
10927
+ if (exists(join21(worktree, "yarn.lock"))) return "yarn";
10928
+ if (exists(join21(worktree, "package-lock.json"))) return "npm";
10929
+ if (exists(join21(worktree, "deno.lock"))) return "deno";
10723
10930
  return "npm";
10724
10931
  }
10725
10932
  function jsInstallCommand(pm) {
@@ -10775,7 +10982,7 @@ var ECOSYSTEM_MARKERS = [
10775
10982
  // конфигурация — дело сборки, не подготовки
10776
10983
  ];
10777
10984
  function detectPlan(worktree, io) {
10778
- const exists = io.exists ?? existsSync12;
10985
+ const exists = io.exists ?? existsSync13;
10779
10986
  const listFiles = io.listFiles ?? safeListFiles;
10780
10987
  const pm = detectPackageManager(worktree, io);
10781
10988
  if (pm) {
@@ -10783,7 +10990,7 @@ function detectPlan(worktree, io) {
10783
10990
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
10784
10991
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
10785
10992
  // заново, но он дешёвый: тянет из того же общего кэша.
10786
- { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join20(worktree, "node_modules"), source: "detect" }
10993
+ { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join21(worktree, "node_modules"), source: "detect" }
10787
10994
  ];
10788
10995
  for (const target of workspaceBuildTargets(worktree, io)) {
10789
10996
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -10792,12 +10999,12 @@ function detectPlan(worktree, io) {
10792
10999
  return { source: "detect", ecosystem: `node/${pm}`, commands };
10793
11000
  }
10794
11001
  for (const marker of ECOSYSTEM_MARKERS) {
10795
- const hit = marker.files.some((f) => exists(join20(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
11002
+ const hit = marker.files.some((f) => exists(join21(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
10796
11003
  if (!hit) continue;
10797
11004
  let ecosystem = marker.ecosystem;
10798
11005
  let command = marker.command;
10799
11006
  for (const refine of marker.refine ?? []) {
10800
- if (!exists(join20(worktree, refine.file))) continue;
11007
+ if (!exists(join21(worktree, refine.file))) continue;
10801
11008
  ecosystem = refine.ecosystem;
10802
11009
  command = refine.command;
10803
11010
  break;
@@ -10854,12 +11061,12 @@ var CI_INSTALL_PATTERNS = [
10854
11061
  function ciWorkflowPlan(worktree, io) {
10855
11062
  const readText = io.readText ?? safeReadText;
10856
11063
  const listFiles = io.listFiles ?? safeListFiles;
10857
- const dir = join20(worktree, ".github", "workflows");
11064
+ const dir = join21(worktree, ".github", "workflows");
10858
11065
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
10859
11066
  const seen = /* @__PURE__ */ new Set();
10860
11067
  const commands = [];
10861
11068
  for (const name of files.sort()) {
10862
- const yaml = readText(join20(dir, name));
11069
+ const yaml = readText(join21(dir, name));
10863
11070
  if (yaml === null) continue;
10864
11071
  for (const raw of workflowRunLines(yaml)) {
10865
11072
  const line = raw.replace(/\s+/g, " ").trim();
@@ -10874,7 +11081,7 @@ function ciWorkflowPlan(worktree, io) {
10874
11081
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
10875
11082
  }
10876
11083
  function workspaceGlobs(worktree, readText) {
10877
- const yaml = readText(join20(worktree, "pnpm-workspace.yaml"));
11084
+ const yaml = readText(join21(worktree, "pnpm-workspace.yaml"));
10878
11085
  if (yaml !== null) {
10879
11086
  const globs = [];
10880
11087
  let inPackages = false;
@@ -10894,7 +11101,7 @@ function workspaceGlobs(worktree, readText) {
10894
11101
  }
10895
11102
  if (globs.length) return globs;
10896
11103
  }
10897
- const pkg = readJson(readText, join20(worktree, "package.json"));
11104
+ const pkg = readJson(readText, join21(worktree, "package.json"));
10898
11105
  const ws = pkg?.workspaces;
10899
11106
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
10900
11107
  return list.filter((g) => typeof g === "string");
@@ -10905,14 +11112,14 @@ function workspaceDirs(worktree, globs, io) {
10905
11112
  if (glob.startsWith("!")) continue;
10906
11113
  const star = glob.indexOf("*");
10907
11114
  if (star === -1) {
10908
- const dir = join20(worktree, glob);
10909
- if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
11115
+ const dir = join21(worktree, glob);
11116
+ if (io.exists(join21(dir, "package.json"))) dirs.add(dir);
10910
11117
  continue;
10911
11118
  }
10912
- const base3 = join20(worktree, glob.slice(0, star).replace(/\/$/, ""));
11119
+ const base3 = join21(worktree, glob.slice(0, star).replace(/\/$/, ""));
10913
11120
  for (const name of io.listDirs(base3)) {
10914
- const dir = join20(base3, name);
10915
- if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
11121
+ const dir = join21(base3, name);
11122
+ if (io.exists(join21(dir, "package.json"))) dirs.add(dir);
10916
11123
  }
10917
11124
  }
10918
11125
  return [...dirs];
@@ -10933,18 +11140,18 @@ function entryPaths(pkg) {
10933
11140
  return out;
10934
11141
  }
10935
11142
  function workspaceBuildTargets(worktree, io = {}) {
10936
- const exists = io.exists ?? existsSync12;
11143
+ const exists = io.exists ?? existsSync13;
10937
11144
  const readText = io.readText ?? safeReadText;
10938
11145
  const listDirs = io.listDirs ?? safeListDirs;
10939
11146
  const globs = workspaceGlobs(worktree, readText);
10940
11147
  if (!globs.length) return [];
10941
11148
  const pkgs = [];
10942
11149
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
10943
- const pkg = readJson(readText, join20(dir, "package.json"));
11150
+ const pkg = readJson(readText, join21(dir, "package.json"));
10944
11151
  if (!pkg || typeof pkg.name !== "string") continue;
10945
11152
  const scripts = pkg.scripts ?? {};
10946
11153
  const entries = entryPaths(pkg);
10947
- const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join20(dir, e)));
11154
+ const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join21(dir, e)));
10948
11155
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
10949
11156
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
10950
11157
  }
@@ -10972,7 +11179,7 @@ function devDatabaseUrl(worktree, io = {}) {
10972
11179
  const fromEnv = (io.env ?? process.env).DATABASE_URL;
10973
11180
  if (fromEnv && fromEnv.trim()) return fromEnv.trim();
10974
11181
  for (const file of [".env", ".env.example"]) {
10975
- const raw = readText(join20(worktree, file));
11182
+ const raw = readText(join21(worktree, file));
10976
11183
  if (raw === null) continue;
10977
11184
  const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
10978
11185
  const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
@@ -10988,11 +11195,11 @@ function isLocalDatabase(url) {
10988
11195
  }
10989
11196
  }
10990
11197
  function prismaPackages(worktree, io) {
10991
- const exists = io.exists ?? existsSync12;
11198
+ const exists = io.exists ?? existsSync13;
10992
11199
  const readText = io.readText ?? safeReadText;
10993
11200
  const listDirs = io.listDirs ?? safeListDirs;
10994
11201
  const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
10995
- return dirs.filter((dir) => exists(join20(dir, "prisma", "schema.prisma")) && listDirs(join20(dir, "prisma", "migrations")).length > 0);
11202
+ return dirs.filter((dir) => exists(join21(dir, "prisma", "schema.prisma")) && listDirs(join21(dir, "prisma", "migrations")).length > 0);
10996
11203
  }
10997
11204
  function jsExecBin(pm, command) {
10998
11205
  switch (pm) {
@@ -11019,26 +11226,59 @@ function migrateCommands(worktree, pm, io) {
11019
11226
  source: "detect"
11020
11227
  }));
11021
11228
  }
11229
+ var ENV_DEFINING_FILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock", "pnpm-workspace.yaml"];
11230
+ function envCachePlan(worktree, io = {}) {
11231
+ if (!ENV_CACHE_ENABLED) return null;
11232
+ const exists = io.exists ?? existsSync13;
11233
+ const readText = io.readText ?? safeReadText;
11234
+ const listDirs = io.listDirs ?? safeListDirs;
11235
+ const pm = detectPackageManager(worktree, io);
11236
+ if (!pm) return null;
11237
+ const parts = [`pm:${pm}`];
11238
+ for (const name of ENV_DEFINING_FILES) {
11239
+ const raw = readText(join21(worktree, name));
11240
+ if (raw !== null) parts.push(`${name}
11241
+ ${raw}`);
11242
+ }
11243
+ const pkgDirs = workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs });
11244
+ const rels = pkgDirs.map((dir) => dir.slice(worktree.length + 1)).sort();
11245
+ for (const rel of ["", ...rels]) {
11246
+ const raw = readText(join21(worktree, rel, "package.json"));
11247
+ if (raw !== null) parts.push(`${rel}/package.json
11248
+ ${raw}`);
11249
+ }
11250
+ return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join21(rel, "node_modules"))] };
11251
+ }
11022
11252
  function planPrepare(worktree, io = {}) {
11023
11253
  const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
11024
11254
  if (!plan) return { source: "none", ecosystem: null, commands: [] };
11025
11255
  return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
11026
11256
  }
11027
11257
  async function ensureWorktreeDeps(worktree, io = {}) {
11028
- const exists = io.exists ?? existsSync12;
11258
+ const exists = io.exists ?? existsSync13;
11029
11259
  const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
11030
11260
  const approve = io.approve ?? (() => true);
11031
- const plan = planPrepare(worktree, io);
11261
+ const cachePlan = envCachePlan(worktree, io);
11262
+ const cacheIO = io.envCache ?? {};
11032
11263
  const report = {
11033
- ecosystem: plan.ecosystem,
11034
- source: plan.source,
11035
- outcome: plan.source === "none" ? "not-detected" : "nothing-to-do",
11264
+ ecosystem: null,
11265
+ source: "none",
11266
+ outcome: "not-detected",
11036
11267
  ran: [],
11037
11268
  failed: [],
11038
11269
  skipped: [],
11039
11270
  blocked: [],
11040
- warned: []
11271
+ warned: [],
11272
+ restored: [],
11273
+ cached: false
11041
11274
  };
11275
+ if (cachePlan && !exists(join21(worktree, "node_modules"))) {
11276
+ report.restored = await restoreEnvSnapshot(worktree, cachePlan.key, cacheIO) ?? [];
11277
+ }
11278
+ const plan = planPrepare(worktree, io);
11279
+ report.ecosystem = plan.ecosystem;
11280
+ report.source = plan.source;
11281
+ report.outcome = plan.source === "none" ? "not-detected" : "nothing-to-do";
11042
11282
  for (const cmd of plan.commands) {
11043
11283
  if (cmd.skipIfExists && exists(cmd.skipIfExists)) {
11044
11284
  report.skipped.push(cmd.label);
@@ -11060,11 +11300,20 @@ async function ensureWorktreeDeps(worktree, io = {}) {
11060
11300
  break;
11061
11301
  }
11062
11302
  }
11303
+ if (cachePlan && !report.failed.length && !report.blocked.length) {
11304
+ report.cached = await saveEnvSnapshot(worktree, cachePlan.key, cachePlan.dirs, cacheIO);
11305
+ if (report.cached) {
11306
+ await pruneEnvSnapshots(cacheIO);
11307
+ await sweepEnvCacheTemp(cacheIO);
11308
+ }
11309
+ }
11063
11310
  return report;
11064
11311
  }
11065
11312
  function depsEvents(report) {
11066
11313
  const events = [];
11067
11314
  const where = report.source === "none" ? "" : ` (${[report.ecosystem, `\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A: ${sourceLabel(report.source)}`].filter(Boolean).join(", ")})`;
11315
+ if (report.restored.length)
11316
+ events.push(`\u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0432\u0437\u044F\u0442\u043E \u0438\u0437 \u043C\u0430\u0448\u0438\u043D\u043D\u043E\u0433\u043E \u043A\u044D\u0448\u0430, \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043D\u0435 \u043F\u043E\u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043B\u0430\u0441\u044C: ${report.restored.length} \u043A\u0430\u0442. node_modules \u2014 TASWM-519`);
11068
11317
  if (report.ran.length) events.push(`\u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 worktree${where}: ${report.ran.join(", ")} \u2014 TASWM-312`);
11069
11318
  if (report.failed.length)
11070
11319
  events.push(
@@ -11293,20 +11542,240 @@ function preStartAttempts(runs, taskSlug, opts = {}) {
11293
11542
  }
11294
11543
  return n;
11295
11544
  }
11296
- function lastPreStartAttempt(runs, taskSlug) {
11297
- return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort(newestFirst)[0] ?? null;
11545
+ function lastPreStartAttempt(runs, taskSlug) {
11546
+ return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort(newestFirst)[0] ?? null;
11547
+ }
11548
+ function unresolvedRepoReason(repo, served) {
11549
+ const raw = repo.trim();
11550
+ if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
11551
+ const list = served.length ? served.join(", ") : "\u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E (\u0440\u0435\u0435\u0441\u0442\u0440 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u043F\u0443\u0441\u0442)";
11552
+ return `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 ${raw} \u043D\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u0432 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0443\u0442\u044C: \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0435\u0433\u043E \u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442. \u041C\u0430\u0448\u0438\u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u0435\u0442: ${list}. \u041A\u043B\u043E\u043D\u0438\u0440\u0443\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u0434\u043E\u0431\u0430\u0432\u044C \u0435\u0433\u043E \u0432 SWARM_REPO_PATHS (\u0438\u043B\u0438 swarm start --repo <\u043F\u0443\u0442\u044C>), \u043B\u0438\u0431\u043E \u043E\u0442\u0434\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u043C\u0430\u0448\u0438\u043D\u0435, \u0433\u0434\u0435 \u043E\u043D \u0435\u0441\u0442\u044C.`;
11553
+ }
11554
+ function parkReason(attempts, reason) {
11555
+ 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}`;
11556
+ }
11557
+ function parkReasonUnretriable(reason) {
11558
+ 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}`;
11559
+ }
11560
+
11561
+ // package.json
11562
+ var package_default = {
11563
+ name: "@taskless-app/swarm",
11564
+ version: "0.6.32",
11565
+ type: "module",
11566
+ description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
11567
+ keywords: [
11568
+ "ai-agents",
11569
+ "coding-agents",
11570
+ "swarm",
11571
+ "orchestration",
11572
+ "autonomous-agents",
11573
+ "mcp",
11574
+ "taskless",
11575
+ "cli"
11576
+ ],
11577
+ license: "SEE LICENSE IN LICENSE",
11578
+ author: "Ilya Smurov <ilya@smurov.com>",
11579
+ homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
11580
+ repository: {
11581
+ type: "git",
11582
+ url: "git+https://github.com/ilyasmurov/taskless.git",
11583
+ directory: "packages/swarm-daemon"
11584
+ },
11585
+ engines: {
11586
+ node: ">=22"
11587
+ },
11588
+ bin: {
11589
+ swarm: "dist/cli.js"
11590
+ },
11591
+ files: [
11592
+ "dist",
11593
+ "docs/HERO.md",
11594
+ "hooks",
11595
+ "roles"
11596
+ ],
11597
+ scripts: {
11598
+ overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
11599
+ swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
11600
+ build: "tsup",
11601
+ start: "node dist/overmind.js",
11602
+ prepublishOnly: "pnpm run build",
11603
+ test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
11604
+ "test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
11605
+ "test:watch": "vitest",
11606
+ typecheck: "tsc -p tsconfig.json --noEmit",
11607
+ lint: "echo 'no lint in swarm-daemon yet'"
11608
+ },
11609
+ dependencies: {
11610
+ "@hono/node-server": "^2.0.12",
11611
+ "@modelcontextprotocol/sdk": "^1.29.0",
11612
+ execa: "^10.0.0",
11613
+ zod: "3.25.76"
11614
+ },
11615
+ gitHead: "f40f0fb4d2f96809f88b7d6ae4f086c5f4f004da"
11616
+ };
11617
+
11618
+ // src/version.ts
11619
+ var PACKAGE_VERSION = package_default.version;
11620
+ var MIN_NODE_MAJOR = Number(/(\d+)/.exec(package_default.engines?.node ?? "")?.[1] ?? 0);
11621
+
11622
+ // src/release.ts
11623
+ init_src();
11624
+ init_claudeBin();
11625
+ var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
11626
+ var LATEST_TIMEOUT_MS = 4e3;
11627
+ async function fetchLatestVersion(opts = {}) {
11628
+ const fetchFn = opts.fetchFn ?? fetch;
11629
+ const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
11630
+ const pkg = opts.pkg ?? SWARM_PACKAGE;
11631
+ const ctrl = new AbortController();
11632
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
11633
+ try {
11634
+ const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
11635
+ signal: ctrl.signal,
11636
+ headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
11637
+ });
11638
+ if (!res.ok) return null;
11639
+ const body = await res.json();
11640
+ return typeof body.version === "string" && body.version ? body.version : null;
11641
+ } catch {
11642
+ return null;
11643
+ } finally {
11644
+ clearTimeout(timer);
11645
+ }
11646
+ }
11647
+ function isOutdated(current, latest) {
11648
+ if (!latest || !current) return false;
11649
+ return compareVersionDirs(latest, current) > 0;
11650
+ }
11651
+ function outdatedLine(current, latest, updateCommand = "swarm update") {
11652
+ if (!isOutdated(current, latest)) return null;
11653
+ return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
11654
+ }
11655
+
11656
+ // src/daemonVersion.ts
11657
+ function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
11658
+ return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
11659
+ }
11660
+ var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
11661
+ async function checkDaemonVersion(start, opts = {}) {
11662
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
11663
+ const fetchLatest = opts.latest ?? (() => fetchLatestVersion());
11664
+ const latestVersion = await fetchLatest().catch(() => null);
11665
+ return {
11666
+ startedAt: start.startedAt,
11667
+ version: start.version,
11668
+ latestVersion,
11669
+ stale: isOutdated(start.version, latestVersion),
11670
+ checkedAt: now().toISOString()
11671
+ };
11672
+ }
11673
+
11674
+ // src/escalations.ts
11675
+ var RESOLVE_ESCALATION_MARK = "\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D";
11676
+ var REVIEW_ESCALATION_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C\u0441\u044F";
11677
+ var REVIEW_UNRECOVERABLE_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u044C";
11678
+ function reviewUnrecoverableMark(branch) {
11679
+ return `${REVIEW_UNRECOVERABLE_MARK} (\u0432\u0435\u0442\u043A\u0430 ${branch})`;
11298
11680
  }
11299
- function unresolvedRepoReason(repo, served) {
11300
- const raw = repo.trim();
11301
- if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
11302
- const list = served.length ? served.join(", ") : "\u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E (\u0440\u0435\u0435\u0441\u0442\u0440 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u043F\u0443\u0441\u0442)";
11303
- return `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 ${raw} \u043D\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u0432 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0443\u0442\u044C: \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0435\u0433\u043E \u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442. \u041C\u0430\u0448\u0438\u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u0435\u0442: ${list}. \u041A\u043B\u043E\u043D\u0438\u0440\u0443\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u0434\u043E\u0431\u0430\u0432\u044C \u0435\u0433\u043E \u0432 SWARM_REPO_PATHS (\u0438\u043B\u0438 swarm start --repo <\u043F\u0443\u0442\u044C>), \u043B\u0438\u0431\u043E \u043E\u0442\u0434\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u043C\u0430\u0448\u0438\u043D\u0435, \u0433\u0434\u0435 \u043E\u043D \u0435\u0441\u0442\u044C.`;
11681
+ var REVIEW_CYCLE_ESCALATION_MARK = "\u0440\u0435-\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u043E\u0441\u044C";
11682
+ var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u0441\u044C";
11683
+ var CHANGES_WAKE_ESCALATION_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C";
11684
+ var REVIEW_BUDGET_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u043E\u0441\u044C \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442";
11685
+ function reviewBudgetMark(cycle) {
11686
+ return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
11304
11687
  }
11305
- function parkReason(attempts, reason) {
11306
- 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}`;
11688
+ var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
11689
+ var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
11690
+ var CI_WAIT_ESCALATION_MARK = "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 CI \u0437\u0430\u0442\u044F\u043D\u0443\u043B\u043E\u0441\u044C";
11691
+ var ESCALATIONS = [
11692
+ {
11693
+ mark: SELF_REFERENCE_MARK,
11694
+ // Надзора тут нет намеренно: у self-reference свой канал доставки (watchdog-нотификация
11695
+ // на месте записи), и вход в NEEDS_HUMAN_MARKS задвоил бы сигнал. Ровно на этом маркере
11696
+ // wakeRun и запирал дверь: под self-reference попадает ЛЮБАЯ задача роя, трогающая
11697
+ // packages/swarm-daemon или packages/swarm-core, — вердикта не будет никогда.
11698
+ sides: ["reviewer", "author"],
11699
+ why: "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430: \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E, \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442",
11700
+ stopReason: () => `PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 (${DAEMON_CODE_PATHS.join(", ")}) \u2014 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E (TASWM-270): \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0438\u0439 \u0434\u0435\u043C\u043E\u043D \u043A\u0440\u0443\u0442\u0438\u0442\u0441\u044F \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u043C \u043A\u043E\u0434\u0435 \u0438 \u0441\u0432\u043E\u044E \u0436\u0435 \u043F\u0440\u0430\u0432\u043A\u0443 \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
11701
+ },
11702
+ {
11703
+ mark: REVIEW_ESCALATION_MARK,
11704
+ sides: ["reviewer", "author", "supervision"],
11705
+ why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E \u0440\u043E\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442",
11706
+ stopReason: (_run, limits) => `\u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B (${limits.reviewSpawn}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
11707
+ },
11708
+ {
11709
+ mark: REVIEW_CYCLE_ESCALATION_MARK,
11710
+ sides: ["reviewer", "author", "supervision"],
11711
+ why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0430\u0432\u0442\u043E\u0441\u043F\u0430\u0432\u043D\u043E\u0432 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442",
11712
+ stopReason: (_run, limits) => `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (${limits.reviewCycle}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
11713
+ },
11714
+ {
11715
+ mark: REVIEW_BUDGET_MARK,
11716
+ sides: ["reviewer", "author", "supervision"],
11717
+ why: "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u043F\u043E\u043F\u044B\u0442\u043A\u0430 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435",
11718
+ scoped: (run) => reviewBudgetMark(run.review_cycle ?? 0),
11719
+ stopReason: () => "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
11720
+ },
11721
+ {
11722
+ mark: REVIEW_UNRECOVERABLE_MARK,
11723
+ sides: ["reviewer", "author", "supervision"],
11724
+ why: "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0441\u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E\u0442 \u0436\u0435 git-\u0432\u044B\u0437\u043E\u0432 \u0441 \u0442\u0435\u043C \u0436\u0435 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u043C",
11725
+ scoped: (run) => reviewUnrecoverableMark(run.branch || run.task_slug),
11726
+ stopReason: () => "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
11727
+ },
11728
+ {
11729
+ mark: CHANGES_WAKE_ESCALATION_MARK,
11730
+ // Проверяющего этот маркер не касается: вердикт-то есть, не поднимается АВТОР.
11731
+ sides: ["author", "supervision"],
11732
+ why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0431\u0435\u0441\u043F\u043B\u043E\u0434\u043D\u044B\u0445 \u0444\u0438\u043A\u0441-\u043F\u043E\u0431\u0443\u0434\u043E\u043A \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0430\u0432\u0442\u043E\u0440\u0430 \u0440\u043E\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0431\u0443\u0434\u0438\u0442",
11733
+ scoped: (run) => `${CHANGES_WAKE_ESCALATION_MARK} (\u043A\u0440\u0443\u0433 ${run.review_cycle ?? 0})`
11734
+ },
11735
+ {
11736
+ mark: RESOLVE_ESCALATION_MARK,
11737
+ sides: ["author", "supervision"],
11738
+ why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440\u043E\u0432 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0434\u0430\u043B\u044C\u0448\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0440\u0430\u0437\u0432\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
11739
+ },
11740
+ {
11741
+ mark: CI_WAKE_ESCALATION_MARK,
11742
+ sides: ["author", "supervision"],
11743
+ why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u0431\u0443\u0434\u043E\u043A \u043F\u043E \u043A\u0440\u0430\u0441\u043D\u043E\u043C\u0443 CI \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0447\u0438\u043D\u0438\u0442\u044C CI \u0441\u0430\u043C"
11744
+ },
11745
+ {
11746
+ mark: VERIFY_ESCALATION_MARK,
11747
+ // Надзора нет: верификация — не ревью, её отсутствие не держит PR (TASWM-344), и в
11748
+ // NEEDS_HUMAN_MARKS этот маркер никогда не входил. Автору дверь открывает: вердикта
11749
+ // верификации у прогона не будет, поднять его может только оператор.
11750
+ sides: ["author"],
11751
+ why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0443 PR \u043D\u0435 \u0431\u0443\u0434\u0435\u0442"
11752
+ },
11753
+ {
11754
+ mark: CI_WAIT_ESCALATION_MARK,
11755
+ // Сторон НЕТ — и это решение, а не пропуск. Маркер лежит на прогоне РЕВЬЮЕРА (paused,
11756
+ // AWAITING_CI_REASON), а не на прогоне автора, и человека зовёт сам:
11757
+ // escalateStaleCiWaits пишет аномалию и дёргает watchdog.notify на месте. Вписать его в
11758
+ // author/supervision значило бы задвоить сигнал и поменять поведение — чего задача
11759
+ // прямо просила не делать.
11760
+ sides: [],
11761
+ why: "\u0440\u043E\u0439 \u0417\u041E\u0412\u0401\u0422 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u043D\u043E \u0441\u0430\u043C \u0435\u0449\u0451 \u0436\u0434\u0451\u0442: \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043D\u0435 \u0441\u0434\u0430\u043B\u0441\u044F, \u0430 \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044E \u0448\u043B\u0451\u0442 \u0441\u0432\u043E\u0438\u043C \u043A\u0430\u043D\u0430\u043B\u043E\u043C"
11762
+ }
11763
+ ];
11764
+ function escalationText(entry, run, side) {
11765
+ return side === "supervision" ? entry.mark : entry.scoped?.(run) ?? entry.mark;
11307
11766
  }
11308
- function parkReasonUnretriable(reason) {
11309
- 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}`;
11767
+ function escalationsFor(side) {
11768
+ return ESCALATIONS.filter((e) => e.sides.includes(side));
11769
+ }
11770
+ function isEscalationText(text, run, side) {
11771
+ return escalationsFor(side).some((e) => text.includes(escalationText(e, run, side)));
11772
+ }
11773
+ function findEscalation(texts, run, side) {
11774
+ for (const entry of escalationsFor(side)) {
11775
+ const needle = escalationText(entry, run, side);
11776
+ if (texts.some((t) => t.includes(needle))) return entry;
11777
+ }
11778
+ return null;
11310
11779
  }
11311
11780
 
11312
11781
  // src/worktreeReap.ts
@@ -11384,7 +11853,12 @@ import { basename as basename3 } from "path";
11384
11853
  async function runGh(args, timeoutMs, cwd) {
11385
11854
  const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
11386
11855
  const child = cwd ? $3({ cwd, timeout: timeoutMs })`gh ${args}` : $3({ timeout: timeoutMs })`gh ${args}`;
11387
- return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
11856
+ try {
11857
+ return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
11858
+ } catch (e) {
11859
+ noteCallFailure("gh", label2, e);
11860
+ throw e;
11861
+ }
11388
11862
  }
11389
11863
  function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
11390
11864
  return (args) => runGh(args, timeoutMs);
@@ -11598,36 +12072,59 @@ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have
11598
12072
  function firstLine(s) {
11599
12073
  return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
11600
12074
  }
12075
+ var GhCallFailedError = class extends Error {
12076
+ failure;
12077
+ constructor(call, failure) {
12078
+ super(`${call}: ${failure.reason}${failure.detail ? ` \u2014 ${failure.detail}` : ""}`);
12079
+ this.name = "GhCallFailedError";
12080
+ this.failure = failure;
12081
+ }
12082
+ };
11601
12083
  var realChecksRunner = async (pr) => {
11602
- const r = await withHardTimeout(
11603
- $3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`,
11604
- "gh",
11605
- callLabel("gh", ["pr", "checks", pr]),
11606
- 15e3
11607
- );
12084
+ const label2 = callLabel("gh", ["pr", "checks", pr]);
12085
+ const r = await withHardTimeout($3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`, "gh", label2, 15e3);
11608
12086
  const stdout = r.stdout ?? "";
11609
12087
  const stderr = r.stderr ?? "";
12088
+ if (isTimedOut(r)) throw new GhCallFailedError(label2, classifyCallFailure(r));
11610
12089
  if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
11611
12090
  if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
12091
+ if (!stdout.trim()) throw new GhCallFailedError(label2, noteCallFailure("gh", label2, r));
11612
12092
  return stdout;
11613
12093
  };
11614
12094
  async function prCheckRows(pr, run) {
12095
+ let out;
11615
12096
  try {
11616
- const out = await run(pr);
11617
- const rows = JSON.parse(out);
11618
- if (!Array.isArray(rows)) return null;
11619
- return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
12097
+ out = await run(pr);
11620
12098
  } catch (e) {
11621
12099
  if (e instanceof CiAccessDeniedError) return { denied: e.detail };
11622
- return null;
12100
+ return { unavailable: e instanceof GhCallFailedError ? e.failure : classifyCallFailure(e) };
12101
+ }
12102
+ let parsed;
12103
+ try {
12104
+ parsed = JSON.parse(out);
12105
+ } catch {
12106
+ return { unavailable: { reason: "unreadable", detail: headOfOutput(out) } };
11623
12107
  }
12108
+ if (!Array.isArray(parsed)) return { unavailable: { reason: "unreadable", detail: "\u043E\u0442\u0432\u0435\u0442 gh \u2014 \u043D\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u043A\u043E\u0432" } };
12109
+ return parsed.map((r) => ({
12110
+ name: r.name ?? "?",
12111
+ bucket: (r.bucket ?? "").toLowerCase(),
12112
+ link: r.link ?? ""
12113
+ }));
12114
+ }
12115
+ function headOfOutput(out) {
12116
+ const head = firstLine(out).slice(0, 120);
12117
+ return head === "gh \u0432\u0435\u0440\u043D\u0443\u043B 403" ? "\u043F\u0443\u0441\u0442\u043E" : head;
11624
12118
  }
11625
12119
  function isDenied(rows) {
11626
- return rows != null && !Array.isArray(rows);
12120
+ return !Array.isArray(rows) && "denied" in rows;
12121
+ }
12122
+ function isUnavailable(rows) {
12123
+ return !Array.isArray(rows) && "unavailable" in rows;
11627
12124
  }
11628
12125
  async function ghChecks(pr, run = realChecksRunner) {
11629
12126
  const rows = await prCheckRows(pr, run);
11630
- if (!rows || isDenied(rows) || rows.length === 0) return null;
12127
+ if (isDenied(rows) || isUnavailable(rows) || rows.length === 0) return null;
11631
12128
  let pass = 0, fail = 0, pending2 = 0;
11632
12129
  for (const r of rows) {
11633
12130
  if (r.bucket === "pass") pass++;
@@ -11651,13 +12148,16 @@ async function ghChecks(pr, run = realChecksRunner) {
11651
12148
  }
11652
12149
  return { bucket, text, tone, total, pass, fail, pending: pending2 };
11653
12150
  }
11654
- function runIdFromLink(link) {
11655
- const m = link.match(/\/actions\/runs\/(\d+)/);
12151
+ function runIdFromLink(link2) {
12152
+ const m = link2.match(/\/actions\/runs\/(\d+)/);
11656
12153
  return m ? m[1] : null;
11657
12154
  }
11658
12155
  async function prCiStatus(pr, run = realChecksRunner) {
11659
12156
  const rows = await prCheckRows(pr, run);
11660
- if (!rows) return null;
12157
+ if (isUnavailable(rows)) {
12158
+ const f = rows.unavailable;
12159
+ return { bucket: "unavailable", reason: f.reason, failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, ...f.detail ? { detail: f.detail } : {} };
12160
+ }
11661
12161
  if (isDenied(rows)) return { bucket: "denied", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, detail: rows.denied };
11662
12162
  if (rows.length === 0) return { bucket: "none", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0 };
11663
12163
  const total = rows.length;
@@ -11713,7 +12213,7 @@ init_git();
11713
12213
 
11714
12214
  // src/diff.ts
11715
12215
  import { statSync as statSync10 } from "fs";
11716
- import { join as join25 } from "path";
12216
+ import { join as join26 } from "path";
11717
12217
  import { $ as $4 } from "execa";
11718
12218
  var MAX_FILES = 200;
11719
12219
  var MAX_LINES_PER_FILE = 1500;
@@ -11881,7 +12381,7 @@ async function collectRunDiff(worktree, opts = {}) {
11881
12381
  files.push(pf);
11882
12382
  }
11883
12383
  }
11884
- files.sort((a, b) => mtime(join25(worktree, b.path)) - mtime(join25(worktree, a.path)));
12384
+ files.sort((a, b) => mtime(join26(worktree, b.path)) - mtime(join26(worktree, a.path)));
11885
12385
  const truncatedFiles = files.length > MAX_FILES;
11886
12386
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
11887
12387
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -12714,7 +13214,7 @@ ${report}${shots.warning}`,
12714
13214
  const myKind = opts.runKind ?? (await store.myRun().catch(() => null))?.kind;
12715
13215
  if (myKind === "hero") {
12716
13216
  const taskless = opts.taskless ?? null;
12717
- const errText2 = (text) => ({ content: [{ type: "text", text }], isError: true });
13217
+ const errText3 = (text) => ({ content: [{ type: "text", text }], isError: true });
12718
13218
  let heroCache = null;
12719
13219
  const heroRun = async () => heroCache ??= await store.myRun();
12720
13220
  let epicCache;
@@ -12732,14 +13232,14 @@ ${report}${shots.warning}`,
12732
13232
  estimate_hours: z.number().positive().describe("\u043E\u0446\u0435\u043D\u043A\u0430 \u0432 \u0447\u0430\u0441\u0430\u0445 (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E)")
12733
13233
  },
12734
13234
  guard(async ({ title, description_md, estimate_hours }) => {
12735
- if (!taskless) return errText2("Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C.");
13235
+ if (!taskless) return errText3("Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C.");
12736
13236
  const hero = await heroRun();
12737
- 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 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
13237
+ if (!hero) return errText3("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
12738
13238
  const ep = await epic(hero.task_slug);
12739
- if (!ep) return errText2(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u044D\u043F\u0438\u043A ${hero.task_slug} \u0438\u0437 Taskless \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430.`);
12740
- if (!ep.projectId) return errText2(`\u044D\u043F\u0438\u043A ${hero.task_slug} \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (\u043D\u0443\u0436\u0435\u043D projectId).`);
13239
+ if (!ep) return errText3(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u044D\u043F\u0438\u043A ${hero.task_slug} \u0438\u0437 Taskless \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430.`);
13240
+ if (!ep.projectId) return errText3(`\u044D\u043F\u0438\u043A ${hero.task_slug} \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (\u043D\u0443\u0436\u0435\u043D projectId).`);
12741
13241
  const statusId = await taskless.backlogStatusId();
12742
- if (!statusId) return errText2("\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0441\u0442\u0430\u0442\u0443\u0441 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438 backlog \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430 \u2014 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u044E (\u0440\u0438\u0441\u043A \u043F\u043E\u043F\u0430\u0441\u0442\u044C \u0441\u0440\u0430\u0437\u0443 \u0432 \xAB\u0421\u0434\u0435\u043B\u0430\u0442\u044C\xBB).");
13242
+ if (!statusId) return errText3("\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0441\u0442\u0430\u0442\u0443\u0441 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438 backlog \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430 \u2014 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u044E (\u0440\u0438\u0441\u043A \u043F\u043E\u043F\u0430\u0441\u0442\u044C \u0441\u0440\u0430\u0437\u0443 \u0432 \xAB\u0421\u0434\u0435\u043B\u0430\u0442\u044C\xBB).");
12743
13243
  const footer = `
12744
13244
 
12745
13245
  ---
@@ -12753,7 +13253,7 @@ ${report}${shots.warning}`,
12753
13253
  projectId: ep.projectId
12754
13254
  // TASWM-138: наследуем проект эпика — иначе сирота не claimable
12755
13255
  });
12756
- if (!created) return errText2("Taskless \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043B \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430).");
13256
+ if (!created) return errText3("Taskless \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043B \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430).");
12757
13257
  await store.addEvent(null, "step", `\u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u0441\u043E\u0437\u0434\u0430\u043D\u0430: ${created.slug}`);
12758
13258
  return { content: [{ type: "text", text: created.slug }] };
12759
13259
  })
@@ -12767,18 +13267,18 @@ ${report}${shots.warning}`,
12767
13267
  after: z.array(z.string()).optional().describe("\u0441\u043B\u0430\u0433\u0438 \u0437\u0430\u0434\u0430\u0447, \u043F\u043E\u0441\u043B\u0435 done \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C (\u043E\u043F\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u043E)")
12768
13268
  },
12769
13269
  guard(async ({ slug, area, after }) => {
12770
- 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.");
13270
+ if (!taskless) return errText3("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.");
12771
13271
  const hero = await heroRun();
12772
- 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.");
13272
+ if (!hero) return errText3("\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.");
12773
13273
  const read = await taskless.getTaskDetailed(slug);
12774
- 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)}.`);
13274
+ if (!read.ok) return errText3(`\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)}.`);
12775
13275
  const task = read.task;
12776
13276
  const ep = await epic(hero.task_slug);
12777
13277
  if (!ep || task.parentId !== ep.id) {
12778
- 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.`);
13278
+ return errText3(`${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.`);
12779
13279
  }
12780
13280
  if (task.statusCategory !== "unstarted") {
12781
- return errText2(
13281
+ return errText3(
12782
13282
  `\u0436\u0434\u0438 \u0430\u043F\u043F\u0440\u0443\u0432\u0430 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430: \u043F\u0435\u0440\u0435\u043D\u043E\u0441 \u0432 \xAB\u0421\u0434\u0435\u043B\u0430\u0442\u044C\xBB. ${slug} \u0441\u0435\u0439\u0447\u0430\u0441 \u0432 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \xAB${task.statusCategory ?? "?"}\xBB, \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F unstarted (\u043E\u0434\u043E\u0431\u0440\u0435\u043D\u043D\u044B\u0445).`
12783
13283
  );
12784
13284
  }
@@ -12800,7 +13300,7 @@ ${report}${shots.warning}`,
12800
13300
  {},
12801
13301
  guard(async () => {
12802
13302
  const hero = await heroRun();
12803
- 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 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043E\u0442\u0440\u044F\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435 \u0441\u043E\u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
13303
+ if (!hero) return errText3("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043E\u0442\u0440\u044F\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435 \u0441\u043E\u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
12804
13304
  const squad = await store.squadRuns();
12805
13305
  const qByRun = /* @__PURE__ */ new Map();
12806
13306
  for (const q of await store.openQuestionsFor()) {
@@ -12830,9 +13330,9 @@ ${report}${shots.warning}`,
12830
13330
  },
12831
13331
  guard(async ({ question_id, text }) => {
12832
13332
  const mine = (await store.openQuestionsFor()).some((q) => q.id === question_id);
12833
- if (!mine) return errText2(`\u0432\u043E\u043F\u0440\u043E\u0441 #${question_id} \u043D\u0435 \u0438\u0437 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0442\u0432\u043E\u0435\u0433\u043E \u043E\u0442\u0440\u044F\u0434\u0430 \u2014 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F.`);
13333
+ if (!mine) return errText3(`\u0432\u043E\u043F\u0440\u043E\u0441 #${question_id} \u043D\u0435 \u0438\u0437 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0442\u0432\u043E\u0435\u0433\u043E \u043E\u0442\u0440\u044F\u0434\u0430 \u2014 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F.`);
12834
13334
  const answered = await store.answer(question_id, text, "hero");
12835
- if (!answered || answered.status !== "answered") return errText2(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 #${question_id}.`);
13335
+ if (!answered || answered.status !== "answered") return errText3(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 #${question_id}.`);
12836
13336
  return { content: [{ type: "text", text: `\u043E\u0442\u0432\u0435\u0442 \u0434\u0440\u043E\u043D\u0443 (\u0432\u043E\u043F\u0440\u043E\u0441 #${question_id}) \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0434\u0440\u043E\u043D \u0440\u0430\u0437\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D` }] };
12837
13337
  })
12838
13338
  );
@@ -12906,7 +13406,7 @@ async function runDroneServer(runId) {
12906
13406
 
12907
13407
  // src/droneBridge.ts
12908
13408
  init_src();
12909
- import { createHash as createHash3 } from "crypto";
13409
+ import { createHash as createHash4 } from "crypto";
12910
13410
 
12911
13411
  // src/anomalies.ts
12912
13412
  init_src();
@@ -13437,7 +13937,7 @@ function questionKey(runId, text) {
13437
13937
  return `${runId} ${text.trim().replace(/\s+/g, " ")}`;
13438
13938
  }
13439
13939
  function noteKey(runId, text) {
13440
- return `note:${runId}:${createHash3("sha1").update(questionKey(runId, text)).digest("hex").slice(0, 16)}`;
13940
+ return `note:${runId}:${createHash4("sha1").update(questionKey(runId, text)).digest("hex").slice(0, 16)}`;
13441
13941
  }
13442
13942
  async function syncDroneChannel(opts) {
13443
13943
  const result = { imported: [], answered: [], notes: [] };
@@ -13663,42 +14163,6 @@ init_authState();
13663
14163
 
13664
14164
  // src/swarmStatus.ts
13665
14165
  init_linkFailure();
13666
-
13667
- // src/release.ts
13668
- init_src();
13669
- init_claudeBin();
13670
- var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
13671
- var LATEST_TIMEOUT_MS = 4e3;
13672
- async function fetchLatestVersion(opts = {}) {
13673
- const fetchFn = opts.fetchFn ?? fetch;
13674
- const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
13675
- const pkg = opts.pkg ?? SWARM_PACKAGE;
13676
- const ctrl = new AbortController();
13677
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
13678
- try {
13679
- const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
13680
- signal: ctrl.signal,
13681
- headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
13682
- });
13683
- if (!res.ok) return null;
13684
- const body = await res.json();
13685
- return typeof body.version === "string" && body.version ? body.version : null;
13686
- } catch {
13687
- return null;
13688
- } finally {
13689
- clearTimeout(timer);
13690
- }
13691
- }
13692
- function isOutdated(current, latest) {
13693
- if (!latest || !current) return false;
13694
- return compareVersionDirs(latest, current) > 0;
13695
- }
13696
- function outdatedLine(current, latest, updateCommand = "swarm update") {
13697
- if (!isOutdated(current, latest)) return null;
13698
- return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
13699
- }
13700
-
13701
- // src/swarmStatus.ts
13702
14166
  init_src();
13703
14167
  function classifyLinkError(e) {
13704
14168
  const { kind, detail } = explainLinkFailure(e);
@@ -14000,22 +14464,22 @@ function makeMergeGateParts(deps) {
14000
14464
  }
14001
14465
 
14002
14466
  // src/envGate.ts
14003
- import { existsSync as existsSync15 } from "fs";
14004
- import { join as join27 } from "path";
14467
+ import { existsSync as existsSync16 } from "fs";
14468
+ import { join as join28 } from "path";
14005
14469
  import { createConnection } from "net";
14006
14470
  import { execa as execa2 } from "execa";
14007
14471
 
14008
14472
  // src/platform.ts
14009
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
14010
- import { delimiter, join as join26 } from "path";
14473
+ import { existsSync as existsSync15, readFileSync as readFileSync20 } from "fs";
14474
+ import { delimiter, join as join27 } from "path";
14011
14475
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
14012
- if (name.includes("/") || name.includes("\\")) return existsSync14(name) ? name : null;
14476
+ if (name.includes("/") || name.includes("\\")) return existsSync15(name) ? name : null;
14013
14477
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
14014
14478
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
14015
14479
  for (const dir of dirs) {
14016
14480
  for (const ext of exts) {
14017
- const candidate = join26(dir, name + ext);
14018
- if (existsSync14(candidate)) return candidate;
14481
+ const candidate = join27(dir, name + ext);
14482
+ if (existsSync15(candidate)) return candidate;
14019
14483
  }
14020
14484
  }
14021
14485
  return null;
@@ -14098,8 +14562,8 @@ var COMPOSE_MARKERS = [
14098
14562
  "compose.yaml"
14099
14563
  ];
14100
14564
  function detectRequirements(repo, io = {}) {
14101
- const exists = io.exists ?? existsSync15;
14102
- const marker = COMPOSE_MARKERS.find((f) => exists(join27(repo, f)));
14565
+ const exists = io.exists ?? existsSync16;
14566
+ const marker = COMPOSE_MARKERS.find((f) => exists(join28(repo, f)));
14103
14567
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
14104
14568
  }
14105
14569
  function resolveRequirements(repo, declared, io = {}) {
@@ -14233,69 +14697,6 @@ init_src();
14233
14697
  import { randomBytes as randomBytes3 } from "crypto";
14234
14698
  import { basename as basename5 } from "path";
14235
14699
  import { serve as serve2 } from "@hono/node-server";
14236
-
14237
- // package.json
14238
- var package_default = {
14239
- name: "@taskless-app/swarm",
14240
- version: "0.6.30",
14241
- type: "module",
14242
- description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
14243
- keywords: [
14244
- "ai-agents",
14245
- "coding-agents",
14246
- "swarm",
14247
- "orchestration",
14248
- "autonomous-agents",
14249
- "mcp",
14250
- "taskless",
14251
- "cli"
14252
- ],
14253
- license: "SEE LICENSE IN LICENSE",
14254
- author: "Ilya Smurov <ilya@smurov.com>",
14255
- homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
14256
- repository: {
14257
- type: "git",
14258
- url: "git+https://github.com/ilyasmurov/taskless.git",
14259
- directory: "packages/swarm-daemon"
14260
- },
14261
- engines: {
14262
- node: ">=22"
14263
- },
14264
- bin: {
14265
- swarm: "dist/cli.js"
14266
- },
14267
- files: [
14268
- "dist",
14269
- "docs/HERO.md",
14270
- "hooks",
14271
- "roles"
14272
- ],
14273
- scripts: {
14274
- overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
14275
- swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
14276
- build: "tsup",
14277
- start: "node dist/overmind.js",
14278
- prepublishOnly: "pnpm run build",
14279
- test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
14280
- "test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
14281
- "test:watch": "vitest",
14282
- typecheck: "tsc -p tsconfig.json --noEmit",
14283
- lint: "echo 'no lint in swarm-daemon yet'"
14284
- },
14285
- dependencies: {
14286
- "@hono/node-server": "^2.0.12",
14287
- "@modelcontextprotocol/sdk": "^1.29.0",
14288
- execa: "^10.0.0",
14289
- zod: "3.25.76"
14290
- },
14291
- gitHead: "3d2b558784c89fbbd1f8f70a944935653eeb3fb1"
14292
- };
14293
-
14294
- // src/version.ts
14295
- var PACKAGE_VERSION = package_default.version;
14296
- var MIN_NODE_MAJOR = Number(/(\d+)/.exec(package_default.engines?.node ?? "")?.[1] ?? 0);
14297
-
14298
- // src/web.ts
14299
14700
  init_storm();
14300
14701
  init_resources();
14301
14702
  init_config();
@@ -15903,34 +16304,16 @@ function closeWebServer(server) {
15903
16304
  });
15904
16305
  }
15905
16306
 
15906
- // src/daemonVersion.ts
15907
- function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
15908
- return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
15909
- }
15910
- var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
15911
- async function checkDaemonVersion(start, opts = {}) {
15912
- const now = opts.now ?? (() => /* @__PURE__ */ new Date());
15913
- const fetchLatest = opts.latest ?? (() => fetchLatestVersion());
15914
- const latestVersion = await fetchLatest().catch(() => null);
15915
- return {
15916
- startedAt: start.startedAt,
15917
- version: start.version,
15918
- latestVersion,
15919
- stale: isOutdated(start.version, latestVersion),
15920
- checkedAt: now().toISOString()
15921
- };
15922
- }
15923
-
15924
16307
  // src/hostCommands.ts
15925
16308
  init_src();
15926
- import { readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
15927
- import { join as join28 } from "path";
16309
+ import { readFileSync as readFileSync21, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
16310
+ import { join as join29 } from "path";
15928
16311
  function hostCommandStatePath(home) {
15929
- return join28(home, "host-command.json");
16312
+ return join29(home, "host-command.json");
15930
16313
  }
15931
16314
  function readState(path) {
15932
16315
  try {
15933
- return JSON.parse(readFileSync19(path, "utf8"));
16316
+ return JSON.parse(readFileSync21(path, "utf8"));
15934
16317
  } catch {
15935
16318
  return {};
15936
16319
  }
@@ -16100,8 +16483,8 @@ async function doRestart(cmd, opts, note) {
16100
16483
 
16101
16484
  // src/selfUpdate.ts
16102
16485
  init_git();
16103
- import { existsSync as existsSync16 } from "fs";
16104
- import { join as join29 } from "path";
16486
+ import { existsSync as existsSync17 } from "fs";
16487
+ import { join as join30 } from "path";
16105
16488
  import { $ as $5 } from "execa";
16106
16489
  var DAEMON_PACKAGE = "@taskless-app/swarm";
16107
16490
  var defaultRun = async (cmd, args, cwd) => {
@@ -16114,7 +16497,7 @@ function tail(text, max = 400) {
16114
16497
  }
16115
16498
  async function runSelfUpdate(io) {
16116
16499
  const run = io.run ?? defaultRun;
16117
- const exists = io.exists ?? existsSync16;
16500
+ const exists = io.exists ?? existsSync17;
16118
16501
  const repo = io.repo;
16119
16502
  await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
16120
16503
  const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
@@ -16143,7 +16526,7 @@ async function runSelfUpdate(io) {
16143
16526
  };
16144
16527
  }
16145
16528
  await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
16146
- if (!exists(join29(repo, "pnpm-lock.yaml"))) {
16529
+ if (!exists(join30(repo, "pnpm-lock.yaml"))) {
16147
16530
  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" };
16148
16531
  }
16149
16532
  const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
@@ -16244,7 +16627,7 @@ function orphanNoteText(run, q) {
16244
16627
 
16245
16628
  // src/probe.ts
16246
16629
  init_executors();
16247
- import { spawn as spawn2 } from "child_process";
16630
+ import { spawn as spawn3 } from "child_process";
16248
16631
  var RESET_RE = /resets[^\n]*/i;
16249
16632
  function executorHealthy(opts = {}) {
16250
16633
  const executor = opts.executor ?? resolveExecutor();
@@ -16263,7 +16646,7 @@ function executorHealthy(opts = {}) {
16263
16646
  }
16264
16647
  resolve4(r);
16265
16648
  };
16266
- const child = spawn2(bin, [...executor.probe.args], { stdio: ["ignore", "pipe", "pipe"] });
16649
+ const child = spawn3(bin, [...executor.probe.args], { stdio: ["ignore", "pipe", "pipe"] });
16267
16650
  child.stdout?.on("data", (d) => {
16268
16651
  out += d.toString();
16269
16652
  });
@@ -16302,19 +16685,19 @@ async function fetchExternalStorm(url, opts = {}) {
16302
16685
  }
16303
16686
 
16304
16687
  // src/anomalyState.ts
16305
- import { existsSync as existsSync17, readFileSync as readFileSync20, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
16306
- import { join as join30 } from "path";
16688
+ import { existsSync as existsSync18, readFileSync as readFileSync22, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
16689
+ import { join as join31 } from "path";
16307
16690
  var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
16308
16691
  var MAX_RESOLVED = 200;
16309
16692
  function emptyState() {
16310
16693
  return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
16311
16694
  }
16312
16695
  function anomalyStatePath(home) {
16313
- return join30(home, "anomaly-state.json");
16696
+ return join31(home, "anomaly-state.json");
16314
16697
  }
16315
16698
  function loadAnomalyState(home, now = Date.now(), io = {}) {
16316
- const exists = io.exists ?? existsSync17;
16317
- const readFile2 = io.readFile ?? ((p) => readFileSync20(p, "utf8"));
16699
+ const exists = io.exists ?? existsSync18;
16700
+ const readFile2 = io.readFile ?? ((p) => readFileSync22(p, "utf8"));
16318
16701
  const path = anomalyStatePath(home);
16319
16702
  if (!exists(path)) return emptyState();
16320
16703
  try {
@@ -16343,11 +16726,11 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
16343
16726
  }
16344
16727
  function saveAnomalyState(home, state, io = {}) {
16345
16728
  const path = anomalyStatePath(home);
16346
- const writeFile3 = io.writeFile ?? ((p, data) => {
16729
+ const writeFile4 = io.writeFile ?? ((p, data) => {
16347
16730
  writeFileSync15(`${p}.tmp`, data);
16348
16731
  renameSync10(`${p}.tmp`, p);
16349
16732
  });
16350
- writeFile3(path, JSON.stringify(state, null, 2));
16733
+ writeFile4(path, JSON.stringify(state, null, 2));
16351
16734
  }
16352
16735
  var AnomalyStateStore = class {
16353
16736
  constructor(home, now = Date.now(), io = {}) {
@@ -16802,23 +17185,23 @@ function abortReason(abort) {
16802
17185
  return `${ABORT_TEXT[abort.kind]} (${abort.raw})`;
16803
17186
  }
16804
17187
  var ABORTED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u041E\u0411\u041E\u0420\u0412\u0410\u041B\u0410\u0421\u042C \u043D\u0430 \u043F\u043E\u043B\u0443\u0441\u043B\u043E\u0432\u0435 (\u043D\u0435 \u0442\u044B \u0435\u0451 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B). \u0422\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log`: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0438 \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439. \u0412\u0410\u0416\u041D\u041E \u043F\u0440\u043E \u043E\u0431\u0440\u044B\u0432\u044B: \u0440\u0432\u0451\u0442\u0441\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0430 \u041E\u0422\u0412\u0415\u0422\u0415 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430, \u0447\u0435\u0439 \u0432\u044B\u0432\u043E\u0434 \u043E\u0433\u0440\u043E\u043C\u0435\u043D (\u043F\u043E\u043B\u043D\u044B\u0439 \u0441\u044C\u044E\u0442 \u0442\u0435\u0441\u0442\u043E\u0432, `git log` \u0431\u0435\u0437 \u043B\u0438\u043C\u0438\u0442\u0430, \u0434\u0430\u043C\u043F \u0444\u0430\u0439\u043B\u0430 \u0446\u0435\u043B\u0438\u043A\u043E\u043C). \u0413\u043D\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430\u0434\u043E \u043F\u043E-\u043F\u0440\u0435\u0436\u043D\u0435\u043C\u0443 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E \u2014 \u043D\u043E \u0432\u044B\u0432\u043E\u0434 \u043F\u0438\u0448\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0444\u0430\u0439\u043B (`LOG=$(mktemp -t suite)`) \u0438 \u0431\u0435\u0440\u0438 \u0438\u0437 \u043D\u0435\u0433\u043E \u0445\u0432\u043E\u0441\u0442 \u0438 \u0438\u0442\u043E\u0433\u043E\u0432\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\u044B\u043D\u044E. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url).";
17188
+ var SUBRUN_EXITED_TEXT = "\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0441\u0430\u043C\u0430, done() \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043D \u2014 \u044D\u0442\u043E \u041D\u0415 \u0441\u043C\u0435\u0440\u0442\u044C (\u0438\u0442\u043E\u0433 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, worktree \u0446\u0435\u043B)";
17189
+ function isSubrun(run) {
17190
+ return run.resolves_run_id != null || run.reviews_run_id != null || run.verifies_run_id != null;
17191
+ }
17192
+ function subrunWaitNudge(run) {
17193
+ const tail2 = run.resolves_run_id != null ? "\u0422\u044B \u0434\u0440\u043E\u043D-\u0420\u0415\u0417\u041E\u041B\u0412\u0415\u0420: \u0434\u043E\u0432\u0435\u0434\u0438 merge-\u043A\u043E\u043C\u043C\u0438\u0442, \u0437\u0430\u043F\u0443\u0448\u044C \u0432 \u0432\u0435\u0442\u043A\u0443 PR \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url). \u041F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0441\u043E\u0440\u0430\u0437\u043C\u0435\u0440\u043D\u043E \u043F\u0440\u0430\u0432\u043A\u0435 \u2014 \u0442\u0438\u043F\u044B/\u0442\u0435\u0441\u0442\u044B \u0417\u0410\u0422\u0420\u041E\u041D\u0423\u0422\u042B\u0425 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u043C \u043F\u0430\u043A\u0435\u0442\u043E\u0432, \u0430 \u043D\u0435 \u0432\u0435\u0441\u044C \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u043E-\u0441\u044C\u044E\u0442: \u043F\u043E\u043B\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 PR \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0435\u0442 CI." : run.verifies_run_id != null ? "\u0422\u044B \u0434\u0440\u043E\u043D-\u0412\u0415\u0420\u0418\u0424\u0418\u041A\u0410\u0422\u041E\u0420: \u0434\u043E\u0432\u0435\u0434\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0438 \u0432\u044B\u043D\u0435\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 (done \u0441 verdict, \u043B\u0438\u0431\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR). \u041F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0441\u043E\u0440\u0430\u0437\u043C\u0435\u0440\u043D\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E\u043C\u0443 \u0434\u0438\u0444\u0444\u0443 \u2014 \u043D\u0435 \u0433\u043E\u043D\u044F\u0439 \u0432\u0435\u0441\u044C \u0441\u044C\u044E\u0442 \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u044B \u0440\u0430\u0434\u0438 \u043E\u0434\u043D\u043E\u0433\u043E PR." : "\u0422\u044B \u0434\u0440\u043E\u043D-\u0420\u0415\u0412\u042C\u042E\u0415\u0420: \u0434\u043E\u0432\u0435\u0434\u0438 \u0447\u0435\u043A-\u043B\u0438\u0441\u0442 \u0438 \u0432\u044B\u043D\u0435\u0441\u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 (APPROVE / REQUEST CHANGES) \u2014 done() \u043B\u0438\u0431\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR.";
17194
+ return `\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0421\u0410\u041C\u0410, \u043D\u043E \u0442\u044B \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043B done() \u2014 \u0440\u043E\u0439 \u043F\u043E\u0434\u043D\u044F\u043B \u0422\u0423 \u0416\u0415 \u0441\u0435\u0441\u0441\u0438\u044E (--continue), \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0446\u0435\u043B. \u0412\u0410\u0416\u041D\u041E: \u0435\u0441\u043B\u0438 \u0442\u044B \u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0444\u043E\u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 (\u0442\u0435\u0441\u0442\u044B/\u0441\u0431\u043E\u0440\u043A\u0443 \u0432 \u0444\u043E\u043D\u0435, Monitor, \xAB\u0436\u0434\u0443 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F\xBB) \u2014 \u0416\u0414\u0410\u0422\u042C \u0415\u0401 \u0422\u0423\u0422 \u041D\u0415\u041A\u041E\u041C\u0423: \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044F, \u043A\u0430\u043A \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u044B \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0448\u044C \u0437\u0432\u0430\u0442\u044C \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B, \u0438 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0435\u0439 \u0443\u043C\u0438\u0440\u0430\u0435\u0442 \u0444\u043E\u043D\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441. \u041D\u0438\u043A\u0430\u043A\u043E\u0433\u043E \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F \u043D\u0435 \u043F\u0440\u0438\u0434\u0451\u0442. \u0414\u043E\u043B\u0433\u043E\u0435 \u0434\u0435\u0440\u0436\u0438 \u0411\u041B\u041E\u041A\u0418\u0420\u0423\u042E\u0429\u0418\u041C \u0432\u044B\u0437\u043E\u0432\u043E\u043C (\u0438\u043B\u0438 \u0434\u0440\u043E\u0431\u0438 \u043D\u0430 \u0447\u0430\u0441\u0442\u0438), \u0430 \u043E\u0431\u044A\u0451\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0431\u0435\u0440\u0438 \u043F\u043E \u043E\u0431\u044A\u0451\u043C\u0443 \u0441\u0432\u043E\u0435\u0439 \u043F\u0440\u0430\u0432\u043A\u0438. ${tail2}`;
17195
+ }
16805
17196
  var EXITED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u043B\u0430\u044F \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0430\u0441\u044C \u0421\u0410\u041C\u0410, \u043D\u043E \u0442\u044B \u0442\u0430\u043A \u0438 \u043D\u0435 \u0432\u044B\u0437\u0432\u0430\u043B done(pr_url) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0443\u0432\u0438\u0434\u0435\u043B \u0441\u0434\u0430\u0447\u0438. \u0422\u044B headless-\u0434\u0440\u043E\u043D, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C \u0422\u0423 \u0416\u0415 \u0437\u0430\u0434\u0430\u0447\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 `git status`/`git log`: \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E \u0438 \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u2014 \u041D\u0415 \u043F\u0435\u0440\u0435\u0434\u0435\u043B\u044B\u0432\u0430\u0439. \u0414\u043E\u0432\u0435\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043E \u043A\u043E\u043D\u0446\u0430, \u043E\u0442\u043A\u0440\u043E\u0439 PR (\u0435\u0441\u043B\u0438 \u0435\u0449\u0451 \u043D\u0435\u0442) \u0438 \u041E\u0411\u042F\u0417\u0410\u0422\u0415\u041B\u042C\u041D\u041E \u0432\u044B\u0437\u043E\u0432\u0438 done(pr_url) \u2014 \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0441\u0434\u0430\u043D\u043D\u043E\u0439. \u0423\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0432 \u043B\u0438\u043C\u0438\u0442/\u043D\u0435 \u043C\u043E\u0436\u0435\u0448\u044C \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C \u2014 \u043D\u0435 \u043C\u043E\u043B\u043E\u0442\u0438 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0437\u0430\u0434\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0447\u0435\u0440\u0435\u0437 MCP-\u0442\u0443\u043B ask. \u0412\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0432 stdout \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439.";
16806
17197
  var STORM_DEAD_LIMIT = Number(process.env.SWARM_STORM_DEAD_LIMIT) || 3;
16807
17198
  var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS) || 3 * 60 * 1e3;
16808
17199
  var RESOLVE_LIMIT = Number(process.env.SWARM_RESOLVE_LIMIT) || 3;
16809
- var RESOLVE_ESCALATION_MARK = "\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D";
16810
17200
  var REVIEW_SPAWN_LIMIT = Number(process.env.SWARM_REVIEW_SPAWN_LIMIT) || 3;
16811
- var REVIEW_ESCALATION_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C\u0441\u044F";
16812
- var REVIEW_UNRECOVERABLE_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u044C";
16813
- function reviewUnrecoverableMark(branch) {
16814
- return `${REVIEW_UNRECOVERABLE_MARK} (\u0432\u0435\u0442\u043A\u0430 ${branch})`;
16815
- }
16816
17201
  var REVIEW_CYCLE_LIMIT = Number(process.env.SWARM_REVIEW_CYCLE_LIMIT) || 3;
16817
- var REVIEW_CYCLE_ESCALATION_MARK = "\u0440\u0435-\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u043E\u0441\u044C";
16818
17202
  var CI_WAKE_LIMIT = Number(process.env.SWARM_CI_WAKE_LIMIT) || 3;
16819
17203
  var CI_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CI_WAKE_COOLDOWN_MS) || 3 * 60 * 1e3;
16820
17204
  var CI_CHECK_COOLDOWN_MS = Number(process.env.SWARM_CI_CHECK_COOLDOWN_MS) || 60 * 1e3;
16821
- var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u0441\u044C";
16822
17205
  var CI_NO_CHECKS_MARK = "\u0443 PR \u043D\u0435\u0442 \u0447\u0435\u043A\u043E\u0432 \u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442";
16823
17206
  var CI_WAIT_POLL_MARK = "\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A";
16824
17207
  var CI_POLL_LIMIT_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u043E \u043F\u043E\u0442\u043E\u043B\u043A\u0443";
@@ -16834,29 +17217,13 @@ var CI_POLL_WINDOW_MS = Math.max(
16834
17217
  Number(process.env.SWARM_CI_POLL_WINDOW_MS) || 90 * 60 * 1e3,
16835
17218
  CI_WAIT_LIMIT_MS + CI_CHECK_COOLDOWN_MS
16836
17219
  );
16837
- var CI_WAIT_ESCALATION_MARK = "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 CI \u0437\u0430\u0442\u044F\u043D\u0443\u043B\u043E\u0441\u044C";
16838
17220
  var CHANGES_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CHANGES_WAKE_COOLDOWN_MS) || 3 * 60 * 1e3;
16839
17221
  var CHANGES_WAKE_LIMIT = Number(process.env.SWARM_CHANGES_WAKE_LIMIT) || 2;
16840
17222
  var CHANGES_FRESH_WAKE_AFTER_MS = Number(process.env.SWARM_CHANGES_FRESH_WAKE_AFTER_MS) || SWARM_ANOMALY_PR_NO_REVIEWER_MS;
16841
17223
  var CHANGES_WAKE_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB";
16842
- var CHANGES_WAKE_ESCALATION_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C";
16843
17224
  var CHANGES_WAKE_FAILED_MARK = "\u043F\u043E\u0431\u0443\u0434\u043A\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C";
16844
17225
  var BUDGET_STOP_REASON = "\u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442: \u0441\u0435\u0441\u0441\u0438\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u0431\u044E\u0434\u0436\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430";
16845
17226
  var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON, ABORTED_STOP_REASON];
16846
- var REVIEW_BUDGET_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u043E\u0441\u044C \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442";
16847
- function reviewBudgetMark(cycle) {
16848
- return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
16849
- }
16850
- var NEEDS_HUMAN_MARKS = [
16851
- RESOLVE_ESCALATION_MARK,
16852
- REVIEW_ESCALATION_MARK,
16853
- REVIEW_UNRECOVERABLE_MARK,
16854
- REVIEW_CYCLE_ESCALATION_MARK,
16855
- CI_WAKE_ESCALATION_MARK,
16856
- CHANGES_WAKE_ESCALATION_MARK,
16857
- REVIEW_BUDGET_MARK
16858
- ];
16859
- var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
16860
17227
  var REVIEW_NO_VERDICT_REASON = "\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430: \u043D\u0438 approve, \u043D\u0438 changes \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434 (TASWM-375)";
16861
17228
  function verdictCommentMark(createdAt) {
16862
17229
  return `\u043A\u043E\u043C\u043C\u0435\u043D\u0442-\u0432\u0435\u0440\u0434\u0438\u043A\u0442@${createdAt}`;
@@ -17112,11 +17479,17 @@ async function prGateForReviewer(deps, run, prUrl, when) {
17112
17479
  await finalizeClosedPrReview(deps, run, `PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 (${when}) \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E`);
17113
17480
  narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, ${when} \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
17114
17481
  return { kind: "finalized" };
17115
- case "unknown":
17482
+ case "unknown": {
17483
+ const notPr = !isGithubPrUrl(prUrl);
17116
17484
  if (!deps.db.events(run.id).some((e) => (e.text ?? "").includes(REVIEW_PR_UNKNOWN_MARK))) {
17117
- deps.db.addEvent(run.id, "step", `${REVIEW_PR_UNKNOWN_MARK} (gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0438\u043B\u0438 \u0432 pr_url \u043D\u0435 PR) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C, \u0441\u043F\u0440\u043E\u0441\u0438\u043C \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435`);
17485
+ deps.db.addEvent(
17486
+ run.id,
17487
+ "step",
17488
+ notPr ? `${REVIEW_PR_UNKNOWN_MARK}: \u0432 pr_url \u043D\u0435 GitHub-PR (${prUrl}) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C. \u0421\u0430\u043C\u043E \u043D\u0435 \u043F\u043E\u0447\u0438\u043D\u0438\u0442\u0441\u044F: \u0434\u0440\u043E\u043D \u0441\u0434\u0430\u043B \u0441\u0441\u044B\u043B\u043A\u043E\u0439 \u043D\u0435 \u043D\u0430 PR.` : `${REVIEW_PR_UNKNOWN_MARK}: gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E PR (\u0441\u0435\u0442\u044C/\u043B\u0438\u043C\u0438\u0442) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C, \u0441\u043F\u0440\u043E\u0441\u0438\u043C \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435`
17489
+ );
17118
17490
  }
17119
17491
  return { kind: "wait" };
17492
+ }
17120
17493
  default:
17121
17494
  if (st.conflicting || st.isDraft) {
17122
17495
  deps.db.addEvent(run.id, "step", `PR \u0441\u0442\u0430\u043B \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u043C/\u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C, ${when} \u2014 \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u0434\u043E \u0441\u043B\u0435\u0434. \u0442\u0438\u043A\u0430`);
@@ -17230,17 +17603,11 @@ function relinkUnboundReviewers(deps) {
17230
17603
  narrate(deps, `${droneLabel(rv)} \u0440\u0430\u0431\u043E\u0442\u0430\u043B \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u043A \u043F\u0440\u043E\u0433\u043E\u043D\u0443 \u2014 \u0441\u0432\u044F\u0437\u0430\u043B \u043F\u043E PR-\u0441\u0441\u044B\u043B\u043A\u0435, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0442\u0435\u043F\u0435\u0440\u044C \u043B\u044F\u0436\u0435\u0442 \u0432 ${parent.task_slug}, \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043A\u043E\u043C\u043C\u0435\u043D\u0442 GitHub.`);
17231
17604
  }
17232
17605
  }
17606
+ var ESCALATION_LIMITS = { reviewSpawn: REVIEW_SPAWN_LIMIT, reviewCycle: REVIEW_CYCLE_LIMIT };
17233
17607
  function deliberateReviewStop(db, run) {
17234
17608
  const texts = db.events(run.id).map((e) => e.text ?? "");
17235
- const has = (mark) => texts.some((t) => t.includes(mark));
17236
- if (has(SELF_REFERENCE_MARK)) {
17237
- return `PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 (${DAEMON_CODE_PATHS.join(", ")}) \u2014 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E (TASWM-270): \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0438\u0439 \u0434\u0435\u043C\u043E\u043D \u043A\u0440\u0443\u0442\u0438\u0442\u0441\u044F \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u043C \u043A\u043E\u0434\u0435 \u0438 \u0441\u0432\u043E\u044E \u0436\u0435 \u043F\u0440\u0430\u0432\u043A\u0443 \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
17238
- }
17239
- if (has(REVIEW_ESCALATION_MARK)) return `\u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B (${REVIEW_SPAWN_LIMIT}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
17240
- if (has(REVIEW_CYCLE_ESCALATION_MARK)) return `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (${REVIEW_CYCLE_LIMIT}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
17241
- if (has(reviewBudgetMark(run.review_cycle ?? 0))) return "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
17242
- if (has(reviewUnrecoverableMark(run.branch || run.task_slug))) return "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
17243
- return null;
17609
+ const hit = findEscalation(texts, run, "reviewer");
17610
+ return hit?.stopReason?.(run, ESCALATION_LIMITS) ?? null;
17244
17611
  }
17245
17612
  function noteReviewBlocked(deps, run, reason) {
17246
17613
  if (!shouldNoteReason(deps.db, run.id, reason)) return;
@@ -17314,7 +17681,6 @@ async function closeOrphanedReviewers(deps) {
17314
17681
  }
17315
17682
  }
17316
17683
  var VERIFY_SPAWN_LIMIT = Number(process.env.SWARM_VERIFY_SPAWN_LIMIT) || 2;
17317
- var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
17318
17684
  function countActiveVerifiers(db) {
17319
17685
  return db.listRuns().filter((r) => r.kind === "verifier" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
17320
17686
  }
@@ -17488,10 +17854,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
17488
17854
  }
17489
17855
  return parts.join("\n");
17490
17856
  }
17491
- var HERO_CHARTER_PATH = join31(dirname5(THIS_FILE), "..", "docs", "HERO.md");
17857
+ var HERO_CHARTER_PATH = join32(dirname5(THIS_FILE), "..", "docs", "HERO.md");
17492
17858
  function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
17493
17859
  const name = run.name || `#${run.id}`;
17494
- const charter = readFileSync21(HERO_CHARTER_PATH, "utf8");
17860
+ const charter = readFileSync23(HERO_CHARTER_PATH, "utf8");
17495
17861
  const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
17496
17862
 
17497
17863
  ${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).`;
@@ -17533,11 +17899,11 @@ function buildResolverPrompt(reviewRun, resolver, conflicts, branch, mainRef) {
17533
17899
  "\u041F\u043E\u0440\u044F\u0434\u043E\u043A:",
17534
17900
  `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.`,
17535
17901
  "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).",
17536
- "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.",
17902
+ "3. \u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0421\u041E\u0420\u0410\u0417\u041C\u0415\u0420\u041D\u041E \u043F\u0440\u0430\u0432\u043A\u0435: \u0442\u0438\u043F\u044B \u0438 \u0442\u0435\u0441\u0442\u044B \u0417\u0410\u0422\u0420\u041E\u041D\u0423\u0422\u042B\u0425 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u043C \u043F\u0430\u043A\u0435\u0442\u043E\u0432 (\u0434\u043B\u044F \u043F\u0440\u0430\u0432\u043A\u0438 \u0432 \u043F\u0430\u0440\u0443 \u0441\u0442\u0440\u043E\u043A \u044D\u0442\u043E \u0442\u0438\u043F\u0438\u0437\u0430\u0446\u0438\u044F \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430, \u0430 \u043D\u0435 \u0441\u044C\u044E\u0442 \u043C\u043E\u043D\u043E\u0440\u0435\u043F\u044B). \u041F\u043E\u043B\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 PR \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0435\u0442 CI, \u0430 \u0442\u044B \u0441\u043B\u0438\u0432\u0430\u0435\u0448\u044C \u0447\u0443\u0436\u0438\u0435 \u0432\u0435\u0442\u043A\u0438, \u0430 \u043D\u0435 \u043F\u0438\u0448\u0435\u0448\u044C \u0444\u0438\u0447\u0443. \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.",
17537
17903
  `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.`,
17538
17904
  `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.`,
17539
17905
  "",
17540
- `\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.`
17906
+ `\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 \u0411\u041B\u041E\u041A\u0418\u0420\u0423\u042E\u0429\u0418\u041C \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u043D\u0435 \u0432 \u0444\u043E\u043D\u0435: \u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044F, \u043A\u0430\u043A \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u044B \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0448\u044C \u0437\u0432\u0430\u0442\u044C \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B, \u0438 \u0444\u043E\u043D\u043E\u0432\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0443\u043C\u0438\u0440\u0430\u0435\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0435\u0439 \u2014 \u0436\u0434\u0430\u0442\u044C \u0435\u0433\u043E \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F \u0442\u0443\u0442 \u043D\u0435\u043A\u043E\u043C\u0443 (TASWM-528).`
17541
17907
  ].join("\n");
17542
17908
  }
17543
17909
  function ciTrustLine(prUrl, verifyMode) {
@@ -17795,17 +18161,11 @@ async function wakeRun(deps, runId) {
17795
18161
  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` };
17796
18162
  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` };
17797
18163
  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` };
17798
- const authorSideStopped = deps.db.events(run.id).some(
17799
- (e) => [RESOLVE_ESCALATION_MARK, CI_WAKE_ESCALATION_MARK, VERIFY_ESCALATION_MARK].some(
17800
- (m) => (e.text ?? "").includes(m)
17801
- )
17802
- );
17803
- const changesEscalated = deps.db.events(run.id).some((e) => (e.text ?? "").includes(`${CHANGES_WAKE_ESCALATION_MARK} (\u043A\u0440\u0443\u0433 ${run.review_cycle ?? 0})`));
17804
- const stoppedForHuman = deliberateReviewStop(deps.db, run) != null || changesEscalated || authorSideStopped;
18164
+ const stoppedForHuman = deps.db.events(run.id).some((e) => isEscalationText(e.text ?? "", run, "author"));
17805
18165
  if (run.status === "review" && run.pr_url && !needsFixIteration(run) && !stoppedForHuman) {
17806
18166
  return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0441\u0434\u0430\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E (${run.pr_url}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E; \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u043E\u0439 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443` };
17807
18167
  }
17808
- const worktreeExists = deps.worktreeExists ?? existsSync18;
18168
+ const worktreeExists = deps.worktreeExists ?? existsSync19;
17809
18169
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
17810
18170
  const wt = run.worktree_path;
17811
18171
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
@@ -17899,11 +18259,25 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
17899
18259
  }
17900
18260
  if (ci?.bucket === "pass" && await wakeReviewerOnCiReady(deps, run, { kind: "pass", ci })) return;
17901
18261
  const counts = ci && ci.total > 0 ? ` (${ci.pass}/${ci.total})` : "";
17902
- const note = ci == null ? "\u0436\u0434\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u043F\u0440\u043E\u0441\u0430" : ci.bucket === "pending" ? "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0435\u0449\u0451 \u0438\u0434\u0443\u0442" : ci.bucket === "none" ? "\u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E, workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C \u2014 \u0436\u0434\u0443" : "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0433\u043E\u0442\u043E\u0432\u044B, \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u043A\u043E\u0433\u043E";
17903
- const waiting = ci == null || ci.bucket === "pending" || ci.bucket === "none";
18262
+ const why = ci == null ? "\u043E\u043F\u0440\u043E\u0441 CI \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0446\u0435\u043B\u0438\u043A\u043E\u043C (checks \u0431\u0440\u043E\u0441\u0438\u043B) \u2014 \u0436\u0434\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u043F\u0440\u043E\u0441\u0430" : ciUnavailableNote(ci);
18263
+ const note = ci == null || ci.bucket === "unavailable" ? why : ci.bucket === "pending" ? "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0435\u0449\u0451 \u0438\u0434\u0443\u0442" : ci.bucket === "none" ? "\u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E, workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C \u2014 \u0436\u0434\u0443" : "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0433\u043E\u0442\u043E\u0432\u044B, \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u043A\u043E\u0433\u043E";
18264
+ const waiting = ci == null || ci.bucket === "unavailable" || ci.bucket === "pending" || ci.bucket === "none";
17904
18265
  const text = `CI ${ci?.bucket ?? "\u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"}${counts} \u2014 ${note}`;
17905
18266
  deps.db.addEvent(run.id, "ci_checked", waiting ? `${text} [${CI_WAIT_POLL_MARK}]` : text);
17906
18267
  }
18268
+ function ciUnavailableNote(ci) {
18269
+ const detail = ci.detail ? ` \u2014 ${ci.detail}` : "";
18270
+ switch (ci.reason) {
18271
+ case "timeout":
18272
+ return `\u0422\u0410\u0419\u041C\u0410\u0423\u0422: gh pr checks \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(GH_CALL_TIMEOUT_MS / 1e3)} \u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D${detail}. \u0421\u0435\u0442\u044C \u0443 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u0435\u0435, \u0447\u0435\u043C \u0443 \u0448\u0435\u043B\u043B\u0430 (\u043F\u0440\u043E\u043A\u0441\u0438/DNS) \u2014 \u0436\u0434\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u043F\u0440\u043E\u0441\u0430`;
18273
+ case "crashed":
18274
+ return `gh \u041D\u0415 \u0417\u0410\u041F\u0423\u0421\u0422\u0418\u041B\u0421\u042F${detail}. \u042D\u0442\u043E \u043D\u0435 \u0441\u0435\u0442\u044C: \u0443 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 gh \u0432 PATH \u2014 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
18275
+ case "unreadable":
18276
+ return `\u041D\u0415\u0427\u0418\u0422\u0410\u0415\u041C\u042B\u0419 \u041E\u0422\u0412\u0415\u0422 gh${detail}. \u0412\u044B\u0432\u043E\u0434 \u0435\u0441\u0442\u044C, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u043A\u043E\u0432 \u2014 \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442 gh, \u0430 \u043D\u0435 \u0436\u0434\u0430\u0442\u044C`;
18277
+ default:
18278
+ return `\u041F\u0423\u0421\u0422\u041E\u0419 \u041E\u0422\u0412\u0415\u0422: gh \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F, \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u0432 \u043D\u0438\u0447\u0435\u0433\u043E${detail}. \u0421\u043C\u043E\u0442\u0440\u0435\u0442\u044C stderr \u0432\u044B\u0437\u043E\u0432\u0430 \u0432 \u043B\u043E\u0433\u0435 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0442\u0430\u043C DNS/\u043F\u0440\u043E\u043A\u0441\u0438/\u043B\u0438\u043C\u0438\u0442`;
18279
+ }
18280
+ }
17907
18281
  function repoHasCi(deps, run) {
17908
18282
  if (deps.repoHasCi) return deps.repoHasCi(run);
17909
18283
  return repoHasCiWorkflows(run.worktree_path, run.repo);
@@ -17911,7 +18285,7 @@ function repoHasCi(deps, run) {
17911
18285
  function repoHasCiWorkflows(worktreePath, repo) {
17912
18286
  for (const base3 of [worktreePath, repo]) {
17913
18287
  if (!base3) continue;
17914
- const dir = join31(base3, ".github", "workflows");
18288
+ const dir = join32(base3, ".github", "workflows");
17915
18289
  try {
17916
18290
  if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
17917
18291
  } catch {
@@ -18097,7 +18471,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
18097
18471
  return true;
18098
18472
  }
18099
18473
  function worktreeAndTranscriptAlive(deps, worktree) {
18100
- const exists = deps.worktreeExists ?? existsSync18;
18474
+ const exists = deps.worktreeExists ?? existsSync19;
18101
18475
  const transcript = deps.hasTranscript ?? executorHasSession;
18102
18476
  return exists(worktree) && transcript(worktree);
18103
18477
  }
@@ -18606,7 +18980,7 @@ async function resumeWatchdogParked(deps, droneCap) {
18606
18980
  }
18607
18981
  if (!candidate) return;
18608
18982
  if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
18609
- const worktreeExists = deps.worktreeExists ?? existsSync18;
18983
+ const worktreeExists = deps.worktreeExists ?? existsSync19;
18610
18984
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
18611
18985
  const wt = candidate.worktree_path;
18612
18986
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
@@ -18775,7 +19149,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
18775
19149
  if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
18776
19150
  if (!deps.respawn) return;
18777
19151
  const wt = resumeCandidate.worktree_path;
18778
- const worktreeExists = deps.worktreeExists ?? existsSync18;
19152
+ const worktreeExists = deps.worktreeExists ?? existsSync19;
18779
19153
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
18780
19154
  const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
18781
19155
  const mode = canContinue ? "continue" : "fresh";
@@ -19035,7 +19409,7 @@ async function handleBudgetExhausted(deps, run, ctx) {
19035
19409
  function buildEscalationCandidates(db) {
19036
19410
  const candidates = [];
19037
19411
  for (const run of db.listRuns("review")) {
19038
- const hit = db.events(run.id).findLast((e) => NEEDS_HUMAN_MARKS.some((mark) => (e.text ?? "").includes(mark)));
19412
+ const hit = db.events(run.id).findLast((e) => isEscalationText(e.text ?? "", run, "supervision"));
19039
19413
  if (!hit) continue;
19040
19414
  candidates.push({
19041
19415
  key: `needs_human:${run.id}`,
@@ -19053,7 +19427,7 @@ function buildEscalationCandidates(db) {
19053
19427
  }
19054
19428
  async function tickOnce(deps) {
19055
19429
  const now = deps.now ? deps.now() : Date.now();
19056
- const worktreeExists = deps.worktreeExists ?? existsSync18;
19430
+ const worktreeExists = deps.worktreeExists ?? existsSync19;
19057
19431
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
19058
19432
  deps.sampleHost?.();
19059
19433
  if (deps.rehydrate) {
@@ -19099,12 +19473,12 @@ async function tickOnce(deps) {
19099
19473
  }
19100
19474
  }
19101
19475
  if (deps.repoMap) {
19102
- const rm3 = deps.repoMap;
19103
- const sinceLast = rm3.lastAt == null ? Infinity : now - rm3.lastAt;
19104
- if (sinceLast >= rm3.intervalMs) {
19105
- rm3.lastAt = now;
19476
+ const rm4 = deps.repoMap;
19477
+ const sinceLast = rm4.lastAt == null ? Infinity : now - rm4.lastAt;
19478
+ if (sinceLast >= rm4.intervalMs) {
19479
+ rm4.lastAt = now;
19106
19480
  try {
19107
- await rm3.run();
19481
+ await rm4.run();
19108
19482
  } catch (e) {
19109
19483
  console.error("[overmind] \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043A\u0430\u0440\u0442\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:", e);
19110
19484
  }
@@ -19479,7 +19853,8 @@ async function tickOnce(deps) {
19479
19853
  continue;
19480
19854
  }
19481
19855
  }
19482
- if (!alive && (outcome === "graceful" || outcome === "aborted") && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && run.kind !== "hero") {
19856
+ if (!alive && (outcome === "graceful" || outcome === "aborted") && run.status === "running" && !isSubrun(run) && // суб-дроны (резолвер/ревьюер/верификатор) — своими ветками ниже, TASWM-528
19857
+ run.kind !== "hero") {
19483
19858
  if (!wasAborted(outcome) && run.pr_url) {
19484
19859
  deps.db.updateRun(run.id, { status: "review", stop_reason: null });
19485
19860
  deps.db.addEvent(run.id, "step", `${SUBMITTED_EXIT_TEXT}: ${run.pr_url}`);
@@ -19523,7 +19898,8 @@ async function tickOnce(deps) {
19523
19898
  );
19524
19899
  continue;
19525
19900
  }
19526
- if (!alive && run.status === "running" && run.resolves_run_id == null && run.reviews_run_id == null && run.verifies_run_id == null && deps.respawn && run.respawns < RESPAWN_LIMIT) {
19901
+ if (!alive && run.status === "running" && !isSubrun(run) && // те же три роли одним предикатом — TASWM-528
19902
+ deps.respawn && run.respawns < RESPAWN_LIMIT) {
19527
19903
  const wt = run.worktree_path;
19528
19904
  const wtAlive = !!wt && worktreeExists(wt);
19529
19905
  const canContinue = wtAlive && hasTranscript(wt);
@@ -19568,32 +19944,59 @@ async function tickOnce(deps) {
19568
19944
  const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
19569
19945
  const ci = ciWatchOff ? null : await deps.ci.checks(run.pr_url).catch(() => null);
19570
19946
  const noCiRepo = ci?.bucket === "none" && !repoHasCi(deps, run);
19571
- if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied")) {
19572
- const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : ci.bucket === "denied" ? `\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : `CI ${ci.pass}/${ci.total}`;
19947
+ if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied" || ci.bucket === "unavailable")) {
19948
+ const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : ci.bucket === "denied" ? `\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : (
19949
+ // TASWM-497: и здесь причина СЛОВАМИ, а не общее «gh не ответил». Тот же словарь,
19950
+ // что в ленте опроса (callFailureWord), чтобы одно и то же состояние в журнале
19951
+ // прогона и в событии парковки не называлось двумя разными способами.
19952
+ ci.bucket === "unavailable" ? `\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043A\u0438 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: gh ${callFailureWord({ reason: ci.reason ?? "empty", detail: "" })}${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : `CI ${ci.pass}/${ci.total}`
19953
+ );
19573
19954
  deps.db.updateRun(run.id, { status: "paused", stop_reason: AWAITING_CI_REASON });
19574
19955
  deps.db.addEvent(run.id, "paused", `${AWAITING_CI_REASON} (${what}) \u2014 \u043D\u0435 \u0441\u043C\u0435\u0440\u0442\u044C: \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043A\u0438 \u0434\u043E\u0439\u0434\u0443\u0442 \u0434\u043E \u043A\u043E\u043D\u0446\u0430`);
19575
19956
  narrate(deps, `${droneLabel(run)} \u0443\u0448\u0451\u043B \u0436\u0434\u0430\u0442\u044C CI (${what}) \u2014 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u044E, \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A.`);
19576
19957
  continue;
19577
19958
  }
19578
19959
  }
19960
+ if (!alive && outcome === "graceful" && run.status === "running" && isSubrun(run)) {
19961
+ const wt = run.worktree_path;
19962
+ const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
19963
+ if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
19964
+ const n = deps.db.incRespawn(run.id);
19965
+ deps.db.addEvent(run.id, "respawned", `\u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0435 #${n}: ${SUBRUN_EXITED_TEXT} \u2014 --continue \u0441 \u043D\u0430\u043A\u0430\u0437\u043E\u043C \u043D\u0435 \u0436\u0434\u0430\u0442\u044C \u0444\u043E\u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0442\u044C \u0441\u043E\u0440\u0430\u0437\u043C\u0435\u0440\u043D\u043E \u043F\u0440\u0430\u0432\u043A\u0435 (TASWM-528)`);
19966
+ narrate(deps, `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B \u0445\u043E\u0434, \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u0432 done() \u2014 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u044E, \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E --continue \u0442\u043E\u0439 \u0436\u0435 \u0441\u0435\u0441\u0441\u0438\u0438.`);
19967
+ try {
19968
+ await deps.respawn(run, "continue", subrunWaitNudge(run));
19969
+ continue;
19970
+ } catch (e) {
19971
+ deps.db.addEvent(run.id, "step", `${SUBRUN_EXITED_TEXT}, \u043D\u043E --continue \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F: ${String(e)} \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u043E\u043F\u044B\u0442\u043A\u0443 \u043F\u043E \u043E\u0431\u0449\u0435\u0439 \u0432\u0435\u0442\u043A\u0435`);
19972
+ }
19973
+ } else {
19974
+ const why = !canContinue ? "worktree/\u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0442 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B" : !deps.respawn ? "\u0440\u0430\u0437\u0432\u043E\u0434\u043A\u0430 \u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u044F \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u0430" : `\u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u0430\u0432\u0442\u043E-\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0435\u043D\u0438\u0439 (${RESPAWN_LIMIT})`;
19975
+ deps.db.addEvent(run.id, "step", `${SUBRUN_EXITED_TEXT}, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: ${why} \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u043F\u043E\u043F\u044B\u0442\u043A\u0443 \u043F\u043E \u043E\u0431\u0449\u0435\u0439 \u0432\u0435\u0442\u043A\u0435 (TASWM-528)`);
19976
+ }
19977
+ }
19579
19978
  if (!alive && run.status === "running" && run.resolves_run_id != null) {
19580
19979
  const prUrl = await deliveredPrUrl(deps, run);
19581
19980
  if (run.worktree_path) await deps.removeWorktree(run.repo, run.worktree_path).catch(() => {
19582
19981
  });
19583
19982
  deps.db.updateRun(run.id, { status: "done" });
19983
+ const how = outcome === "graceful" ? "\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u0432\u044B\u0448\u0435\u043B \u0441\u0430\u043C, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C" : "\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u0443\u043C\u0435\u0440 \u0434\u043E done()";
19584
19984
  deps.db.addEvent(
19585
19985
  run.id,
19586
19986
  "done",
19587
- prUrl ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u0443\u043C\u0435\u0440 \u0434\u043E done() \u2014 \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 PR (${prUrl}), \u043F\u043E\u043F\u044B\u0442\u043A\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430; \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u043E\u0433\u043E PR \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 merge/CI/\u043F\u043E\u0432\u0442\u043E\u0440 (TASWM-224)` : "\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u0443\u043C\u0435\u0440 \u0434\u043E done() \u2014 \u043F\u043E\u043F\u044B\u0442\u043A\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430; \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u043E\u0433\u043E PR \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 merge/CI/\u043F\u043E\u0432\u0442\u043E\u0440 (TASWM-224)"
19987
+ prUrl ? `${how} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 PR (${prUrl}), \u043F\u043E\u043F\u044B\u0442\u043A\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430; \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u043E\u0433\u043E PR \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 merge/CI/\u043F\u043E\u0432\u0442\u043E\u0440 (TASWM-224)` : `${how} \u2014 \u043F\u043E\u043F\u044B\u0442\u043A\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430; \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u043E\u0433\u043E PR \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 merge/CI/\u043F\u043E\u0432\u0442\u043E\u0440 (TASWM-224)`
19588
19988
  );
19589
19989
  narrate(deps, `${droneLabel(run)} (\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440) \u0432\u044B\u0448\u0435\u043B \u0434\u043E done() \u2014 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u044E \u0431\u0435\u0437 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0441\u043A\u043E\u0433\u043E \u0430\u043B\u0451\u0440\u0442\u0430, \u0434\u0430\u043B\u044C\u0448\u0435 \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 PR.`);
19590
19990
  continue;
19591
19991
  }
19592
19992
  let reason = null;
19593
19993
  if (!alive) {
19594
- const crashNote = outcome === "crash" ? "\u0443\u0431\u0438\u0442 \u043D\u0430 \u043F\u043E\u043B\u043F\u0443\u0442\u0438 (\u043D\u0435\u0442 result-\u0444\u0430\u0439\u043B\u0430 \u2014 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0434\u043E \u0437\u0430\u043F\u0438\u0441\u0438 \u0438\u0442\u043E\u0433\u0430: OOM/\u043F\u0430\u043D\u0438\u043A\u0430/kill)" : "pid \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442";
19595
- reason = run.respawns >= RESPAWN_LIMIT ? `\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440 (${crashNote}): \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0439 (${RESPAWN_LIMIT})` : `\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440: ${crashNote}`;
19596
- narrate(deps, `${droneLabel(run)} \u043D\u0435 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u0435\u0431\u044F \u2014 \u0441\u0434\u0430\u044E\u0441\u044C, \u0434\u0430\u043B\u044C\u0448\u0435 \u043D\u0443\u0436\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440.`);
19994
+ const how = outcome === "graceful" ? "\u0434\u0440\u043E\u043D \u0432\u044B\u0448\u0435\u043B \u0441\u0430\u043C (\u0441\u0435\u0441\u0441\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430 \u0448\u0442\u0430\u0442\u043D\u043E), \u043D\u043E done() \u043D\u0435 \u043F\u043E\u0437\u0432\u0430\u043B, \u0430 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C" : `\u0434\u0440\u043E\u043D \u0443\u043C\u0435\u0440: ${outcome === "crash" ? "\u0443\u0431\u0438\u0442 \u043D\u0430 \u043F\u043E\u043B\u043F\u0443\u0442\u0438 (\u043D\u0435\u0442 result-\u0444\u0430\u0439\u043B\u0430 \u2014 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0434\u043E \u0437\u0430\u043F\u0438\u0441\u0438 \u0438\u0442\u043E\u0433\u0430: OOM/\u043F\u0430\u043D\u0438\u043A\u0430/kill)" : "pid \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442"}`;
19995
+ reason = run.respawns >= RESPAWN_LIMIT ? `${how} \u2014 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0439 (${RESPAWN_LIMIT})` : how;
19996
+ narrate(
19997
+ deps,
19998
+ outcome === "graceful" ? `${droneLabel(run)} \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B \u0445\u043E\u0434 \u0441\u0430\u043C, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0435\u0433\u043E \u0441\u0435\u0441\u0441\u0438\u044E \u043D\u0435\u0447\u0435\u043C \u2014 \u0434\u0430\u043B\u044C\u0448\u0435 \u043D\u0443\u0436\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440.` : `${droneLabel(run)} \u043D\u0435 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u0435\u0431\u044F \u2014 \u0441\u0434\u0430\u044E\u0441\u044C, \u0434\u0430\u043B\u044C\u0448\u0435 \u043D\u0443\u0436\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440.`
19999
+ );
19597
20000
  } else if (run.status === "running" && now - await livenessMs(deps, run, now, liveChildPpids) > STALL_MS) {
19598
20001
  reason = `\u0434\u0440\u043E\u043D \u0437\u0430\u0432\u0438\u0441: \u043C\u043E\u043B\u0447\u0438\u0442 > ${Math.round(STALL_MS / 6e4)} \u043C\u0438\u043D`;
19599
20002
  narrate(deps, `${droneLabel(run)} \u0437\u0430\u0442\u0438\u0445 \u0434\u043E\u043B\u044C\u0448\u0435 ${Math.round(STALL_MS / 6e4)} \u043C\u0438\u043D \u2014 \u043F\u043E\u043C\u0435\u0447\u0430\u044E \u0437\u0430\u0432\u0438\u0441\u0448\u0438\u043C, \u0433\u043B\u044F\u043D\u0443, \u0447\u0442\u043E \u0442\u0430\u043C.`);
@@ -19820,7 +20223,7 @@ async function resolveConflict(deps, reviewRun) {
19820
20223
  model: reviewRun.model,
19821
20224
  review_model: reviewRun.review_model
19822
20225
  });
19823
- const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
20226
+ const wt = join32(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19824
20227
  try {
19825
20228
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19826
20229
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -19899,7 +20302,7 @@ async function spawnReviewer(deps, reviewRun, files) {
19899
20302
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
19900
20303
  workspace_id: reviewRun.workspace_id
19901
20304
  });
19902
- const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
20305
+ const wt = join32(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19903
20306
  try {
19904
20307
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19905
20308
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
@@ -19980,7 +20383,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
19980
20383
  // штамп цикла: фикс-итерацию проверяет свежий верификатор
19981
20384
  workspace_id: verifiedRun.workspace_id
19982
20385
  });
19983
- const wt = join31(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
20386
+ const wt = join32(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19984
20387
  try {
19985
20388
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19986
20389
  await deps.git.setWorktreeIdentity(wt, verifier.name);
@@ -20026,7 +20429,7 @@ async function respawnRun(deps, run, mode, nudge) {
20026
20429
  ]);
20027
20430
  const base3 = effectiveBaseBranch(run.base_branch, projectBase);
20028
20431
  const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
20029
- if (!existsSync18(wt)) {
20432
+ if (!existsSync19(wt)) {
20030
20433
  const branch = runToSpawn.branch || runToSpawn.task_slug;
20031
20434
  const source = await addWorktree(repo, wt, branch, {
20032
20435
  ...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
@@ -20053,7 +20456,7 @@ async function respawnRun(deps, run, mode, nudge) {
20053
20456
  deps.db.updateRun(run.id, { status: "running", pid });
20054
20457
  }
20055
20458
  async function syncCrashedOnStartup(deps) {
20056
- const worktreeExists = deps.worktreeExists ?? existsSync18;
20459
+ const worktreeExists = deps.worktreeExists ?? existsSync19;
20057
20460
  let orphaned = 0;
20058
20461
  let merged = 0;
20059
20462
  let requeued = 0;
@@ -20207,7 +20610,7 @@ async function migrateWorktreesAtStartup() {
20207
20610
  legacyDir: WORKTREES_LEGACY_DIR,
20208
20611
  targetDir: WORKTREES_DIR,
20209
20612
  envOverride: process.env.SWARM_WORKTREES || "",
20210
- exists: (d) => existsSync18(d),
20613
+ exists: (d) => existsSync19(d),
20211
20614
  isNonEmptyDir: (d) => {
20212
20615
  try {
20213
20616
  return readdirSync13(d).length > 0;
@@ -20222,7 +20625,7 @@ async function migrateWorktreesAtStartup() {
20222
20625
  });
20223
20626
  } catch (e) {
20224
20627
  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);
20225
- return existsSync18(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
20628
+ return existsSync19(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
20226
20629
  }
20227
20630
  }
20228
20631
  async function reportWorkspaceAccess(tl, db) {
@@ -20695,7 +21098,7 @@ async function runDaemon(opts) {
20695
21098
  baseSync: (url) => prBaseSync(url),
20696
21099
  checks: async (url) => {
20697
21100
  const ci = await prCiStatus(url);
20698
- if (ci == null || ci.bucket === "denied") return null;
21101
+ if (ci.bucket === "denied" || ci.bucket === "unavailable") return null;
20699
21102
  return { bucket: ci.bucket };
20700
21103
  }
20701
21104
  } : {}
@@ -20875,7 +21278,7 @@ async function runDaemon(opts) {
20875
21278
  isDirty: (p) => worktreeDirty(p),
20876
21279
  releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
20877
21280
  pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
20878
- worktreeExists: existsSync18,
21281
+ worktreeExists: existsSync19,
20879
21282
  liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
20880
21283
  narrate: narrator.narrate
20881
21284
  });
@@ -20998,7 +21401,7 @@ async function runDaemon(opts) {
20998
21401
  mode,
20999
21402
  nudge
21000
21403
  ),
21001
- worktreeExists: (p) => existsSync18(p),
21404
+ worktreeExists: (p) => existsSync19(p),
21002
21405
  // TASWM-514: с этого момента считаем попытки спавна — счётчик живёт ровно одну жизнь
21003
21406
  // процесса демона и рестарт не переживает (см. preStartAttempts).
21004
21407
  startedAt: Date.now(),
@@ -21073,9 +21476,11 @@ async function runDaemon(opts) {
21073
21476
  setTimeout(() => {
21074
21477
  Promise.all([closeWebServer(web.server), droneChannel ? closeDroneChannel(droneChannel.server) : Promise.resolve()]).then(() => {
21075
21478
  if (!stopping) {
21076
- spawn3(process.execPath, respawnArgs, {
21479
+ spawn4(process.execPath, respawnArgs, {
21077
21480
  detached: true,
21078
- stdio: "ignore",
21481
+ // TASWM-497: в ЛОГ, а не в /dev/null. Со `stdio: "ignore"` демон после каждого
21482
+ // `update`/`restart` замолкал навсегда, и разбирать инциденты по логу было нечем.
21483
+ stdio: respawnStdio(DAEMON_LOG_PATH),
21079
21484
  cwd: process.cwd(),
21080
21485
  env: { ...process.env, SWARM_REPO: opts.repo }
21081
21486
  }).unref();
@@ -21482,6 +21887,7 @@ export {
21482
21887
  STALL_MS,
21483
21888
  STORM_DEAD_LIMIT,
21484
21889
  SUBMITTED_EXIT_TEXT,
21890
+ SUBRUN_EXITED_TEXT,
21485
21891
  VERIFY_ESCALATION_MARK,
21486
21892
  VERIFY_SPAWN_LIMIT,
21487
21893
  WATCHDOG_PARK_REASON,
@@ -21497,6 +21903,7 @@ export {
21497
21903
  buildVerifierPrompt,
21498
21904
  cancelRun,
21499
21905
  hasLiveDescendant,
21906
+ isSubrun,
21500
21907
  lastChangesSummary,
21501
21908
  listDroneProcesses,
21502
21909
  livePpids,
@@ -21511,6 +21918,7 @@ export {
21511
21918
  runDaemon,
21512
21919
  spawnReviewer,
21513
21920
  spawnVerifier,
21921
+ subrunWaitNudge,
21514
21922
  syncCrashedOnStartup,
21515
21923
  tickOnce,
21516
21924
  verdictCommentMark,