@taskless-app/swarm 0.6.57 → 0.6.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/overmind.js CHANGED
@@ -665,6 +665,11 @@ function defaultModelFor(role, providerId = null) {
665
665
  const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
666
666
  return provider.roles[role];
667
667
  }
668
+ function executorIdForModelRef(ref) {
669
+ const providerId = parseModelRef(ref)?.providerId ?? DEFAULT_PROVIDER_ID;
670
+ const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
671
+ return provider.executorId;
672
+ }
668
673
  var HARNESS_DEFAULT_MODEL, MODEL_PROVIDERS, DEFAULT_PROVIDER_ID;
669
674
  var init_modelPolicy = __esm({
670
675
  "../swarm-core/src/modelPolicy.ts"() {
@@ -678,6 +683,21 @@ var init_modelPolicy = __esm({
678
683
  selectable: true,
679
684
  roles: { executor: "claude-opus-5-5", review: "claude-sonnet-5" }
680
685
  },
686
+ // TASWM-593 · GLM (Z.ai), INTERIM: подписка GLM Coding Plan работает через официальный
687
+ // Anthropic-совместимый эндпоинт Z.ai (api.z.ai/api/anthropic), поэтому харнесс — тот же
688
+ // claude-code, а всё различие провайдера — env процесса дрона (BASE_URL + токен, см.
689
+ // providerEnv в адаптере; клаудовский аккаунт НЕ нужен — бинарь бьёт в Z.ai). Interim
690
+ // для роя владельца до появления `--model` у ZCode CLI: тогда провайдер переезжает на
691
+ // собственный zcode-адаптер (волт reference_zcode_cli_headless_research).
692
+ // Дефолты ролей: glm-5.3 — флагман; каталог подписки снаружи не виден, имя сверено
693
+ // живой пробой, выдуманных имён в реестре нет — настройкой можно поставить любую.
694
+ {
695
+ id: "zai",
696
+ label: "GLM (Z.ai)",
697
+ executorId: "claude-code",
698
+ selectable: true,
699
+ roles: { executor: "glm-5.3", review: "glm-5.3" }
700
+ },
681
701
  // TASWM-565 · шаг 1 из четырёх: провайдер в реестре ЕСТЬ, выбрать его ещё нельзя.
682
702
  // Имён моделей здесь нет СОЗНАТЕЛЬНО: у владельца подписка, состав моделей в ней нам
683
703
  // неизвестен, а выдуманное имя хуже маркера «решает Codex».
@@ -2515,10 +2535,25 @@ function writeMcpConfig(spec) {
2515
2535
  writeFileSync3(path, JSON.stringify(cfg, null, 2));
2516
2536
  return path;
2517
2537
  }
2538
+ function providerEnv(providerId, model, env = process.env) {
2539
+ const compat = ANTHROPIC_COMPAT_PROVIDERS[providerId];
2540
+ if (!compat) return {};
2541
+ const token = (env[compat.tokenEnv] ?? "").trim();
2542
+ if (!token) {
2543
+ throw new Error(
2544
+ `\u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 "${providerId}": \u043D\u0435 \u0437\u0430\u0434\u0430\u043D ${compat.tokenEnv} \u2014 \u043A\u043B\u044E\u0447 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u043A\u043B\u0430\u0434\u0451\u0442\u0441\u044F \u0432 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0414\u0415\u041C\u041E\u041D\u0410 (env \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u0438\u043B\u0438 .env \u043C\u0430\u0448\u0438\u043D\u044B), \u0432 \u0442\u0440\u0435\u043A\u0435\u0440\u0435 \u043E\u043D \u043D\u0435 \u0445\u0440\u0430\u043D\u0438\u0442\u0441\u044F. \u0411\u0435\u0437 \u043A\u043B\u044E\u0447\u0430 \u0434\u0440\u043E\u043D \u0443\u043C\u0440\u0451\u0442 \u043D\u0430 \u043F\u0435\u0440\u0432\u043E\u043C \u0436\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0435 \u0438 \u0441\u043E\u0439\u0434\u0451\u0442 \u0437\u0430 \u0441\u0431\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u2014 \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0438\u043C \u0432\u043E\u0432\u0441\u0435.`
2545
+ );
2546
+ }
2547
+ return {
2548
+ ANTHROPIC_BASE_URL: (env[compat.baseUrlEnv] ?? "").trim() || compat.baseUrlDefault,
2549
+ ANTHROPIC_AUTH_TOKEN: token,
2550
+ ...model ? { ANTHROPIC_MODEL: model, ANTHROPIC_SMALL_FAST_MODEL: model } : {}
2551
+ };
2552
+ }
2518
2553
  function abortField(abort) {
2519
2554
  return abort ? { aborted: abort } : {};
2520
2555
  }
2521
- var ISOLATION_FLAGS, MCP_CONFIG_FILE, claudeCodeExecutor;
2556
+ var ISOLATION_FLAGS, MCP_CONFIG_FILE, ANTHROPIC_COMPAT_PROVIDERS, PROVIDER_TOKEN_ENV, claudeCodeExecutor;
2522
2557
  var init_claudeCode = __esm({
2523
2558
  "src/executors/claudeCode.ts"() {
2524
2559
  "use strict";
@@ -2529,21 +2564,31 @@ var init_claudeCode = __esm({
2529
2564
  init_transcriptSpend();
2530
2565
  ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
2531
2566
  MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
2567
+ ANTHROPIC_COMPAT_PROVIDERS = {
2568
+ zai: {
2569
+ baseUrlDefault: "https://api.z.ai/api/anthropic",
2570
+ baseUrlEnv: "SWARM_ZAI_BASE_URL",
2571
+ tokenEnv: "SWARM_ZAI_TOKEN"
2572
+ }
2573
+ };
2574
+ PROVIDER_TOKEN_ENV = Object.values(ANTHROPIC_COMPAT_PROVIDERS).map((p) => p.tokenEnv);
2532
2575
  claudeCodeExecutor = {
2533
2576
  id: "claude-code",
2534
2577
  title: "Claude Code (claude)",
2535
2578
  binEnv: "SWARM_CLAUDE_BIN",
2536
2579
  install: { what: "Claude Code CLI \u2014 \u0438\u043C \u0434\u0440\u043E\u043D \u043F\u0438\u0448\u0435\u0442 \u043A\u043E\u0434", pkg: "@anthropic-ai/claude-code" },
2537
- resolveBin: () => resolveClaudeBin(),
2580
+ resolveBin: (env) => resolveClaudeBin(env ? { env } : {}),
2538
2581
  // Стрелка, а не метод: `plan` могут вызвать в отрыве от объекта (деструктуризация в тестах,
2539
2582
  // проброс колбэком), и `this` тогда уедет в undefined.
2540
2583
  plan: (spec) => {
2541
2584
  const cfgPath = writeMcpConfig(spec);
2542
2585
  const hook = droneMergeGateHookPath(spec.worktree);
2543
2586
  const settings = spec.denyPaths.length > 0 || hook ? droneSettingsJson(spec.denyPaths, hook) : void 0;
2587
+ const providerEnvVars = providerEnv(spec.providerId, spec.model);
2544
2588
  return {
2545
2589
  bin: resolveClaudeBin(),
2546
- args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
2590
+ args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort),
2591
+ ...Object.keys(providerEnvVars).length > 0 ? { env: providerEnvVars } : {}
2547
2592
  };
2548
2593
  },
2549
2594
  parseResult(raw, model) {
@@ -2595,7 +2640,12 @@ var init_claudeCode = __esm({
2595
2640
  probe: {
2596
2641
  args: [...ISOLATION_FLAGS, "-p", "ok", "--max-turns", "1"],
2597
2642
  // TASWM-214: исчерпанный лимит подписки — НЕ шторм провайдера, и путать их нельзя.
2598
- limitPattern: /(weekly limit|usage limit|hit your .*limit)/i
2643
+ limitPattern: /(weekly limit|usage limit|hit your .*limit)/i,
2644
+ // TASWM-592: отвергнутый доступ — не «шторм»: ключ протух, аккаунт забанен/отключён,
2645
+ // регион закрыт (403 без VPN — живой кейс этого роя). Коды — только в контексте
2646
+ // ошибки (ревью PR #1482): голое «403» в arbitrary-выводе и POSIX permission denied
2647
+ // от локальной ФС советовали бы «чинить вход» не про ту болезнь.
2648
+ authPattern: /(?:error|api|http|status|unauthorized|forbidden)[^\n]{0,60}\b40[13]\b|\b40[13]\b[^\n]{0,60}(?:error|unauthorized|forbidden)|authentication_?error|unauthorized|invalid api key|account[^.\n]{0,40}?(?:suspended|banned|disabled)/i
2599
2649
  }
2600
2650
  };
2601
2651
  }
@@ -2726,7 +2776,7 @@ var init_codex = __esm({
2726
2776
  title: "Codex CLI (codex)",
2727
2777
  binEnv: "SWARM_CODEX_BIN",
2728
2778
  install: { what: "Codex CLI \u2014 \u0430\u043B\u044C\u0442\u0435\u0440\u043D\u0430\u0442\u0438\u0432\u043D\u044B\u0439 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C \u0434\u0440\u043E\u043D\u0430", pkg: "@openai/codex" },
2729
- resolveBin: () => resolveCodexBin(),
2779
+ resolveBin: (env) => resolveCodexBin(env ?? process.env),
2730
2780
  // Стрелка, а не метод — см. тот же комментарий в claudeCode.ts.
2731
2781
  plan: (spec) => {
2732
2782
  const notice = skillsNotice(spec.skillDirs);
@@ -2775,12 +2825,48 @@ ${notice}` : spec.prompt;
2775
2825
  // Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
2776
2826
  // провайдера разом. Один короткий ответ, без тулов и без сети сверх модели.
2777
2827
  args: ["exec", "--json", SKIP_GIT_CHECK_FLAG, BYPASS_FLAG, "ok"],
2828
+ // TASWM-592: не вошёл/ключ отвергнут — не шторм; точные формулировки codex уточним
2829
+ // на живом исполнителе, поэтому паттерн широкий.
2830
+ authPattern: /\b(401|403)\b|not logged in|unauthorized|authentication|invalid api key/i,
2778
2831
  limitPattern: /(usage limit|rate limit|quota exceeded|insufficient_quota)/i
2779
2832
  }
2780
2833
  };
2781
2834
  }
2782
2835
  });
2783
2836
 
2837
+ // src/swarmModels.ts
2838
+ function pick2(v) {
2839
+ const s = typeof v === "string" ? v.trim() : "";
2840
+ return s.length > 0 ? s : null;
2841
+ }
2842
+ function isReviewKind(kind) {
2843
+ return kind === "reviewer" || kind === "verifier";
2844
+ }
2845
+ function modelRoleOf(kind) {
2846
+ return isReviewKind(kind) ? "review" : "executor";
2847
+ }
2848
+ function forKind(kind, swarm, norm) {
2849
+ if (!swarm) return null;
2850
+ return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
2851
+ }
2852
+ function resolveRunModelRef(run, swarm, env = process.env) {
2853
+ const configured = parseModelRef(
2854
+ pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
2855
+ );
2856
+ if (configured) return configured;
2857
+ const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
2858
+ return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
2859
+ }
2860
+ function resolveRunEffort(run, swarm, env = process.env) {
2861
+ return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
2862
+ }
2863
+ var init_swarmModels = __esm({
2864
+ "src/swarmModels.ts"() {
2865
+ "use strict";
2866
+ init_src();
2867
+ }
2868
+ });
2869
+
2784
2870
  // src/executors/types.ts
2785
2871
  var init_types2 = __esm({
2786
2872
  "src/executors/types.ts"() {
@@ -2798,12 +2884,22 @@ function resolveExecutor(id) {
2798
2884
  }
2799
2885
  return found;
2800
2886
  }
2887
+ function executorForRun(run, swarm, env = process.env, pinned = null) {
2888
+ const ref = pinned ?? resolveRunModelRef(run, swarm, env);
2889
+ if (ref.providerId === DEFAULT_PROVIDER_ID) {
2890
+ const envExecutor = (env.SWARM_EXECUTOR ?? "").trim();
2891
+ if (envExecutor) return resolveExecutor(envExecutor);
2892
+ }
2893
+ return resolveExecutor(executorIdForModelRef(`${ref.providerId}/${ref.model}`));
2894
+ }
2801
2895
  var DEFAULT_EXECUTOR_ID, EXECUTORS;
2802
2896
  var init_executors = __esm({
2803
2897
  "src/executors/index.ts"() {
2804
2898
  "use strict";
2899
+ init_src();
2805
2900
  init_claudeCode();
2806
2901
  init_codex();
2902
+ init_swarmModels();
2807
2903
  init_types2();
2808
2904
  init_claudeCode();
2809
2905
  init_codex();
@@ -4273,7 +4369,7 @@ var init_procCleanup = __esm({
4273
4369
  // src/git.ts
4274
4370
  import { existsSync as existsSync12, readdirSync as readdirSync12, realpathSync as realpathSync2, statSync as statSync10 } from "fs";
4275
4371
  import { appendFile, chmod, mkdir, readFile, rename, rm, writeFile } from "fs/promises";
4276
- import { isAbsolute as isAbsolute2, join as join20, resolve as resolve3, sep as sep2 } from "path";
4372
+ import { isAbsolute as isAbsolute3, join as join21, resolve as resolve3, sep as sep2 } from "path";
4277
4373
  import { $ as $2 } from "execa";
4278
4374
  function gitNet(repo, args, timeoutMs) {
4279
4375
  return withHardTimeout(
@@ -4302,7 +4398,7 @@ async function inspectWorktreePath(path) {
4302
4398
  return "orphan";
4303
4399
  }
4304
4400
  try {
4305
- if (statSync10(join20(path, ".git")).isDirectory()) return "main-checkout";
4401
+ if (statSync10(join21(path, ".git")).isDirectory()) return "main-checkout";
4306
4402
  } catch {
4307
4403
  }
4308
4404
  const top = await $2({ reject: false })`git -C ${path} rev-parse --show-toplevel`;
@@ -4362,7 +4458,7 @@ async function parkOrphanDir(repo, path, branch) {
4362
4458
  }
4363
4459
  function isLinkedWorktree(dir) {
4364
4460
  try {
4365
- return statSync10(join20(dir, ".git")).isFile();
4461
+ return statSync10(join21(dir, ".git")).isFile();
4366
4462
  } catch {
4367
4463
  return false;
4368
4464
  }
@@ -4474,8 +4570,8 @@ async function ensureGitExcluded(worktree, pattern) {
4474
4570
  try {
4475
4571
  const commonDir = await gitPath(worktree, "--git-common-dir");
4476
4572
  if (!commonDir) return false;
4477
- const infoDir = join20(commonDir, "info");
4478
- const excludeFile = join20(infoDir, "exclude");
4573
+ const infoDir = join21(commonDir, "info");
4574
+ const excludeFile = join21(infoDir, "exclude");
4479
4575
  let current = "";
4480
4576
  try {
4481
4577
  current = await readFile(excludeFile, "utf8");
@@ -4495,7 +4591,7 @@ async function gitPath(worktree, flag) {
4495
4591
  const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
4496
4592
  const raw = out.stdout.trim();
4497
4593
  if (out.exitCode !== 0 || !raw) return null;
4498
- return isAbsolute2(raw) ? raw : join20(worktree, raw);
4594
+ return isAbsolute3(raw) ? raw : join21(worktree, raw);
4499
4595
  }
4500
4596
  async function ensureWorktreeHygiene(worktree) {
4501
4597
  for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
@@ -4505,12 +4601,12 @@ async function installGuardHooks(worktree) {
4505
4601
  try {
4506
4602
  const gitDir = await gitPath(worktree, "--git-dir");
4507
4603
  if (!gitDir) return false;
4508
- const hooksDir = join20(gitDir, "swarm-hooks");
4604
+ const hooksDir = join21(gitDir, "swarm-hooks");
4509
4605
  const prev = await previousHooksPath(worktree);
4510
4606
  await mkdir(hooksDir, { recursive: true });
4511
4607
  for (const hook of GUARD_HOOKS) {
4512
- await writeFile(join20(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
4513
- await chmod(join20(hooksDir, hook), 493);
4608
+ await writeFile(join21(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
4609
+ await chmod(join21(hooksDir, hook), 493);
4514
4610
  }
4515
4611
  await $2`git -C ${worktree} config extensions.worktreeConfig true`;
4516
4612
  await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
@@ -4529,7 +4625,7 @@ async function previousHooksPath(worktree) {
4529
4625
  if (out.exitCode === 0 && value) return value;
4530
4626
  }
4531
4627
  const commonDir = await gitPath(worktree, "--git-common-dir");
4532
- return commonDir ? join20(commonDir, "hooks") : null;
4628
+ return commonDir ? join21(commonDir, "hooks") : null;
4533
4629
  }
4534
4630
  async function removeWorktree(repo, path) {
4535
4631
  const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
@@ -4554,7 +4650,7 @@ async function worktreeDirty(path) {
4554
4650
  async function repairMovedWorktrees(targetDir) {
4555
4651
  const subdirs = (dir) => {
4556
4652
  try {
4557
- return readdirSync12(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join20(dir, d.name));
4653
+ return readdirSync12(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join21(dir, d.name));
4558
4654
  } catch {
4559
4655
  return [];
4560
4656
  }
@@ -5092,7 +5188,7 @@ var init_droneChannel = __esm({
5092
5188
 
5093
5189
  // src/attachments.ts
5094
5190
  import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
5095
- import { join as join24 } from "path";
5191
+ import { join as join25 } from "path";
5096
5192
  function attachmentsFromDoc(doc, origin) {
5097
5193
  const out = [];
5098
5194
  const walk2 = (node) => {
@@ -5183,7 +5279,7 @@ async function materializeAttachments(opts) {
5183
5279
  };
5184
5280
  const result = { files: [], skipped: [] };
5185
5281
  if (opts.refs.length === 0) return result;
5186
- const dir = join24(opts.worktree, ATTACHMENTS_DIR);
5282
+ const dir = join25(opts.worktree, ATTACHMENTS_DIR);
5187
5283
  await io.removeDir(dir);
5188
5284
  let dirReady = false;
5189
5285
  let total = 0;
@@ -5229,7 +5325,7 @@ async function materializeAttachments(opts) {
5229
5325
  await io.mkdir(dir);
5230
5326
  dirReady = true;
5231
5327
  }
5232
- await io.writeFile(join24(dir, name), file.bytes);
5328
+ await io.writeFile(join25(dir, name), file.bytes);
5233
5329
  } catch (e) {
5234
5330
  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)})`);
5235
5331
  continue;
@@ -5382,7 +5478,7 @@ var init_attachments = __esm({
5382
5478
 
5383
5479
  // src/shotUpload.ts
5384
5480
  import { readFile as fsReadFile } from "fs/promises";
5385
- import { basename as basename4, isAbsolute as isAbsolute3, join as join25 } from "path";
5481
+ import { basename as basename4, isAbsolute as isAbsolute4, join as join26 } from "path";
5386
5482
  function shotMimetype(path) {
5387
5483
  const dot = path.lastIndexOf(".");
5388
5484
  if (dot < 0) return null;
@@ -5408,7 +5504,7 @@ async function readShots(paths, opts) {
5408
5504
  }
5409
5505
  let bytes;
5410
5506
  try {
5411
- bytes = await read(isAbsolute3(path) ? path : join25(opts.worktree, path));
5507
+ bytes = await read(isAbsolute4(path) ? path : join26(opts.worktree, path));
5412
5508
  } catch (e) {
5413
5509
  skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
5414
5510
  continue;
@@ -5490,7 +5586,7 @@ var init_shotUpload = __esm({
5490
5586
  // src/oauth.ts
5491
5587
  import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
5492
5588
  import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, unlinkSync as unlinkSync4 } from "fs";
5493
- import { join as join26 } from "path";
5589
+ import { join as join27 } from "path";
5494
5590
  function makePkce() {
5495
5591
  const verifier = randomBytes2(48).toString("base64url");
5496
5592
  const challenge = createHash3("sha256").update(verifier).digest("base64url");
@@ -5625,8 +5721,8 @@ var init_oauth = __esm({
5625
5721
  AUTHORIZE = "/api/oauth/authorize";
5626
5722
  TOKEN = "/api/oauth/token";
5627
5723
  SCOPE = "openid profile email taskless:full";
5628
- STORE_PATH2 = join26(SWARM_HOME, "oauth.json");
5629
- CLIENT_PATH = join26(SWARM_HOME, "oauth-client.json");
5724
+ STORE_PATH2 = join27(SWARM_HOME, "oauth.json");
5725
+ CLIENT_PATH = join27(SWARM_HOME, "oauth-client.json");
5630
5726
  base2 = (api) => api.replace(/\/$/, "");
5631
5727
  }
5632
5728
  });
@@ -5673,7 +5769,7 @@ var init_authState = __esm({
5673
5769
 
5674
5770
  // src/workspace.ts
5675
5771
  import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5 } from "fs";
5676
- import { join as join27 } from "path";
5772
+ import { join as join28 } from "path";
5677
5773
  function loadWorkspace() {
5678
5774
  try {
5679
5775
  const w = JSON.parse(readFileSync21(WORKSPACE_PATH, "utf8"));
@@ -5690,7 +5786,7 @@ var init_workspace = __esm({
5690
5786
  "src/workspace.ts"() {
5691
5787
  "use strict";
5692
5788
  init_config();
5693
- WORKSPACE_PATH = join27(SWARM_HOME, "workspace.json");
5789
+ WORKSPACE_PATH = join28(SWARM_HOME, "workspace.json");
5694
5790
  }
5695
5791
  });
5696
5792
 
@@ -7322,7 +7418,7 @@ var init_droneStore = __esm({
7322
7418
  // src/overmind.ts
7323
7419
  import { homedir as homedir7 } from "os";
7324
7420
  import { existsSync as existsSync20, readFileSync as readFileSync25, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync13 } from "fs";
7325
- import { basename as basename6, dirname as dirname5, join as join34 } from "path";
7421
+ import { basename as basename6, dirname as dirname5, join as join35 } from "path";
7326
7422
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
7327
7423
  import { spawn as spawn4 } from "child_process";
7328
7424
 
@@ -9500,7 +9596,7 @@ var SwarmClient = class _SwarmClient {
9500
9596
  *
9501
9597
  * Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
9502
9598
  */
9503
- async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick) {
9599
+ async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick, executors) {
9504
9600
  try {
9505
9601
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
9506
9602
  if (daemonVersion) args.daemonVersion = daemonVersion;
@@ -9530,6 +9626,7 @@ var SwarmClient = class _SwarmClient {
9530
9626
  if (tick && Number.isFinite(tick.seq) && Number.isFinite(tick.ageMs) && tick.intervalMs > 0) {
9531
9627
  args.tick = { seq: Math.max(0, Math.trunc(tick.seq)), ageMs: Math.max(0, Math.trunc(tick.ageMs)), intervalMs: Math.trunc(tick.intervalMs) };
9532
9628
  }
9629
+ if (executors) args.executorsAvailable = executors;
9533
9630
  const res = await this.call("swarm_heartbeat", args);
9534
9631
  if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
9535
9632
  this.heartbeatOkAt = this.now();
@@ -11179,39 +11276,10 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
11179
11276
  };
11180
11277
  }
11181
11278
 
11182
- // src/swarmModels.ts
11183
- init_src();
11184
- function pick2(v) {
11185
- const s = typeof v === "string" ? v.trim() : "";
11186
- return s.length > 0 ? s : null;
11187
- }
11188
- function isReviewKind(kind) {
11189
- return kind === "reviewer" || kind === "verifier";
11190
- }
11191
- function modelRoleOf(kind) {
11192
- return isReviewKind(kind) ? "review" : "executor";
11193
- }
11194
- function forKind(kind, swarm, norm) {
11195
- if (!swarm) return null;
11196
- return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
11197
- }
11198
- function resolveRunModelRef(run, swarm, env = process.env) {
11199
- const configured = parseModelRef(
11200
- pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
11201
- );
11202
- if (configured) return configured;
11203
- const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
11204
- return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
11205
- }
11206
- function resolveRunModel(run, swarm, env = process.env) {
11207
- return resolveRunModelRef(run, swarm, env).model;
11208
- }
11209
- function resolveRunEffort(run, swarm, env = process.env) {
11210
- return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
11211
- }
11212
-
11213
11279
  // src/launcher.ts
11280
+ init_swarmModels();
11214
11281
  init_executors();
11282
+ init_claudeCode();
11215
11283
  init_src();
11216
11284
  var requireFromHere = createRequire(import.meta.url);
11217
11285
  function resolveTsxCli() {
@@ -11262,7 +11330,10 @@ var DAEMON_STATE_ENV = [
11262
11330
  "SWARM_OUTBOX",
11263
11331
  "SWARM_HEARTBEAT",
11264
11332
  "SWARM_REVIEW_CUTOFF",
11265
- "TASKLESS_TOKEN"
11333
+ "TASKLESS_TOKEN",
11334
+ // TASWM-593 (ревью PR #1483): машинные ключи совместимых провайдеров — дрону не наследуются;
11335
+ // доходит только переведённый ANTHROPIC_AUTH_TOKEN из plan.env, который мешается ПОСЛЕ чистки.
11336
+ ...PROVIDER_TOKEN_ENV
11266
11337
  ];
11267
11338
  function bundledRoleDir(kind) {
11268
11339
  return join17(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
@@ -11361,13 +11432,18 @@ function makeLauncher(cfg) {
11361
11432
  ...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
11362
11433
  }
11363
11434
  };
11364
- const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
11435
+ const modelRef = resolveRunModelRef(run, cfg.swarmModels?.() ?? null);
11365
11436
  const effort = resolveRunEffort(run, cfg.swarmEfforts?.() ?? null);
11366
11437
  try {
11367
11438
  cfg.recordEffort?.(run.id, effort);
11368
11439
  } catch (e) {
11369
11440
  console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0441\u0438\u043B\u0438\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
11370
11441
  }
11442
+ try {
11443
+ cfg.recordModelRef?.(run.id, modelRef);
11444
+ } catch (e) {
11445
+ console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u043C\u043E\u0434\u0435\u043B\u044C \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
11446
+ }
11371
11447
  const realHome = cfg.droneIsolation?.realHome ?? homedir6();
11372
11448
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
11373
11449
  if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync11(join17(cfg.droneIsolation.swarmHome, "token"))) {
@@ -11375,12 +11451,13 @@ function makeLauncher(cfg) {
11375
11451
  `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join17(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
11376
11452
  );
11377
11453
  }
11378
- const executor = cfg.executor ?? resolveExecutor();
11454
+ const executor = cfg.executor ?? executorForRun(run, cfg.swarmModels?.() ?? null);
11379
11455
  const plan = executor.plan({
11380
11456
  worktree,
11381
11457
  prompt,
11382
11458
  continueSession: !!continueSession,
11383
- model,
11459
+ model: modelRef.model,
11460
+ providerId: modelRef.providerId,
11384
11461
  mcp,
11385
11462
  skillDirs: roleDirs(worktree, run.kind),
11386
11463
  // Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
@@ -11436,6 +11513,7 @@ init_procCleanup();
11436
11513
  import { readFileSync as readFileSync15, readdirSync as readdirSync10, renameSync as renameSync9, statSync as statSync9 } from "fs";
11437
11514
  import { join as join18 } from "path";
11438
11515
  init_executors();
11516
+ init_swarmModels();
11439
11517
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
11440
11518
  var SEGMENT_LOG_RE = /^(\d+)\.json(\.done)?$/;
11441
11519
  var RESCUE_OPEN_WINDOW_MAX_MS = 12 * 60 * 60 * 1e3;
@@ -11499,8 +11577,9 @@ function accountRunCost(deps, run) {
11499
11577
  const dir = findRunLogDir(deps.runLogsDir, deps.db.runLogKeys(run.id));
11500
11578
  const files = dir ? unprocessedLogFiles(dir) : [];
11501
11579
  if (!dir || files.length === 0) return;
11502
- const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
11503
- const executor = deps.executor ?? resolveExecutor();
11580
+ const pinnedRef = deps.modelRefFor?.(run) ?? null;
11581
+ const model = (pinnedRef ?? resolveRunModelRef(run, deps.swarmModels?.() ?? null)).model;
11582
+ const executor = deps.executor ?? executorForRun(run, deps.swarmModels?.() ?? null, process.env, pinnedRef);
11504
11583
  const now = deps.now?.() ?? Date.now();
11505
11584
  const starts = segmentStarts(dir);
11506
11585
  let added = 0;
@@ -11614,13 +11693,67 @@ function abortOfExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
11614
11693
  if (raw == null) return null;
11615
11694
  return executor.parseResult(raw, null)?.aborted ?? null;
11616
11695
  }
11617
- function makeExitClassifier(runLogsDir, runLogKeys, executor) {
11618
- const exec = executor ?? resolveExecutor();
11619
- return (run) => classifyExit(runLogsDir, runLogKeys(run.id), exec);
11696
+ function makeExitClassifier(runLogsDir, runLogKeys, executorFor = () => resolveExecutor()) {
11697
+ return (run) => classifyExit(runLogsDir, runLogKeys(run.id), executorFor(run));
11620
11698
  }
11621
- function makeAbortReader(runLogsDir, runLogKeys, executor) {
11622
- const exec = executor ?? resolveExecutor();
11623
- return (run) => abortOfExit(runLogsDir, runLogKeys(run.id), exec);
11699
+ function makeAbortReader(runLogsDir, runLogKeys, executorFor = () => resolveExecutor()) {
11700
+ return (run) => abortOfExit(runLogsDir, runLogKeys(run.id), executorFor(run));
11701
+ }
11702
+
11703
+ // src/overmind.ts
11704
+ init_executors();
11705
+ init_claudeCode();
11706
+ init_swarmModels();
11707
+
11708
+ // src/executors/detect.ts
11709
+ init_executors();
11710
+ import { accessSync, constants } from "fs";
11711
+ import { delimiter, join as join20 } from "path";
11712
+ function binRunnable(bin, env) {
11713
+ if (!bin) return false;
11714
+ if (bin.includes("/") || bin.includes("\\")) {
11715
+ try {
11716
+ accessSync(bin, constants.X_OK);
11717
+ return true;
11718
+ } catch {
11719
+ return false;
11720
+ }
11721
+ }
11722
+ const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
11723
+ for (const dir of (env.PATH ?? "").split(delimiter)) {
11724
+ if (!dir) continue;
11725
+ for (const ext of exts) {
11726
+ try {
11727
+ accessSync(join20(dir, bin + ext), constants.X_OK);
11728
+ return true;
11729
+ } catch {
11730
+ }
11731
+ }
11732
+ }
11733
+ return false;
11734
+ }
11735
+ function detectInstalledExecutors(env = process.env) {
11736
+ return Object.values(EXECUTORS).map((executor) => {
11737
+ let bin = "";
11738
+ try {
11739
+ bin = executor.resolveBin(env);
11740
+ } catch {
11741
+ bin = "";
11742
+ }
11743
+ return { id: executor.id, title: executor.title, bin, installed: binRunnable(bin, env) };
11744
+ });
11745
+ }
11746
+ function installedExecutorIds(env = process.env) {
11747
+ return detectInstalledExecutors(env).filter((e) => e.installed).map((e) => e.id);
11748
+ }
11749
+ var cachedAt = Number.NaN;
11750
+ var cachedIds = [];
11751
+ function cachedInstalledExecutorIds(now = Date.now(), ttlMs = 3e5, env = process.env) {
11752
+ if (!Number.isFinite(cachedAt) || now - cachedAt > ttlMs) {
11753
+ cachedAt = now;
11754
+ cachedIds = installedExecutorIds(env);
11755
+ }
11756
+ return cachedIds;
11624
11757
  }
11625
11758
 
11626
11759
  // src/orchestrator.ts
@@ -11629,19 +11762,19 @@ init_git();
11629
11762
 
11630
11763
  // src/deps.ts
11631
11764
  import { existsSync as existsSync14, readdirSync as readdirSync13, readFileSync as readFileSync18 } from "fs";
11632
- import { join as join23 } from "path";
11765
+ import { join as join24 } from "path";
11633
11766
  import { execa } from "execa";
11634
11767
 
11635
11768
  // src/prepareLock.ts
11636
11769
  import { createHash } from "crypto";
11637
11770
  import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync11, writeSync } from "fs";
11638
- import { join as join21 } from "path";
11771
+ import { join as join22 } from "path";
11639
11772
  import { tmpdir } from "os";
11640
11773
  var WAIT_TIMEOUT_MS = 5 * 6e4;
11641
11774
  var POLL_MS = 300;
11642
11775
  var STALE_MS = 20 * 6e4;
11643
11776
  function machineLockPath(key, dir = tmpdir()) {
11644
- return join21(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
11777
+ return join22(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
11645
11778
  }
11646
11779
  function defaultTryCreate(path, content) {
11647
11780
  try {
@@ -11703,7 +11836,7 @@ init_config();
11703
11836
  import { createHash as createHash2 } from "crypto";
11704
11837
  import { cp, link, lstat, mkdir as mkdir2, readdir, readlink, rename as rename2, rm as rm2, stat, symlink, utimes, writeFile as writeFile2 } from "fs/promises";
11705
11838
  import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
11706
- import { join as join22 } from "path";
11839
+ import { join as join23 } from "path";
11707
11840
  function fingerprint(parts) {
11708
11841
  const h = createHash2("sha256");
11709
11842
  for (const p of parts) {
@@ -11717,8 +11850,8 @@ async function cloneTree(src, dst) {
11717
11850
  const entries = await readdir(src, { withFileTypes: true });
11718
11851
  await Promise.all(
11719
11852
  entries.map(async (e) => {
11720
- const from = join22(src, e.name);
11721
- const to = join22(dst, e.name);
11853
+ const from = join23(src, e.name);
11854
+ const to = join23(dst, e.name);
11722
11855
  if (e.isDirectory()) return cloneTree(from, to);
11723
11856
  if (e.isSymbolicLink()) {
11724
11857
  return symlink(await readlink(from), to).catch(() => cp(from, to, { recursive: true, verbatimSymlinks: true }));
@@ -11731,11 +11864,11 @@ function cacheRoot(io) {
11731
11864
  return io.root ?? ENV_CACHE_DIR;
11732
11865
  }
11733
11866
  function snapshotDir(io, key) {
11734
- return join22(cacheRoot(io), key);
11867
+ return join23(cacheRoot(io), key);
11735
11868
  }
11736
11869
  function readManifest(dir) {
11737
11870
  try {
11738
- const parsed = JSON.parse(readFileSync17(join22(dir, "manifest.json"), "utf8"));
11871
+ const parsed = JSON.parse(readFileSync17(join23(dir, "manifest.json"), "utf8"));
11739
11872
  if (!parsed || typeof parsed !== "object") return null;
11740
11873
  const m = parsed;
11741
11874
  if (typeof m.key !== "string" || !Array.isArray(m.dirs)) return null;
@@ -11748,7 +11881,7 @@ function readManifest(dir) {
11748
11881
  }
11749
11882
  function hasEnvSnapshot(key, io = {}) {
11750
11883
  const exists = io.exists ?? existsSync13;
11751
- return exists(join22(snapshotDir(io, key), "manifest.json"));
11884
+ return exists(join23(snapshotDir(io, key), "manifest.json"));
11752
11885
  }
11753
11886
  async function restoreEnvSnapshot(worktree, key, io = {}) {
11754
11887
  const exists = io.exists ?? existsSync13;
@@ -11760,13 +11893,13 @@ async function restoreEnvSnapshot(worktree, key, io = {}) {
11760
11893
  try {
11761
11894
  for (let i = 0; i < manifest.dirs.length; i++) {
11762
11895
  const rel = manifest.dirs[i] ?? "";
11763
- const target = join22(worktree, rel);
11896
+ const target = join23(worktree, rel);
11764
11897
  if (exists(target)) continue;
11765
- await clone(join22(dir, "trees", String(i)), target);
11898
+ await clone(join23(dir, "trees", String(i)), target);
11766
11899
  made.push(rel);
11767
11900
  }
11768
11901
  } catch {
11769
- for (const rel of made) await rm2(join22(worktree, rel), { recursive: true, force: true }).catch(() => {
11902
+ for (const rel of made) await rm2(join23(worktree, rel), { recursive: true, force: true }).catch(() => {
11770
11903
  });
11771
11904
  return null;
11772
11905
  }
@@ -11782,16 +11915,16 @@ async function saveEnvSnapshot(worktree, key, dirs, io = {}) {
11782
11915
  const exists = io.exists ?? existsSync13;
11783
11916
  const clone = io.clone ?? cloneTree;
11784
11917
  if (hasEnvSnapshot(key, io)) return true;
11785
- const present = dirs.filter((rel) => exists(join22(worktree, rel)));
11918
+ const present = dirs.filter((rel) => exists(join23(worktree, rel)));
11786
11919
  if (!present.length) return false;
11787
11920
  const root = cacheRoot(io);
11788
- const tmp = join22(root, `.tmp-${key}-${process.pid}`);
11921
+ const tmp = join23(root, `.tmp-${key}-${process.pid}`);
11789
11922
  try {
11790
11923
  await rm2(tmp, { recursive: true, force: true });
11791
- await mkdir2(join22(tmp, "trees"), { recursive: true });
11792
- for (let i = 0; i < present.length; i++) await clone(join22(worktree, present[i] ?? ""), join22(tmp, "trees", String(i)));
11924
+ await mkdir2(join23(tmp, "trees"), { recursive: true });
11925
+ for (let i = 0; i < present.length; i++) await clone(join23(worktree, present[i] ?? ""), join23(tmp, "trees", String(i)));
11793
11926
  const manifest = { key, dirs: present, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
11794
- await writeFile2(join22(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
11927
+ await writeFile2(join23(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
11795
11928
  await rename2(tmp, snapshotDir(io, key));
11796
11929
  return true;
11797
11930
  } catch {
@@ -11811,13 +11944,13 @@ async function pruneEnvSnapshots(io = {}) {
11811
11944
  }
11812
11945
  const withTime = [];
11813
11946
  for (const name of names) {
11814
- const s = await stat(join22(root, name)).catch(() => null);
11947
+ const s = await stat(join23(root, name)).catch(() => null);
11815
11948
  if (s) withTime.push({ name, mtime: s.mtimeMs });
11816
11949
  }
11817
11950
  withTime.sort((a, b) => b.mtime - a.mtime);
11818
11951
  let removed = 0;
11819
11952
  for (const { name } of withTime.slice(Math.max(0, keep))) {
11820
- await rm2(join22(root, name), { recursive: true, force: true }).catch(() => {
11953
+ await rm2(join23(root, name), { recursive: true, force: true }).catch(() => {
11821
11954
  });
11822
11955
  removed++;
11823
11956
  }
@@ -11833,9 +11966,9 @@ async function sweepEnvCacheTemp(io = {}) {
11833
11966
  }
11834
11967
  let removed = 0;
11835
11968
  for (const name of names) {
11836
- const s = await lstat(join22(root, name)).catch(() => null);
11969
+ const s = await lstat(join23(root, name)).catch(() => null);
11837
11970
  if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
11838
- await rm2(join22(root, name), { recursive: true, force: true }).catch(() => {
11971
+ await rm2(join23(root, name), { recursive: true, force: true }).catch(() => {
11839
11972
  });
11840
11973
  removed++;
11841
11974
  }
@@ -11904,19 +12037,19 @@ function overridePlan(override) {
11904
12037
  function detectPackageManager(worktree, io = {}) {
11905
12038
  const exists = io.exists ?? existsSync14;
11906
12039
  const readText = io.readText ?? safeReadText;
11907
- const hasPkgJson = exists(join23(worktree, "package.json"));
12040
+ const hasPkgJson = exists(join24(worktree, "package.json"));
11908
12041
  if (!hasPkgJson) {
11909
- if (exists(join23(worktree, "deno.json")) || exists(join23(worktree, "deno.jsonc")) || exists(join23(worktree, "deno.lock"))) return "deno";
12042
+ if (exists(join24(worktree, "deno.json")) || exists(join24(worktree, "deno.jsonc")) || exists(join24(worktree, "deno.lock"))) return "deno";
11910
12043
  return null;
11911
12044
  }
11912
- const pkg = readJson(readText, join23(worktree, "package.json"));
12045
+ const pkg = readJson(readText, join24(worktree, "package.json"));
11913
12046
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
11914
12047
  if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
11915
- if (exists(join23(worktree, "bun.lock")) || exists(join23(worktree, "bun.lockb"))) return "bun";
11916
- if (exists(join23(worktree, "pnpm-lock.yaml"))) return "pnpm";
11917
- if (exists(join23(worktree, "yarn.lock"))) return "yarn";
11918
- if (exists(join23(worktree, "package-lock.json"))) return "npm";
11919
- if (exists(join23(worktree, "deno.lock"))) return "deno";
12048
+ if (exists(join24(worktree, "bun.lock")) || exists(join24(worktree, "bun.lockb"))) return "bun";
12049
+ if (exists(join24(worktree, "pnpm-lock.yaml"))) return "pnpm";
12050
+ if (exists(join24(worktree, "yarn.lock"))) return "yarn";
12051
+ if (exists(join24(worktree, "package-lock.json"))) return "npm";
12052
+ if (exists(join24(worktree, "deno.lock"))) return "deno";
11920
12053
  return "npm";
11921
12054
  }
11922
12055
  function jsInstallCommand(pm) {
@@ -11980,7 +12113,7 @@ function detectPlan(worktree, io) {
11980
12113
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
11981
12114
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
11982
12115
  // заново, но он дешёвый: тянет из того же общего кэша.
11983
- { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join23(worktree, "node_modules"), source: "detect" }
12116
+ { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join24(worktree, "node_modules"), source: "detect" }
11984
12117
  ];
11985
12118
  for (const target of workspaceBuildTargets(worktree, io)) {
11986
12119
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -11989,12 +12122,12 @@ function detectPlan(worktree, io) {
11989
12122
  return { source: "detect", ecosystem: `node/${pm}`, commands };
11990
12123
  }
11991
12124
  for (const marker of ECOSYSTEM_MARKERS) {
11992
- const hit = marker.files.some((f) => exists(join23(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
12125
+ const hit = marker.files.some((f) => exists(join24(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
11993
12126
  if (!hit) continue;
11994
12127
  let ecosystem = marker.ecosystem;
11995
12128
  let command = marker.command;
11996
12129
  for (const refine of marker.refine ?? []) {
11997
- if (!exists(join23(worktree, refine.file))) continue;
12130
+ if (!exists(join24(worktree, refine.file))) continue;
11998
12131
  ecosystem = refine.ecosystem;
11999
12132
  command = refine.command;
12000
12133
  break;
@@ -12051,12 +12184,12 @@ var CI_INSTALL_PATTERNS = [
12051
12184
  function ciWorkflowPlan(worktree, io) {
12052
12185
  const readText = io.readText ?? safeReadText;
12053
12186
  const listFiles = io.listFiles ?? safeListFiles;
12054
- const dir = join23(worktree, ".github", "workflows");
12187
+ const dir = join24(worktree, ".github", "workflows");
12055
12188
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
12056
12189
  const seen = /* @__PURE__ */ new Set();
12057
12190
  const commands = [];
12058
12191
  for (const name of files.sort()) {
12059
- const yaml = readText(join23(dir, name));
12192
+ const yaml = readText(join24(dir, name));
12060
12193
  if (yaml === null) continue;
12061
12194
  for (const raw of workflowRunLines(yaml)) {
12062
12195
  const line = raw.replace(/\s+/g, " ").trim();
@@ -12071,7 +12204,7 @@ function ciWorkflowPlan(worktree, io) {
12071
12204
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
12072
12205
  }
12073
12206
  function workspaceGlobs(worktree, readText) {
12074
- const yaml = readText(join23(worktree, "pnpm-workspace.yaml"));
12207
+ const yaml = readText(join24(worktree, "pnpm-workspace.yaml"));
12075
12208
  if (yaml !== null) {
12076
12209
  const globs = [];
12077
12210
  let inPackages = false;
@@ -12091,7 +12224,7 @@ function workspaceGlobs(worktree, readText) {
12091
12224
  }
12092
12225
  if (globs.length) return globs;
12093
12226
  }
12094
- const pkg = readJson(readText, join23(worktree, "package.json"));
12227
+ const pkg = readJson(readText, join24(worktree, "package.json"));
12095
12228
  const ws = pkg?.workspaces;
12096
12229
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
12097
12230
  return list.filter((g) => typeof g === "string");
@@ -12102,14 +12235,14 @@ function workspaceDirs(worktree, globs, io) {
12102
12235
  if (glob.startsWith("!")) continue;
12103
12236
  const star = glob.indexOf("*");
12104
12237
  if (star === -1) {
12105
- const dir = join23(worktree, glob);
12106
- if (io.exists(join23(dir, "package.json"))) dirs.add(dir);
12238
+ const dir = join24(worktree, glob);
12239
+ if (io.exists(join24(dir, "package.json"))) dirs.add(dir);
12107
12240
  continue;
12108
12241
  }
12109
- const base3 = join23(worktree, glob.slice(0, star).replace(/\/$/, ""));
12242
+ const base3 = join24(worktree, glob.slice(0, star).replace(/\/$/, ""));
12110
12243
  for (const name of io.listDirs(base3)) {
12111
- const dir = join23(base3, name);
12112
- if (io.exists(join23(dir, "package.json"))) dirs.add(dir);
12244
+ const dir = join24(base3, name);
12245
+ if (io.exists(join24(dir, "package.json"))) dirs.add(dir);
12113
12246
  }
12114
12247
  }
12115
12248
  return [...dirs];
@@ -12137,11 +12270,11 @@ function workspaceBuildTargets(worktree, io = {}) {
12137
12270
  if (!globs.length) return [];
12138
12271
  const pkgs = [];
12139
12272
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
12140
- const pkg = readJson(readText, join23(dir, "package.json"));
12273
+ const pkg = readJson(readText, join24(dir, "package.json"));
12141
12274
  if (!pkg || typeof pkg.name !== "string") continue;
12142
12275
  const scripts = pkg.scripts ?? {};
12143
12276
  const entries = entryPaths(pkg);
12144
- const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join23(dir, e)));
12277
+ const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join24(dir, e)));
12145
12278
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
12146
12279
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
12147
12280
  }
@@ -12169,7 +12302,7 @@ function devDatabaseUrl(worktree, io = {}) {
12169
12302
  const fromEnv = (io.env ?? process.env).DATABASE_URL;
12170
12303
  if (fromEnv && fromEnv.trim()) return fromEnv.trim();
12171
12304
  for (const file of [".env", ".env.example"]) {
12172
- const raw = readText(join23(worktree, file));
12305
+ const raw = readText(join24(worktree, file));
12173
12306
  if (raw === null) continue;
12174
12307
  const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
12175
12308
  const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
@@ -12189,7 +12322,7 @@ function prismaPackages(worktree, io) {
12189
12322
  const readText = io.readText ?? safeReadText;
12190
12323
  const listDirs = io.listDirs ?? safeListDirs;
12191
12324
  const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
12192
- return dirs.filter((dir) => exists(join23(dir, "prisma", "schema.prisma")) && listDirs(join23(dir, "prisma", "migrations")).length > 0);
12325
+ return dirs.filter((dir) => exists(join24(dir, "prisma", "schema.prisma")) && listDirs(join24(dir, "prisma", "migrations")).length > 0);
12193
12326
  }
12194
12327
  function jsExecBin(pm, command) {
12195
12328
  switch (pm) {
@@ -12226,18 +12359,18 @@ function envCachePlan(worktree, io = {}) {
12226
12359
  if (!pm) return null;
12227
12360
  const parts = [`pm:${pm}`];
12228
12361
  for (const name of ENV_DEFINING_FILES) {
12229
- const raw = readText(join23(worktree, name));
12362
+ const raw = readText(join24(worktree, name));
12230
12363
  if (raw !== null) parts.push(`${name}
12231
12364
  ${raw}`);
12232
12365
  }
12233
12366
  const pkgDirs = workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs });
12234
12367
  const rels = pkgDirs.map((dir) => dir.slice(worktree.length + 1)).sort();
12235
12368
  for (const rel of ["", ...rels]) {
12236
- const raw = readText(join23(worktree, rel, "package.json"));
12369
+ const raw = readText(join24(worktree, rel, "package.json"));
12237
12370
  if (raw !== null) parts.push(`${rel}/package.json
12238
12371
  ${raw}`);
12239
12372
  }
12240
- return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join23(rel, "node_modules"))] };
12373
+ return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join24(rel, "node_modules"))] };
12241
12374
  }
12242
12375
  function planPrepare(worktree, io = {}) {
12243
12376
  const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
@@ -12262,7 +12395,7 @@ async function ensureWorktreeDeps(worktree, io = {}) {
12262
12395
  restored: [],
12263
12396
  cached: false
12264
12397
  };
12265
- if (cachePlan && !exists(join23(worktree, "node_modules"))) {
12398
+ if (cachePlan && !exists(join24(worktree, "node_modules"))) {
12266
12399
  report.restored = await restoreEnvSnapshot(worktree, cachePlan.key, cacheIO) ?? [];
12267
12400
  }
12268
12401
  const plan = planPrepare(worktree, io);
@@ -12593,7 +12726,7 @@ function parkReasonUnretriable(reason) {
12593
12726
  // package.json
12594
12727
  var package_default = {
12595
12728
  name: "@taskless-app/swarm",
12596
- version: "0.6.57",
12729
+ version: "0.6.59",
12597
12730
  type: "module",
12598
12731
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
12599
12732
  keywords: [
@@ -12644,7 +12777,7 @@ var package_default = {
12644
12777
  execa: "^10.0.0",
12645
12778
  zod: "3.25.76"
12646
12779
  },
12647
- gitHead: "e7c84e94f2f2e0ff28d5dfe8b11b43ef1291ce5e"
12780
+ gitHead: "13cdd65f2929d86153c0a52715c0d9891f8a1e4b"
12648
12781
  };
12649
12782
 
12650
12783
  // src/version.ts
@@ -13433,7 +13566,7 @@ init_git();
13433
13566
 
13434
13567
  // src/diff.ts
13435
13568
  import { statSync as statSync12 } from "fs";
13436
- import { join as join28 } from "path";
13569
+ import { join as join29 } from "path";
13437
13570
  import { $ as $4 } from "execa";
13438
13571
  var MAX_FILES = 200;
13439
13572
  var MAX_LINES_PER_FILE = 1500;
@@ -13601,7 +13734,7 @@ async function collectRunDiff(worktree, opts = {}) {
13601
13734
  files.push(pf);
13602
13735
  }
13603
13736
  }
13604
- files.sort((a, b) => mtime(join28(worktree, b.path)) - mtime(join28(worktree, a.path)));
13737
+ files.sort((a, b) => mtime(join29(worktree, b.path)) - mtime(join29(worktree, a.path)));
13605
13738
  const truncatedFiles = files.length > MAX_FILES;
13606
13739
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
13607
13740
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -15415,14 +15548,14 @@ function makeMaxDronesReader(opts) {
15415
15548
  const ttl = opts.ttlMs ?? 1e4;
15416
15549
  const now = opts.now ?? Date.now;
15417
15550
  let cached = null;
15418
- let cachedAt = -Infinity;
15551
+ let cachedAt2 = -Infinity;
15419
15552
  return async () => {
15420
15553
  const t = now();
15421
- if (cached != null && t - cachedAt < ttl) return cached;
15554
+ if (cached != null && t - cachedAt2 < ttl) return cached;
15422
15555
  const live = await opts.fetch().catch(() => null);
15423
15556
  if (live != null && Number.isFinite(live) && live > 0) {
15424
15557
  cached = Math.trunc(live);
15425
- cachedAt = t;
15558
+ cachedAt2 = t;
15426
15559
  return cached;
15427
15560
  }
15428
15561
  return opts.fallback;
@@ -15855,20 +15988,20 @@ async function prodClearMergeIntent(runId, deps = {}) {
15855
15988
 
15856
15989
  // src/envGate.ts
15857
15990
  import { existsSync as existsSync17 } from "fs";
15858
- import { join as join30 } from "path";
15991
+ import { join as join31 } from "path";
15859
15992
  import { createConnection } from "net";
15860
15993
  import { execa as execa2 } from "execa";
15861
15994
 
15862
15995
  // src/platform.ts
15863
15996
  import { existsSync as existsSync16, readFileSync as readFileSync22 } from "fs";
15864
- import { delimiter, join as join29 } from "path";
15997
+ import { delimiter as delimiter2, join as join30 } from "path";
15865
15998
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
15866
15999
  if (name.includes("/") || name.includes("\\")) return existsSync16(name) ? name : null;
15867
- const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
16000
+ const dirs = (env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean);
15868
16001
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
15869
16002
  for (const dir of dirs) {
15870
16003
  for (const ext of exts) {
15871
- const candidate = join29(dir, name + ext);
16004
+ const candidate = join30(dir, name + ext);
15872
16005
  if (existsSync16(candidate)) return candidate;
15873
16006
  }
15874
16007
  }
@@ -15953,7 +16086,7 @@ var COMPOSE_MARKERS = [
15953
16086
  ];
15954
16087
  function detectRequirements(repo, io = {}) {
15955
16088
  const exists = io.exists ?? existsSync17;
15956
- const marker = COMPOSE_MARKERS.find((f) => exists(join30(repo, f)));
16089
+ const marker = COMPOSE_MARKERS.find((f) => exists(join31(repo, f)));
15957
16090
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
15958
16091
  }
15959
16092
  function resolveRequirements(repo, declared, io = {}) {
@@ -16104,8 +16237,9 @@ function stormFacts(db) {
16104
16237
  if (!db.stormActive()) return null;
16105
16238
  const since = db.currentStormStartedAt();
16106
16239
  const recs = since ? db.stormProbeRecordsSince(since) : [];
16240
+ const last = lastProbeReason(recs);
16107
16241
  return {
16108
- reason: lastProbeReason(recs) === "limit" ? "limit" : "provider",
16242
+ reason: last === "limit" ? "limit" : last === "auth" ? "auth" : "provider",
16109
16243
  hint: recs.length ? recs[recs.length - 1].resetHint ?? null : null
16110
16244
  };
16111
16245
  }
@@ -16731,6 +16865,13 @@ function stormBanner(storm) {
16731
16865
  desc: `\u0414\u0440\u043E\u043D\u044B \u043F\u043E\u0435\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435 \u0440\u0435\u0437\u0435\u0442\u0430 \u043B\u0438\u043C\u0438\u0442\u0430${storm.resetHint ? ` \xB7 ${storm.resetHint}` : ""}. Anthropic \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u044D\u0442\u043E \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438, \u043D\u0435 \u0441\u0431\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430.`
16732
16866
  };
16733
16867
  }
16868
+ if (storm.reason === "auth") {
16869
+ return {
16870
+ ico: "\u{1F511}",
16871
+ title: "\u0414\u043E\u0441\u0442\u0443\u043F \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u2014 \u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435",
16872
+ desc: "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u0436\u0438\u0432, \u043D\u043E \u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u043D\u0435 \u043D\u0430\u0441: \u043A\u043B\u044E\u0447, \u0430\u043A\u043A\u0430\u0443\u043D\u0442 \u0438\u043B\u0438 \u0440\u0435\u0433\u0438\u043E\u043D (403 \u0431\u0435\u0437 VPN). \u0421\u0430\u043C\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0432\u0445\u043E\u0434 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F (`swarm doctor`)."
16873
+ };
16874
+ }
16734
16875
  const frozen = storm.dead ? `${storm.dead} \u0432 \u043A\u043E\u043A\u043E\u043D\u0435` : "\u043D\u043E\u0432\u044B\u0445 \u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C";
16735
16876
  return {
16736
16877
  ico: "\u26A1",
@@ -17288,6 +17429,10 @@ function paintStorm(){ var b=document.getElementById('storm-banner'); if(!b) ret
17288
17429
  b.innerHTML='<span class="sb-ico">\u23F3</span><div class="sb-body"><div class="sb-t">\u041B\u0438\u043C\u0438\u0442 Claude \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435</div>'+
17289
17430
  '<div class="sb-d">\u0414\u0440\u043E\u043D\u044B \u043F\u043E\u0435\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435 \u0440\u0435\u0437\u0435\u0442\u0430 \u043B\u0438\u043C\u0438\u0442\u0430'+reset+E(since)+'. Anthropic \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u044D\u0442\u043E \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438, \u043D\u0435 \u0441\u0431\u043E\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430.</div></div>';
17290
17431
  b.hidden=false; return; }
17432
+ if(st.reason==='auth'){
17433
+ b.innerHTML='<span class="sb-ico">\u{1F511}</span><div class="sb-body"><div class="sb-t">\u0414\u043E\u0441\u0442\u0443\u043F \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u2014 \u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435</div>'+
17434
+ '<div class="sb-d">\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u0436\u0438\u0432, \u043D\u043E \u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u043D\u0435 \u043D\u0430\u0441: \u043A\u043B\u044E\u0447, \u0430\u043A\u043A\u0430\u0443\u043D\u0442 \u0438\u043B\u0438 \u0440\u0435\u0433\u0438\u043E\u043D'+E(since)+'. \u0421\u0430\u043C\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0432\u0445\u043E\u0434 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F (swarm doctor).</div></div>';
17435
+ b.hidden=false; return; }
17291
17436
  var frozen = st.dead ? st.dead+' '+plural(st.dead,DRONEW)+' \u0432 \u043A\u043E\u043A\u043E\u043D\u0435' : '\u043D\u043E\u0432\u044B\u0445 \u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C';
17292
17437
  b.innerHTML='<span class="sb-ico">\u26A1</span><div class="sb-body"><div class="sb-t">\u041F\u0441\u0438\u043E\u043D\u043D\u0430\u044F \u0441\u0432\u044F\u0437\u044C \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430 \u2014 Anthropic \u0448\u0442\u043E\u0440\u043C\u0438\u0442</div>'+
17293
17438
  '<div class="sb-d">\u0420\u043E\u0439 \u0437\u0430\u043C\u0435\u0440, \u0434\u0440\u043E\u043D\u044B \u0432 \u043A\u043E\u043A\u043E\u043D\u0430\u0445 \u2014 '+E(frozen)+', \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043A\u043E\u043F\u0438\u0442\u0441\u044F'+E(since)+'. \u041F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0441\u0430\u043C, \u043A\u0430\u043A \u0441\u0432\u044F\u0437\u044C \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.</div></div>';
@@ -17697,9 +17842,9 @@ function closeWebServer(server) {
17697
17842
  // src/hostCommands.ts
17698
17843
  init_src();
17699
17844
  import { readFileSync as readFileSync23, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
17700
- import { join as join31 } from "path";
17845
+ import { join as join32 } from "path";
17701
17846
  function hostCommandStatePath(home) {
17702
- return join31(home, "host-command.json");
17847
+ return join32(home, "host-command.json");
17703
17848
  }
17704
17849
  function readState(path) {
17705
17850
  try {
@@ -17874,7 +18019,7 @@ async function doRestart(cmd, opts, note) {
17874
18019
  // src/selfUpdate.ts
17875
18020
  init_git();
17876
18021
  import { existsSync as existsSync18 } from "fs";
17877
- import { join as join32 } from "path";
18022
+ import { join as join33 } from "path";
17878
18023
  import { $ as $5 } from "execa";
17879
18024
  var DAEMON_PACKAGE = "@taskless-app/swarm";
17880
18025
  var defaultRun = async (cmd, args, cwd) => {
@@ -17916,7 +18061,7 @@ async function runSelfUpdate(io) {
17916
18061
  };
17917
18062
  }
17918
18063
  await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
17919
- if (!exists(join32(repo, "pnpm-lock.yaml"))) {
18064
+ if (!exists(join33(repo, "pnpm-lock.yaml"))) {
17920
18065
  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" };
17921
18066
  }
17922
18067
  const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
@@ -18036,7 +18181,10 @@ function executorHealthy(opts = {}) {
18036
18181
  }
18037
18182
  resolve5(r);
18038
18183
  };
18039
- const child = spawn3(bin, [...executor.probe.args], { stdio: ["ignore", "pipe", "pipe"] });
18184
+ const child = spawn3(bin, [...executor.probe.args], {
18185
+ stdio: ["ignore", "pipe", "pipe"],
18186
+ ...opts.env ? { env: { ...process.env, ...opts.env } } : {}
18187
+ });
18040
18188
  child.stdout?.on("data", (d) => {
18041
18189
  out += d.toString();
18042
18190
  });
@@ -18045,11 +18193,14 @@ function executorHealthy(opts = {}) {
18045
18193
  });
18046
18194
  const timer = setTimeout(() => finish({ ok: false, reason: "provider", resetHint: null }), timeoutMs);
18047
18195
  child.on("error", () => finish({ ok: false, reason: "exec", resetHint: null }));
18048
- child.on("exit", (code) => {
18196
+ child.on("close", (code) => {
18049
18197
  if (code === 0) return finish({ ok: true, reason: "ok", resetHint: null });
18050
18198
  if (executor.probe.limitPattern.test(out)) {
18051
18199
  return finish({ ok: false, reason: "limit", resetHint: out.match(RESET_RE)?.[0]?.trim() ?? null });
18052
18200
  }
18201
+ if (executor.probe.authPattern?.test(out)) {
18202
+ return finish({ ok: false, reason: "auth", resetHint: null });
18203
+ }
18053
18204
  finish({ ok: false, reason: "provider", resetHint: null });
18054
18205
  });
18055
18206
  });
@@ -18076,14 +18227,14 @@ async function fetchExternalStorm(url, opts = {}) {
18076
18227
 
18077
18228
  // src/anomalyState.ts
18078
18229
  import { existsSync as existsSync19, readFileSync as readFileSync24, renameSync as renameSync11, writeFileSync as writeFileSync16 } from "fs";
18079
- import { join as join33 } from "path";
18230
+ import { join as join34 } from "path";
18080
18231
  var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
18081
18232
  var MAX_RESOLVED = 200;
18082
18233
  function emptyState() {
18083
18234
  return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
18084
18235
  }
18085
18236
  function anomalyStatePath(home) {
18086
- return join33(home, "anomaly-state.json");
18237
+ return join34(home, "anomaly-state.json");
18087
18238
  }
18088
18239
  function loadAnomalyState(home, now = Date.now(), io = {}) {
18089
18240
  const exists = io.exists ?? existsSync19;
@@ -18282,7 +18433,7 @@ function decideFreshAction(db, wt) {
18282
18433
  for (const r of stalled) {
18283
18434
  if (findEscalation(db.events(r.id).map((e) => e.text ?? ""), r, "author")) continue;
18284
18435
  const path = r.worktree_path;
18285
- const recoverable = !!path && wt.exists(path) && wt.hasTranscript(path);
18436
+ const recoverable = !!path && wt.exists(path) && wt.hasTranscript(r);
18286
18437
  if (recoverable) continue;
18287
18438
  return {
18288
18439
  type: "fresh",
@@ -18561,7 +18712,7 @@ function withManualAreas(history, areas) {
18561
18712
  // src/overmind.ts
18562
18713
  var ANOMALY_RESOLVE_BATCH = 50;
18563
18714
  var THIS_FILE = fileURLToPath3(import.meta.url);
18564
- var executorHasSession = (p) => EXECUTOR.session.exists(p);
18715
+ var executorHasSession = (run) => !!run.worktree_path && EXECUTOR.session.exists(run.worktree_path);
18565
18716
  var OVERMIND_ENTRY = THIS_FILE;
18566
18717
  var STALL_MS = Number(process.env.SWARM_STALL_MS) || 10 * 60 * 1e3;
18567
18718
  var STALL_FLOOR_MS = Number(process.env.SWARM_STALL_FLOOR_MS) || 30 * 60 * 1e3;
@@ -18670,7 +18821,7 @@ async function livenessMs(deps, run, now, liveChildPpids) {
18670
18821
  if (commitMs != null) ms = Math.max(ms, commitMs);
18671
18822
  }
18672
18823
  if (deps.transcriptMtimeMs) {
18673
- const transcriptMs = deps.transcriptMtimeMs(run.worktree_path);
18824
+ const transcriptMs = deps.transcriptMtimeMs(run);
18674
18825
  if (transcriptMs != null) ms = Math.max(ms, transcriptMs);
18675
18826
  }
18676
18827
  return ms;
@@ -19362,7 +19513,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
19362
19513
  }
19363
19514
  return parts.join("\n");
19364
19515
  }
19365
- var HERO_CHARTER_PATH = join34(dirname5(THIS_FILE), "..", "docs", "HERO.md");
19516
+ var HERO_CHARTER_PATH = join35(dirname5(THIS_FILE), "..", "docs", "HERO.md");
19366
19517
  function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
19367
19518
  const name = run.name || `#${run.id}`;
19368
19519
  const charter = readFileSync25(HERO_CHARTER_PATH, "utf8");
@@ -19422,7 +19573,7 @@ function ciTrustLine(prUrl, verifyMode) {
19422
19573
  }
19423
19574
  return `\u041D\u0435 \u043F\u0435\u0440\u0435\u0433\u043E\u043D\u044F\u0439 \u0442\u0435\u0441\u0442-\u0441\u044C\u044E\u0442/typecheck \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u044D\u0442\u043E \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043B CI \u043D\u0430 \u044D\u0442\u043E\u043C \u0436\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u0435 (\u0434\u0432\u043E\u0439\u043D\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043E\u0442\u043C\u0435\u043D\u0451\u043D, TASWM-249). \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0435\u0433\u043E \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0441\u0430\u043C: ${readChecks}. \u0414\u0430\u043B\u044C\u0448\u0435 \u0442\u0440\u0438 \u0440\u0430\u0437\u043D\u044B\u0445 \u0441\u043B\u0443\u0447\u0430\u044F: \u2460 \u0445\u043E\u0442\u044C \u043E\u0434\u0438\u043D \u0447\u0435\u043A \u0435\u0449\u0451 PENDING \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u043E\u043E\u0431\u0449\u0435 \u043D\u0435 \u0432\u044B\u043D\u043E\u0441\u0438: \u043D\u0435 \u043F\u043E\u0441\u0442\u0438 REQUEST CHANGES, \u043D\u0435 \u0437\u043E\u0432\u0438 done(), \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0441\u0435\u0441\u0441\u0438\u044E \u043A\u0430\u043A \u0435\u0441\u0442\u044C \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \u0437\u0430\u043C\u0435\u0442\u0438\u0442, \u0447\u0442\u043E \u0442\u044B \u0436\u0434\u0451\u0448\u044C CI, \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442 \u0434\u043E\u0434\u0435\u043B\u0430\u0442\u044C \u0440\u0435\u0432\u044C\u044E, \u043A\u043E\u0433\u0434\u0430 \u0432\u0441\u0435 \u0447\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0442\u0441\u044F (\u044D\u0442\u043E \u0448\u0442\u0430\u0442\u043D\u044B\u0439 \u043F\u0443\u0442\u044C, \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0437\u0432\u0430\u0442\u044C \u043D\u0435 \u043D\u0443\u0436\u043D\u043E). \u2461 \u0432\u0441\u0435 \u0447\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C \u0438 \u0435\u0441\u0442\u044C FAIL \u2014 REQUEST CHANGES, \u043D\u0430\u0437\u043E\u0432\u0438 \u043F\u043E\u0438\u043C\u0451\u043D\u043D\u043E, \u0447\u0442\u043E \u0443\u043F\u0430\u043B\u043E. \u2462 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0447\u0435\u043A\u043E\u0432 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0442\u043E\u0436\u0435 REQUEST CHANGES: \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C (\u0443 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430 verifyMode=ci, \u0442\u043E \u0435\u0441\u0442\u044C CI \u043E\u0431\u044F\u0437\u0430\u043D \u0431\u044B\u0442\u044C). ${dontTrustAuthor}`;
19424
19575
  }
19425
- var REVIEW_SKILL_PATH = join34(bundledRoleDir("drone"), "skills", "taskless-review", "SKILL.md");
19576
+ var REVIEW_SKILL_PATH = join35(bundledRoleDir("drone"), "skills", "taskless-review", "SKILL.md");
19426
19577
  function reviewDefectClasses(skillPath = REVIEW_SKILL_PATH) {
19427
19578
  const text = readFileSync25(skillPath, "utf8");
19428
19579
  const m = /^## Классы дефектов\n([\s\S]*?)(?=^## |(?![\s\S]))/m.exec(text);
@@ -19663,7 +19814,7 @@ async function resurrectStormDead(deps, worktreeExists, hasTranscript, opts) {
19663
19814
  if (countActiveDrones(deps.db) >= cap) break;
19664
19815
  if (!await projectQuotaGateAllows(deps, run)) continue;
19665
19816
  const wt = run.worktree_path;
19666
- const mode = !!wt && worktreeExists(wt) && hasTranscript(wt) ? "continue" : "fresh";
19817
+ const mode = !!wt && worktreeExists(wt) && hasTranscript(run) ? "continue" : "fresh";
19667
19818
  deps.db.addEvent(run.id, "resumed", mode === "continue" ? STORM_RESUME_CONTINUE_TEXT : STORM_RESUME_FRESH_TEXT);
19668
19819
  try {
19669
19820
  await deps.respawn(run, mode);
@@ -19691,7 +19842,7 @@ async function wakeRun(deps, runId, opts = {}) {
19691
19842
  const worktreeExists = deps.worktreeExists ?? existsSync20;
19692
19843
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
19693
19844
  const wt = run.worktree_path;
19694
- const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
19845
+ const canContinue = !!wt && worktreeExists(wt) && hasTranscript(run);
19695
19846
  const isService = run.reviews_run_id != null || run.resolves_run_id != null;
19696
19847
  if (!canContinue && isService) {
19697
19848
  return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u2014 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0439 (\u0440\u0435\u0432\u044C\u044E\u0435\u0440/\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440), \u0430 \u0441\u0435\u0441\u0441\u0438\u044E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: fresh \u043F\u043E\u0442\u0435\u0440\u044F\u043B \u0431\u044B \u0435\u0433\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442` };
@@ -19813,7 +19964,7 @@ function repoHasCi(deps, run) {
19813
19964
  function repoHasCiWorkflows(worktreePath, repo) {
19814
19965
  for (const base3 of [worktreePath, repo]) {
19815
19966
  if (!base3) continue;
19816
- const dir = join34(base3, ".github", "workflows");
19967
+ const dir = join35(base3, ".github", "workflows");
19817
19968
  try {
19818
19969
  if (readdirSync14(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
19819
19970
  } catch {
@@ -19904,7 +20055,7 @@ async function wakeAuthorOnRedCi(deps, run, now, ci, worktreeExists, hasTranscri
19904
20055
  }
19905
20056
  if (stats.lastMs != null && now - stats.lastMs < CI_WAKE_COOLDOWN_MS) return false;
19906
20057
  const wt = run.worktree_path;
19907
- if (!wt || !worktreeExists(wt) || !hasTranscript(wt)) return false;
20058
+ if (!wt || !worktreeExists(wt) || !hasTranscript(run)) return false;
19908
20059
  const summary = await deps.ci.failureSummary(ci.failing, ci.failingRunIds).catch(() => null) ?? `\u274C CI \u0443\u043F\u0430\u043B: ${ci.failing.join(", ") || "(\u0441\u043C. gh pr checks)"}`;
19909
20060
  if (isInfraCiFailure(summary)) {
19910
20061
  const seen = ciStreak(deps.db, run.id, CI_INFRA_MARK).count;
@@ -19970,7 +20121,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
19970
20121
  }
19971
20122
  const wt = reviewer.worktree_path;
19972
20123
  const stats = ciWakeStats(deps.db, reviewer.id);
19973
- const canContinue = !!wt && worktreeAndTranscriptAlive(deps, wt);
20124
+ const canContinue = !!wt && worktreeAndTranscriptAlive(deps, reviewer);
19974
20125
  if (stats.count >= CI_WAKE_LIMIT || !canContinue) {
19975
20126
  const why = !canContinue ? "worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : `${CI_WAKE_LIMIT} \u043F\u043E\u0431\u0443\u0434(\u043E\u043A) \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 CI \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E`;
19976
20127
  deps.db.updateRun(reviewer.id, { status: "stalled" });
@@ -20013,10 +20164,11 @@ async function wakeReviewerOnCiReady(deps, run, over) {
20013
20164
  }
20014
20165
  return true;
20015
20166
  }
20016
- function worktreeAndTranscriptAlive(deps, worktree) {
20167
+ function worktreeAndTranscriptAlive(deps, run) {
20017
20168
  const exists = deps.worktreeExists ?? existsSync20;
20018
20169
  const transcript = deps.hasTranscript ?? executorHasSession;
20019
- return exists(worktree) && transcript(worktree);
20170
+ const wt = run.worktree_path;
20171
+ return !!wt && exists(wt) && transcript(run);
20020
20172
  }
20021
20173
  async function projectWantsChecks(deps, run) {
20022
20174
  if (!deps.autoReviewForRepo) return true;
@@ -20196,7 +20348,7 @@ async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback)
20196
20348
  activeFiles.prune(holders.map((r) => r.id));
20197
20349
  const fileExists = (p) => {
20198
20350
  try {
20199
- return statSync13(join34(repo, p), { throwIfNoEntry: false })?.isFile() ?? false;
20351
+ return statSync13(join35(repo, p), { throwIfNoEntry: false })?.isFile() ?? false;
20200
20352
  } catch {
20201
20353
  return false;
20202
20354
  }
@@ -20610,7 +20762,7 @@ async function resumeWatchdogParked(deps, droneCap) {
20610
20762
  const worktreeExists = deps.worktreeExists ?? existsSync20;
20611
20763
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
20612
20764
  const wt = candidate.worktree_path;
20613
- const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
20765
+ const canContinue = !!wt && worktreeExists(wt) && hasTranscript(candidate);
20614
20766
  const mode = canContinue ? "continue" : "fresh";
20615
20767
  if (mode === "continue" && wt && deps.reclaimWorktree) {
20616
20768
  try {
@@ -20831,7 +20983,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
20831
20983
  console.error(`[overmind] \u0441\u043D\u0430\u043F\u0448\u043E\u0442 worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
20832
20984
  }
20833
20985
  }
20834
- const canContinue = !!candidate.worktree_path && worktreeAndTranscriptAlive(deps, candidate.worktree_path);
20986
+ const canContinue = !!candidate.worktree_path && worktreeAndTranscriptAlive(deps, candidate);
20835
20987
  deps.db.updateRun(candidate.id, { status: "paused", stop_reason: RELIEF_PAUSE_REASON });
20836
20988
  deps.db.addEvent(candidate.id, "paused", reliefPauseReason(canContinue, decisionText));
20837
20989
  state.lastPauseAt = now;
@@ -20910,7 +21062,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
20910
21062
  if (now - loopEntry.lastPauseAt < reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs, loopCfg)) return;
20911
21063
  }
20912
21064
  const wt = resumeCandidate.worktree_path;
20913
- const canContinue = !!wt && worktreeAndTranscriptAlive(deps, wt);
21065
+ const canContinue = !!wt && worktreeAndTranscriptAlive(deps, resumeCandidate);
20914
21066
  const mode = canContinue ? "continue" : "fresh";
20915
21067
  if (mode === "continue" && wt && deps.reclaimWorktree) {
20916
21068
  try {
@@ -21895,7 +22047,7 @@ async function tickOnce(deps) {
21895
22047
  const abort = aborted ? deps.exitAbort?.(run) ?? null : null;
21896
22048
  const what = aborted ? abortReason(abort) : EXITED_NO_DONE_REASON;
21897
22049
  const wt = run.worktree_path;
21898
- const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
22050
+ const canContinue = !!wt && worktreeExists(wt) && hasTranscript(run);
21899
22051
  if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
21900
22052
  const n = deps.db.incRespawn(run.id);
21901
22053
  deps.db.addEvent(
@@ -21932,7 +22084,7 @@ async function tickOnce(deps) {
21932
22084
  deps.respawn && run.respawns < RESPAWN_LIMIT) {
21933
22085
  const wt = run.worktree_path;
21934
22086
  const wtAlive = !!wt && worktreeExists(wt);
21935
- const canContinue = wtAlive && hasTranscript(wt);
22087
+ const canContinue = wtAlive && hasTranscript(run);
21936
22088
  const young = now - lastActivityMs(deps.db, run) < RESPAWN_YOUNG_MS;
21937
22089
  const mode = young ? "fresh" : canContinue ? "continue" : null;
21938
22090
  if (mode) {
@@ -21989,7 +22141,7 @@ async function tickOnce(deps) {
21989
22141
  }
21990
22142
  if (!alive && outcome === "graceful" && run.status === "running" && isSubrun(run)) {
21991
22143
  const wt = run.worktree_path;
21992
- const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
22144
+ const canContinue = !!wt && worktreeExists(wt) && hasTranscript(run);
21993
22145
  if (deps.respawn && canContinue && run.respawns < RESPAWN_LIMIT) {
21994
22146
  const n = deps.db.incRespawn(run.id);
21995
22147
  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)`);
@@ -22065,7 +22217,7 @@ async function tickOnce(deps) {
22065
22217
  if (run.pid != null && deps.isAlive(run.pid)) continue;
22066
22218
  if (!await projectQuotaGateAllows(deps, run)) continue;
22067
22219
  const wt = run.worktree_path;
22068
- const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
22220
+ const canContinue = !!wt && worktreeExists(wt) && hasTranscript(run);
22069
22221
  const mode = canContinue ? "continue" : "fresh";
22070
22222
  deps.db.addEvent(
22071
22223
  run.id,
@@ -22295,7 +22447,7 @@ async function resolveConflict(deps, reviewRun) {
22295
22447
  model: reviewRun.model,
22296
22448
  review_model: reviewRun.review_model
22297
22449
  });
22298
- const wt = join34(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
22450
+ const wt = join35(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
22299
22451
  try {
22300
22452
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
22301
22453
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -22375,7 +22527,7 @@ async function spawnReviewer(deps, reviewRun, files) {
22375
22527
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
22376
22528
  workspace_id: reviewRun.workspace_id
22377
22529
  });
22378
- const wt = join34(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
22530
+ const wt = join35(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
22379
22531
  try {
22380
22532
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
22381
22533
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
@@ -22456,7 +22608,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
22456
22608
  // штамп цикла: фикс-итерацию проверяет свежий верификатор
22457
22609
  workspace_id: verifiedRun.workspace_id
22458
22610
  });
22459
- const wt = join34(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
22611
+ const wt = join35(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
22460
22612
  try {
22461
22613
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
22462
22614
  await deps.git.setWorktreeIdentity(wt, verifier.name);
@@ -22792,6 +22944,7 @@ async function runDaemon(opts) {
22792
22944
  } : void 0;
22793
22945
  let lastDroneCap = null;
22794
22946
  let lastSwarmModels = null;
22947
+ const runModelRefPin = /* @__PURE__ */ new Map();
22795
22948
  let lastSwarmEfforts = null;
22796
22949
  let lastMaxCostUsd = null;
22797
22950
  let lastOwnCapacity = null;
@@ -22804,8 +22957,8 @@ async function runDaemon(opts) {
22804
22957
  runLogsDir: RUN_LOGS_DIR,
22805
22958
  // TASWM-419: чем именуется каталог лога прогона — стабильные ключи из стора, не номер.
22806
22959
  runLogKeys: (run) => db.runLogKeys(run.id),
22807
- // Кем исполнять (TASWM-349) — настройка SWARM_EXECUTOR, один резолв на весь демон.
22808
- executor: EXECUTOR,
22960
+ // TASWM-592: исполнитель здесь НЕ пинится — лаунчер берёт адаптер ПО ПРОГОНУ (реестр
22961
+ // провайдеров по ссылке модели). Ручной пин cfg.executor остаётся у тестов.
22809
22962
  droneIsolation: DRONE_ISOLATION,
22810
22963
  channel: droneChannelLaunch,
22811
22964
  droneCap: () => lastDroneCap,
@@ -22817,6 +22970,10 @@ async function runDaemon(opts) {
22817
22970
  recordEffort: (runId, effort) => {
22818
22971
  db.updateRun(runId, { effort });
22819
22972
  },
22973
+ recordModelRef: (runId, ref) => {
22974
+ if (runModelRefPin.size > 4096) runModelRefPin.clear();
22975
+ runModelRefPin.set(runId, ref);
22976
+ },
22820
22977
  maxCostUsd: () => lastMaxCostUsd,
22821
22978
  // TASWM-535: поднятый потолок тяжёлого прогона живёт в его ленте — спавн читает её
22822
22979
  // оттуда же, откуда потом прочитает прод-сторож.
@@ -22891,7 +23048,9 @@ async function runDaemon(opts) {
22891
23048
  // реконсайла и его возраст. Возраст, а не момент: часы машины и прода
22892
23049
  // расходятся (ноутбук после сна — на минуты), а сверяет признак прод со своим
22893
23050
  // настенным временем. Не «демон запущен», а «цикл прокрутился».
22894
- tickPulse(tickHealth, Date.now(), tickIntervalMs)
23051
+ tickPulse(tickHealth, Date.now(), tickIntervalMs),
23052
+ // TASWM-592: установленные агенты машины — справочник для парка машин.
23053
+ cachedInstalledExecutorIds()
22895
23054
  );
22896
23055
  if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
22897
23056
  if (reply.commands.length) onHostCommands(reply.commands);
@@ -23330,16 +23489,18 @@ async function runDaemon(opts) {
23330
23489
  isAlive: pidAlive,
23331
23490
  // Учёт cost/usage (TASWM-188): реальный аккаунтант поверх RUN_LOGS_DIR + db.
23332
23491
  // TASWM-364: swarmModels — ТОТ ЖЕ снимок, что уехал в launcher: цена токенов считается
23333
- // по модели, на которой прогон реально работал.
23334
- accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels }),
23492
+ // по модели, на которой прогон реально работал. TASWM-592: исполнитель НЕ пинится —
23493
+ // аккаунтант сам резолвит его по прогону (итог писал харнесс ЭТОГО прогона).
23494
+ accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, swarmModels: () => lastSwarmModels, modelRefFor: (run) => runModelRefPin.get(run.id) ?? null }),
23335
23495
  // TASWM-413: тот же аккаунтант, но поводом служит завершённый ЗАПУСК, а не смерть
23336
23496
  // прогона в running — иначе сданный через done() прогон не получает цену никогда.
23337
- sweepCost: makeCostSweeper({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels, isAlive: pidAlive }),
23497
+ sweepCost: makeCostSweeper({ runLogsDir: RUN_LOGS_DIR, db, swarmModels: () => lastSwarmModels, isAlive: pidAlive, modelRefFor: (run) => runModelRefPin.get(run.id) ?? null }),
23338
23498
  // Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
23339
23499
  // (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
23340
- exitOutcome: makeExitClassifier(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
23500
+ // TASWM-592: исполнитель — по прогону (та же ссылка модели + живой снимок настроек).
23501
+ exitOutcome: makeExitClassifier(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), (run) => executorForRun(run, lastSwarmModels, process.env, runModelRefPin.get(run.id) ?? null)),
23341
23502
  // TASWM-487: вид обрыва — из того же result-файла и теми же ключами каталога.
23342
- exitAbort: makeAbortReader(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
23503
+ exitAbort: makeAbortReader(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), (run) => executorForRun(run, lastSwarmModels, process.env, runModelRefPin.get(run.id) ?? null)),
23343
23504
  // TASWM-487: жива ли работа на origin. Тот же фолбэк repo, что у removeWorktree выше:
23344
23505
  // у прогона в основном репозитории демона run.repo пуст.
23345
23506
  branchOnOrigin: (repo, branch) => remoteBranchExists(repo || opts.repo, branch),
@@ -23414,7 +23575,8 @@ async function runDaemon(opts) {
23414
23575
  lastCommitMs: (p) => worktreeLastCommitMs(p),
23415
23576
  // mtime транскрипта claude-сессии как liveness (TASWM-136): точнее коммитов — обновляется
23416
23577
  // на каждое действие дрона (Edit/Read/tool-use), не только на коммит.
23417
- transcriptMtimeMs: (p) => EXECUTOR.session.mtimeMs(p),
23578
+ // TASWM-592: где транскрипт лежит, знает харнесс ПРОГОНА — резолвим исполнителя по модели.
23579
+ transcriptMtimeMs: (run) => executorForRun(run, lastSwarmModels, process.env, runModelRefPin.get(run.id) ?? null).session.mtimeMs(run.worktree_path ?? ""),
23418
23580
  // ВЕС ЗАДАЧИ (TASWM-535): чем демон проверяет, что заявивший о тяжести дрон РЕАЛЬНО
23419
23581
  // продвигается (коммиты от базы прогона), и от какого потолка считать ему добавку —
23420
23582
  // от той же живой настройки роя, что уезжает харнессу на спавне.
@@ -23533,7 +23695,8 @@ async function runDaemon(opts) {
23533
23695
  // TASWM-514: с этого момента считаем попытки спавна — счётчик живёт ровно одну жизнь
23534
23696
  // процесса демона и рестарт не переживает (см. preStartAttempts).
23535
23697
  startedAt: Date.now(),
23536
- hasTranscript: executorHasSession,
23698
+ // TASWM-592: run-aware — где лежит сессия, знает харнесс ПРОГОНА.
23699
+ hasTranscript: (run) => !!run.worktree_path && executorForRun(run, lastSwarmModels, process.env, runModelRefPin.get(run.id) ?? null).session.exists(run.worktree_path),
23537
23700
  // CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
23538
23701
  // (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
23539
23702
  // строится над failing/failingRunIds ИЗ checks — без повторного `gh pr checks`.
@@ -23544,7 +23707,22 @@ async function runDaemon(opts) {
23544
23707
  // Штормовой режим (TASWM-23): health-probe на отбой (реальный claude) + внешний
23545
23708
  // сигнал SWARM_STORM_URL (2-й слой, если задан). Логика/пороги — в manageStorm/storm.ts.
23546
23709
  storm: {
23547
- probe: () => executorHealthy({ executor: EXECUTOR, timeoutMs: resolveStormConfig().probeTimeoutMs }),
23710
+ // TASWM-592: проба бьёт в исполнителя, на котором работают дроны (реестр провайдеров
23711
+ // по ссылке модели роя), — иначе рой на другом харнессе штормовал бы по вине claude
23712
+ // и наоборот.
23713
+ probe: () => {
23714
+ try {
23715
+ const ref = resolveRunModelRef({ kind: "drone" }, lastSwarmModels);
23716
+ return executorHealthy({
23717
+ executor: executorForRun({ kind: "drone" }, lastSwarmModels),
23718
+ timeoutMs: resolveStormConfig().probeTimeoutMs,
23719
+ env: providerEnv(ref.providerId, ref.model)
23720
+ });
23721
+ } catch (e) {
23722
+ console.error(`[storm] \u043F\u0440\u043E\u0431\u0430: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F/env \u043F\u043E \u043C\u043E\u0434\u0435\u043B\u0438 \u0440\u043E\u044F \u2014 ${String(e)}`);
23723
+ return Promise.resolve({ ok: false, reason: "exec", resetHint: null });
23724
+ }
23725
+ },
23548
23726
  // externalDown опционален (exactOptionalPropertyTypes): ключ есть только при
23549
23727
  // заданном SWARM_STORM_URL, иначе опускаем — не передаём undefined.
23550
23728
  ...SWARM_STORM_URL ? { externalDown: () => fetchExternalStorm(SWARM_STORM_URL) } : {}
@@ -23711,7 +23889,7 @@ async function runDaemon(opts) {
23711
23889
  wakeRun: async (slug) => {
23712
23890
  const run = db.activeRunForSlug(slug);
23713
23891
  if (!run) return null;
23714
- return wakeRun({ db, isAlive: pidAlive, respawn: deps.respawn, narrate: narrator.narrate }, run.id);
23892
+ return wakeRun({ db, isAlive: pidAlive, respawn: deps.respawn, narrate: narrator.narrate, ...deps.hasTranscript ? { hasTranscript: deps.hasTranscript } : {} }, run.id);
23715
23893
  },
23716
23894
  update: async (onStep) => {
23717
23895
  const pm = detectGlobalPm(process.argv[1]);
@@ -23861,7 +24039,7 @@ async function runDaemon(opts) {
23861
24039
  // Побудка прогона по id (TASWM-240): вход для `swarm wake <id>` — тот же respawn с
23862
24040
  // полными deps демона (getTask/verifyMode/реестр репо), что и авто-оживление, и по
23863
24041
  // зеркалу демона, а не по короткоживущему стору CLI.
23864
- wake: (runId) => wakeRun({ db, isAlive: pidAlive, respawn: deps.respawn, narrate: narrator.narrate }, runId),
24042
+ wake: (runId) => wakeRun({ db, isAlive: pidAlive, respawn: deps.respawn, narrate: narrator.narrate, ...deps.hasTranscript ? { hasTranscript: deps.hasTranscript } : {} }, runId),
23865
24043
  replay: (qid, chosen) => replayDecision(
23866
24044
  {
23867
24045
  db,