@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/README.md CHANGED
@@ -188,6 +188,13 @@ SWARM_EXECUTOR=codex swarm start # Codex CLI (`codex exec`)
188
188
  учёт стоимости, watchdog и воскрешение после обрыва работают одинаково. Что
189
189
  различается — прячет адаптер:
190
190
 
191
+ С TASWM-592 исполнитель — свойство ПРОГОНА: ссылка его модели (пин задачи →
192
+ настройка роя «Модель дронов» → env) выбирает харнесс через реестр провайдеров
193
+ (`executorForRun`). `SWARM_EXECUTOR` остался машинной ступенью: он пиннит прогоны,
194
+ чей провайдер не назван явно — машина «вся на codex» остаётся на codex, а прогон с
195
+ `openai/…` всё равно уйдёт к codex, с `anthropic/…` — к claude. Сменить модель роя
196
+ целиком можно и настройкой: «Модель дронов» = `openai/default`.
197
+
191
198
  | | `claude-code` | `codex` |
192
199
  |---|---|---|
193
200
  | запуск | `claude -p <промпт>` | `codex exec <промпт>` |
@@ -197,6 +204,17 @@ SWARM_EXECUTOR=codex swarm start # Codex CLI (`codex exec`)
197
204
  | сессии (живость дрона) | `~/.claude/projects/<worktree>` | `~/.taskless-swarm/executor-state/codex/<worktree>` |
198
205
  | скиллы роли | `--plugin-dir` | текстом в промпте (флага нет) |
199
206
 
207
+ ### GLM (Z.ai) — провайдер `zai` (интерим)
208
+
209
+ Подписка GLM Coding Plan работает через официальный Anthropic-совместимый эндпоинт
210
+ Z.ai, поэтому харнесс остаётся claude-code (аккаунт Anthropic не нужен). Ключ —
211
+ в окружении ДЕМОНА: `SWARM_ZAI_TOKEN` (консоль z.ai, CLI-ключ подписки), опционально
212
+ `SWARM_ZAI_BASE_URL` (прокси/регион). Переключение: «Модель дронов» = `zai/glm-5.3`
213
+ или поле модели задачи. Дрону ключ не наследуется — доходит только переведённый
214
+ `ANTHROPIC_AUTH_TOKEN`. Стоимость прогонов на подписке не измеряется (честный ноль
215
+ с пометкой, тарифы не выдумываем). Порядок обновления: сначала `swarm update` на
216
+ машинах, потом пин `zai/…` — старый демон не знает env-провязку и умрёт на спавне.
217
+
200
218
  Путь к исполняемому файлу, если он не в `PATH`: `SWARM_CLAUDE_BIN` /
201
219
  `SWARM_CODEX_BIN`. Неизвестное значение `SWARM_EXECUTOR` — ошибка на старте, а не
202
220
  тихий откат на дефолт: «думал, поехали на другом» узнаётся иначе только из счёта.
package/dist/cli.js CHANGED
@@ -668,6 +668,11 @@ function defaultModelFor(role, providerId = null) {
668
668
  const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
669
669
  return provider.roles[role];
670
670
  }
671
+ function executorIdForModelRef(ref) {
672
+ const providerId = parseModelRef(ref)?.providerId ?? DEFAULT_PROVIDER_ID;
673
+ const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
674
+ return provider.executorId;
675
+ }
671
676
  var HARNESS_DEFAULT_MODEL, MODEL_PROVIDERS, DEFAULT_PROVIDER_ID;
672
677
  var init_modelPolicy = __esm({
673
678
  "../swarm-core/src/modelPolicy.ts"() {
@@ -681,6 +686,21 @@ var init_modelPolicy = __esm({
681
686
  selectable: true,
682
687
  roles: { executor: "claude-opus-5-5", review: "claude-sonnet-5" }
683
688
  },
689
+ // TASWM-593 · GLM (Z.ai), INTERIM: подписка GLM Coding Plan работает через официальный
690
+ // Anthropic-совместимый эндпоинт Z.ai (api.z.ai/api/anthropic), поэтому харнесс — тот же
691
+ // claude-code, а всё различие провайдера — env процесса дрона (BASE_URL + токен, см.
692
+ // providerEnv в адаптере; клаудовский аккаунт НЕ нужен — бинарь бьёт в Z.ai). Interim
693
+ // для роя владельца до появления `--model` у ZCode CLI: тогда провайдер переезжает на
694
+ // собственный zcode-адаптер (волт reference_zcode_cli_headless_research).
695
+ // Дефолты ролей: glm-5.3 — флагман; каталог подписки снаружи не виден, имя сверено
696
+ // живой пробой, выдуманных имён в реестре нет — настройкой можно поставить любую.
697
+ {
698
+ id: "zai",
699
+ label: "GLM (Z.ai)",
700
+ executorId: "claude-code",
701
+ selectable: true,
702
+ roles: { executor: "glm-5.3", review: "glm-5.3" }
703
+ },
684
704
  // TASWM-565 · шаг 1 из четырёх: провайдер в реестре ЕСТЬ, выбрать его ещё нельзя.
685
705
  // Имён моделей здесь нет СОЗНАТЕЛЬНО: у владельца подписка, состав моделей в ней нам
686
706
  // неизвестен, а выдуманное имя хуже маркера «решает Codex».
@@ -2294,10 +2314,25 @@ function writeMcpConfig(spec) {
2294
2314
  writeFileSync2(path, JSON.stringify(cfg, null, 2));
2295
2315
  return path;
2296
2316
  }
2317
+ function providerEnv(providerId, model, env = process.env) {
2318
+ const compat = ANTHROPIC_COMPAT_PROVIDERS[providerId];
2319
+ if (!compat) return {};
2320
+ const token = (env[compat.tokenEnv] ?? "").trim();
2321
+ if (!token) {
2322
+ throw new Error(
2323
+ `\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.`
2324
+ );
2325
+ }
2326
+ return {
2327
+ ANTHROPIC_BASE_URL: (env[compat.baseUrlEnv] ?? "").trim() || compat.baseUrlDefault,
2328
+ ANTHROPIC_AUTH_TOKEN: token,
2329
+ ...model ? { ANTHROPIC_MODEL: model, ANTHROPIC_SMALL_FAST_MODEL: model } : {}
2330
+ };
2331
+ }
2297
2332
  function abortField(abort) {
2298
2333
  return abort ? { aborted: abort } : {};
2299
2334
  }
2300
- var ISOLATION_FLAGS, MCP_CONFIG_FILE, claudeCodeExecutor;
2335
+ var ISOLATION_FLAGS, MCP_CONFIG_FILE, ANTHROPIC_COMPAT_PROVIDERS, PROVIDER_TOKEN_ENV, claudeCodeExecutor;
2301
2336
  var init_claudeCode = __esm({
2302
2337
  "src/executors/claudeCode.ts"() {
2303
2338
  "use strict";
@@ -2308,21 +2343,31 @@ var init_claudeCode = __esm({
2308
2343
  init_transcriptSpend();
2309
2344
  ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
2310
2345
  MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
2346
+ ANTHROPIC_COMPAT_PROVIDERS = {
2347
+ zai: {
2348
+ baseUrlDefault: "https://api.z.ai/api/anthropic",
2349
+ baseUrlEnv: "SWARM_ZAI_BASE_URL",
2350
+ tokenEnv: "SWARM_ZAI_TOKEN"
2351
+ }
2352
+ };
2353
+ PROVIDER_TOKEN_ENV = Object.values(ANTHROPIC_COMPAT_PROVIDERS).map((p) => p.tokenEnv);
2311
2354
  claudeCodeExecutor = {
2312
2355
  id: "claude-code",
2313
2356
  title: "Claude Code (claude)",
2314
2357
  binEnv: "SWARM_CLAUDE_BIN",
2315
2358
  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" },
2316
- resolveBin: () => resolveClaudeBin(),
2359
+ resolveBin: (env) => resolveClaudeBin(env ? { env } : {}),
2317
2360
  // Стрелка, а не метод: `plan` могут вызвать в отрыве от объекта (деструктуризация в тестах,
2318
2361
  // проброс колбэком), и `this` тогда уедет в undefined.
2319
2362
  plan: (spec) => {
2320
2363
  const cfgPath = writeMcpConfig(spec);
2321
2364
  const hook = droneMergeGateHookPath(spec.worktree);
2322
2365
  const settings = spec.denyPaths.length > 0 || hook ? droneSettingsJson(spec.denyPaths, hook) : void 0;
2366
+ const providerEnvVars = providerEnv(spec.providerId, spec.model);
2323
2367
  return {
2324
2368
  bin: resolveClaudeBin(),
2325
- args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
2369
+ args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort),
2370
+ ...Object.keys(providerEnvVars).length > 0 ? { env: providerEnvVars } : {}
2326
2371
  };
2327
2372
  },
2328
2373
  parseResult(raw, model) {
@@ -2374,7 +2419,12 @@ var init_claudeCode = __esm({
2374
2419
  probe: {
2375
2420
  args: [...ISOLATION_FLAGS, "-p", "ok", "--max-turns", "1"],
2376
2421
  // TASWM-214: исчерпанный лимит подписки — НЕ шторм провайдера, и путать их нельзя.
2377
- limitPattern: /(weekly limit|usage limit|hit your .*limit)/i
2422
+ limitPattern: /(weekly limit|usage limit|hit your .*limit)/i,
2423
+ // TASWM-592: отвергнутый доступ — не «шторм»: ключ протух, аккаунт забанен/отключён,
2424
+ // регион закрыт (403 без VPN — живой кейс этого роя). Коды — только в контексте
2425
+ // ошибки (ревью PR #1482): голое «403» в arbitrary-выводе и POSIX permission denied
2426
+ // от локальной ФС советовали бы «чинить вход» не про ту болезнь.
2427
+ 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
2378
2428
  }
2379
2429
  };
2380
2430
  }
@@ -2505,7 +2555,7 @@ var init_codex = __esm({
2505
2555
  title: "Codex CLI (codex)",
2506
2556
  binEnv: "SWARM_CODEX_BIN",
2507
2557
  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" },
2508
- resolveBin: () => resolveCodexBin(),
2558
+ resolveBin: (env) => resolveCodexBin(env ?? process.env),
2509
2559
  // Стрелка, а не метод — см. тот же комментарий в claudeCode.ts.
2510
2560
  plan: (spec) => {
2511
2561
  const notice = skillsNotice(spec.skillDirs);
@@ -2554,12 +2604,48 @@ ${notice}` : spec.prompt;
2554
2604
  // Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
2555
2605
  // провайдера разом. Один короткий ответ, без тулов и без сети сверх модели.
2556
2606
  args: ["exec", "--json", SKIP_GIT_CHECK_FLAG, BYPASS_FLAG, "ok"],
2607
+ // TASWM-592: не вошёл/ключ отвергнут — не шторм; точные формулировки codex уточним
2608
+ // на живом исполнителе, поэтому паттерн широкий.
2609
+ authPattern: /\b(401|403)\b|not logged in|unauthorized|authentication|invalid api key/i,
2557
2610
  limitPattern: /(usage limit|rate limit|quota exceeded|insufficient_quota)/i
2558
2611
  }
2559
2612
  };
2560
2613
  }
2561
2614
  });
2562
2615
 
2616
+ // src/swarmModels.ts
2617
+ function pick2(v) {
2618
+ const s = typeof v === "string" ? v.trim() : "";
2619
+ return s.length > 0 ? s : null;
2620
+ }
2621
+ function isReviewKind(kind) {
2622
+ return kind === "reviewer" || kind === "verifier";
2623
+ }
2624
+ function modelRoleOf(kind) {
2625
+ return isReviewKind(kind) ? "review" : "executor";
2626
+ }
2627
+ function forKind(kind, swarm, norm) {
2628
+ if (!swarm) return null;
2629
+ return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
2630
+ }
2631
+ function resolveRunModelRef(run, swarm, env = process.env) {
2632
+ const configured = parseModelRef(
2633
+ pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
2634
+ );
2635
+ if (configured) return configured;
2636
+ const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
2637
+ return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
2638
+ }
2639
+ function resolveRunEffort(run, swarm, env = process.env) {
2640
+ return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
2641
+ }
2642
+ var init_swarmModels = __esm({
2643
+ "src/swarmModels.ts"() {
2644
+ "use strict";
2645
+ init_src();
2646
+ }
2647
+ });
2648
+
2563
2649
  // src/executors/types.ts
2564
2650
  var init_types2 = __esm({
2565
2651
  "src/executors/types.ts"() {
@@ -2577,12 +2663,22 @@ function resolveExecutor(id) {
2577
2663
  }
2578
2664
  return found;
2579
2665
  }
2666
+ function executorForRun(run, swarm, env = process.env, pinned = null) {
2667
+ const ref = pinned ?? resolveRunModelRef(run, swarm, env);
2668
+ if (ref.providerId === DEFAULT_PROVIDER_ID) {
2669
+ const envExecutor = (env.SWARM_EXECUTOR ?? "").trim();
2670
+ if (envExecutor) return resolveExecutor(envExecutor);
2671
+ }
2672
+ return resolveExecutor(executorIdForModelRef(`${ref.providerId}/${ref.model}`));
2673
+ }
2580
2674
  var DEFAULT_EXECUTOR_ID, EXECUTORS;
2581
2675
  var init_executors = __esm({
2582
2676
  "src/executors/index.ts"() {
2583
2677
  "use strict";
2678
+ init_src();
2584
2679
  init_claudeCode();
2585
2680
  init_codex();
2681
+ init_swarmModels();
2586
2682
  init_types2();
2587
2683
  init_claudeCode();
2588
2684
  init_codex();
@@ -3520,7 +3616,7 @@ var init_package = __esm({
3520
3616
  "package.json"() {
3521
3617
  package_default = {
3522
3618
  name: "@taskless-app/swarm",
3523
- version: "0.6.57",
3619
+ version: "0.6.59",
3524
3620
  type: "module",
3525
3621
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3526
3622
  keywords: [
@@ -3571,7 +3667,7 @@ var init_package = __esm({
3571
3667
  execa: "^10.0.0",
3572
3668
  zod: "3.25.76"
3573
3669
  },
3574
- gitHead: "e7c84e94f2f2e0ff28d5dfe8b11b43ef1291ce5e"
3670
+ gitHead: "13cdd65f2929d86153c0a52715c0d9891f8a1e4b"
3575
3671
  };
3576
3672
  }
3577
3673
  });
@@ -6021,6 +6117,13 @@ function blockedTroubles(f) {
6021
6117
  fixes: [{ what: "\u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0440\u0435\u0437\u0435\u0442\u0430 \u043B\u0438\u043C\u0438\u0442\u0430; \u0440\u0430\u0431\u043E\u0442\u0430 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043D\u0438\u043A\u0443\u0434\u0430 \u043D\u0435 \u0434\u0435\u043D\u0435\u0442\u0441\u044F", command: "swarm status # \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0440\u0435\u0437\u0435\u0442\u0430" }]
6022
6118
  });
6023
6119
  }
6120
+ if (work.storm?.reason === "auth") {
6121
+ out.push({
6122
+ id: "storm-auth",
6123
+ why: "\u0434\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 (\u043A\u043B\u044E\u0447/\u0430\u043A\u043A\u0430\u0443\u043D\u0442/\u0440\u0435\u0433\u0438\u043E\u043D) \u2014 \u043F\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: \u0440\u0435\u0442\u0440\u0430\u0438 \u0442\u0443\u0442 \u0431\u0435\u0441\u0441\u0438\u043B\u044C\u043D\u044B",
6124
+ fixes: [{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u0445\u043E\u0434 \u0438 \u043A\u043B\u044E\u0447 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F", command: "swarm doctor" }]
6125
+ });
6126
+ }
6024
6127
  if (work.storm?.reason === "provider") {
6025
6128
  out.push({
6026
6129
  id: "storm-provider",
@@ -6158,7 +6261,7 @@ function detailLines(f) {
6158
6261
  if (otherLine.length) lines.push(` \u0435\u0449\u0451 \u0432 \u0440\u043E\u0435: ${otherLine.map(([st, n]) => `${st} ${n}`).join(", ")}`);
6159
6262
  if (w.storm) {
6160
6263
  lines.push(
6161
- ` ${w.storm.reason === "limit" ? `\u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F${w.storm.hint ? ` (${w.storm.hint})` : ""}` : "\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u0448\u0442\u043E\u0440\u043C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430)"}`
6264
+ ` ${w.storm.reason === "limit" ? `\u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F${w.storm.hint ? ` (${w.storm.hint})` : ""}` : w.storm.reason === "auth" ? "\u0434\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 (\u043A\u043B\u044E\u0447/\u0430\u043A\u043A\u0430\u0443\u043D\u0442/\u0440\u0435\u0433\u0438\u043E\u043D)" : "\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u0448\u0442\u043E\u0440\u043C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430)"}`
6162
6265
  );
6163
6266
  }
6164
6267
  return lines;
@@ -9245,7 +9348,7 @@ var SwarmClient = class _SwarmClient {
9245
9348
  *
9246
9349
  * Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
9247
9350
  */
9248
- async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick) {
9351
+ async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick, executors) {
9249
9352
  try {
9250
9353
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
9251
9354
  if (daemonVersion) args.daemonVersion = daemonVersion;
@@ -9275,6 +9378,7 @@ var SwarmClient = class _SwarmClient {
9275
9378
  if (tick && Number.isFinite(tick.seq) && Number.isFinite(tick.ageMs) && tick.intervalMs > 0) {
9276
9379
  args.tick = { seq: Math.max(0, Math.trunc(tick.seq)), ageMs: Math.max(0, Math.trunc(tick.ageMs)), intervalMs: Math.trunc(tick.intervalMs) };
9277
9380
  }
9381
+ if (executors) args.executorsAvailable = executors;
9278
9382
  const res = await this.call("swarm_heartbeat", args);
9279
9383
  if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
9280
9384
  this.heartbeatOkAt = this.now();
@@ -10825,8 +10929,9 @@ function stormFacts(db2) {
10825
10929
  if (!db2.stormActive()) return null;
10826
10930
  const since = db2.currentStormStartedAt();
10827
10931
  const recs = since ? db2.stormProbeRecordsSince(since) : [];
10932
+ const last = lastProbeReason(recs);
10828
10933
  return {
10829
- reason: lastProbeReason(recs) === "limit" ? "limit" : "provider",
10934
+ reason: last === "limit" ? "limit" : last === "auth" ? "auth" : "provider",
10830
10935
  hint: recs.length ? recs[recs.length - 1].resetHint ?? null : null
10831
10936
  };
10832
10937
  }
@@ -11452,6 +11557,13 @@ function stormBanner(storm) {
11452
11557
  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.`
11453
11558
  };
11454
11559
  }
11560
+ if (storm.reason === "auth") {
11561
+ return {
11562
+ ico: "\u{1F511}",
11563
+ 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",
11564
+ 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`)."
11565
+ };
11566
+ }
11455
11567
  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";
11456
11568
  return {
11457
11569
  ico: "\u26A1",
@@ -12009,6 +12121,10 @@ function paintStorm(){ var b=document.getElementById('storm-banner'); if(!b) ret
12009
12121
  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>'+
12010
12122
  '<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>';
12011
12123
  b.hidden=false; return; }
12124
+ if(st.reason==='auth'){
12125
+ 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>'+
12126
+ '<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>';
12127
+ b.hidden=false; return; }
12012
12128
  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';
12013
12129
  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>'+
12014
12130
  '<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>';
@@ -12534,39 +12650,10 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
12534
12650
  };
12535
12651
  }
12536
12652
 
12537
- // src/swarmModels.ts
12538
- init_src();
12539
- function pick2(v) {
12540
- const s = typeof v === "string" ? v.trim() : "";
12541
- return s.length > 0 ? s : null;
12542
- }
12543
- function isReviewKind(kind) {
12544
- return kind === "reviewer" || kind === "verifier";
12545
- }
12546
- function modelRoleOf(kind) {
12547
- return isReviewKind(kind) ? "review" : "executor";
12548
- }
12549
- function forKind(kind, swarm, norm) {
12550
- if (!swarm) return null;
12551
- return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
12552
- }
12553
- function resolveRunModelRef(run, swarm, env = process.env) {
12554
- const configured = parseModelRef(
12555
- pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
12556
- );
12557
- if (configured) return configured;
12558
- const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
12559
- return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
12560
- }
12561
- function resolveRunModel(run, swarm, env = process.env) {
12562
- return resolveRunModelRef(run, swarm, env).model;
12563
- }
12564
- function resolveRunEffort(run, swarm, env = process.env) {
12565
- return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
12566
- }
12567
-
12568
12653
  // src/launcher.ts
12654
+ init_swarmModels();
12569
12655
  init_executors();
12656
+ init_claudeCode();
12570
12657
  init_src();
12571
12658
  var requireFromHere = createRequire(import.meta.url);
12572
12659
  function resolveTsxCli() {
@@ -12617,7 +12704,10 @@ var DAEMON_STATE_ENV = [
12617
12704
  "SWARM_OUTBOX",
12618
12705
  "SWARM_HEARTBEAT",
12619
12706
  "SWARM_REVIEW_CUTOFF",
12620
- "TASKLESS_TOKEN"
12707
+ "TASKLESS_TOKEN",
12708
+ // TASWM-593 (ревью PR #1483): машинные ключи совместимых провайдеров — дрону не наследуются;
12709
+ // доходит только переведённый ANTHROPIC_AUTH_TOKEN из plan.env, который мешается ПОСЛЕ чистки.
12710
+ ...PROVIDER_TOKEN_ENV
12621
12711
  ];
12622
12712
  function bundledRoleDir(kind) {
12623
12713
  return join14(dirname4(fileURLToPath2(import.meta.url)), "..", "roles", kind);
@@ -12716,13 +12806,18 @@ function makeLauncher(cfg) {
12716
12806
  ...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
12717
12807
  }
12718
12808
  };
12719
- const model = resolveRunModel(run, cfg.swarmModels?.() ?? null);
12809
+ const modelRef = resolveRunModelRef(run, cfg.swarmModels?.() ?? null);
12720
12810
  const effort = resolveRunEffort(run, cfg.swarmEfforts?.() ?? null);
12721
12811
  try {
12722
12812
  cfg.recordEffort?.(run.id, effort);
12723
12813
  } catch (e) {
12724
12814
  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)}`);
12725
12815
  }
12816
+ try {
12817
+ cfg.recordModelRef?.(run.id, modelRef);
12818
+ } catch (e) {
12819
+ 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)}`);
12820
+ }
12726
12821
  const realHome = cfg.droneIsolation?.realHome ?? homedir5();
12727
12822
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
12728
12823
  if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(join14(cfg.droneIsolation.swarmHome, "token"))) {
@@ -12730,12 +12825,13 @@ function makeLauncher(cfg) {
12730
12825
  `[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join14(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
12731
12826
  );
12732
12827
  }
12733
- const executor = cfg.executor ?? resolveExecutor();
12828
+ const executor = cfg.executor ?? executorForRun(run, cfg.swarmModels?.() ?? null);
12734
12829
  const plan = executor.plan({
12735
12830
  worktree,
12736
12831
  prompt,
12737
12832
  continueSession: !!continueSession,
12738
- model,
12833
+ model: modelRef.model,
12834
+ providerId: modelRef.providerId,
12739
12835
  mcp,
12740
12836
  skillDirs: roleDirs(worktree, run.kind),
12741
12837
  // Закрытые дрону пути имеют смысл только при теневом HOME: без него дрон и так
@@ -14451,9 +14547,10 @@ switch (cmd) {
14451
14547
  runLogsDir: RUN_LOGS_DIR,
14452
14548
  // TASWM-419: каталог лога — по стабильному ключу прогона (тот же адрес, что у демона).
14453
14549
  runLogKeys: (r) => db.runLogKeys(r.id),
14454
- // Тот же исполнитель, что у демона (TASWM-349): `swarm wake` при мёртвом демоне не
14455
- // должен внезапно поднимать дрона другим харнессом.
14456
- executor: EXECUTOR,
14550
+ // TASWM-592: исполнитель НЕ пинится — лаунчер резолвит его по модели ПРОГОНА, тем же
14551
+ // правилом, что и живой демон («какой харнесс» теперь свойство модели, не машины).
14552
+ // Живой настройки роя тут нет (демон молчит — иначе мы сюда не попали бы), поэтому
14553
+ // каскад короче: пин прогона → env машины → дефолт реестра.
14457
14554
  droneIsolation: DRONE_ISOLATION,
14458
14555
  // TASWM-450: ПОТОЛОК ТРАТ обязателен и здесь. Демон собирает лаунчер с
14459
14556
  // `maxCostUsd: () => lastMaxCostUsd`, а этот путь его не передавал вовсе — значит
@@ -14477,6 +14574,9 @@ switch (cmd) {
14477
14574
  db,
14478
14575
  isAlive: pidAlive,
14479
14576
  // тот же kill(pid,0)-гейт liveness, что у демона (procCleanup.ts)
14577
+ // TASWM-592 (ревью): сессию ищет харнесс ПРОГОНА, а не глобальный исполнитель —
14578
+ // иначе побудка codex-прогона при мёртвом демоне теряла продолжаемую сессию.
14579
+ hasTranscript: (run2) => !!run2.worktree_path && executorForRun(run2, null).session.exists(run2.worktree_path),
14480
14580
  respawn: (r, mode) => respawnRun(
14481
14581
  { db, launch, repoFallback: SWARM_REPO, worktreesDir: effectiveWorktreesDir(), getTask: () => Promise.resolve(null) },
14482
14582
  r,