@taskless-app/swarm 0.6.10 → 0.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1220 -356
  2. package/dist/overmind.js +1128 -301
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -49,7 +49,7 @@ function isMilestoneKind(kind) {
49
49
  return MILESTONE_KIND_SET.has(kind);
50
50
  }
51
51
  function toRunKind(role) {
52
- return role === "hero" || role === "reviewer" ? role : "drone";
52
+ return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
53
53
  }
54
54
  var EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
55
55
  var init_types = __esm({
@@ -338,6 +338,74 @@ var init_reviewPhase = __esm({
338
338
  }
339
339
  });
340
340
 
341
+ // ../swarm-core/src/verifyVerdict.ts
342
+ function isVerifyVerdict(v) {
343
+ return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
344
+ }
345
+ function toVerifyVerdict(v) {
346
+ return isVerifyVerdict(v) ? v : null;
347
+ }
348
+ function isNegativeVerifyVerdict(v) {
349
+ return v === "not_reproduced" || v === "found_issues";
350
+ }
351
+ function verifyVerdictLabel(v) {
352
+ switch (v) {
353
+ case "confirmed":
354
+ return "\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E";
355
+ case "not_reproduced":
356
+ return "\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F";
357
+ case "found_issues":
358
+ return "\u041D\u0410\u0428\u0401\u041B";
359
+ }
360
+ }
361
+ function verifyStats(runs) {
362
+ let confirmed = 0;
363
+ let notReproduced = 0;
364
+ let foundIssues = 0;
365
+ for (const r of runs) {
366
+ switch (toVerifyVerdict(r.verify_verdict)) {
367
+ case "confirmed":
368
+ confirmed++;
369
+ break;
370
+ case "not_reproduced":
371
+ notReproduced++;
372
+ break;
373
+ case "found_issues":
374
+ foundIssues++;
375
+ break;
376
+ default:
377
+ break;
378
+ }
379
+ }
380
+ const negative = notReproduced + foundIssues;
381
+ const total = confirmed + negative;
382
+ return { total, confirmed, notReproduced, foundIssues, negative, negativeShare: total ? negative / total : 0 };
383
+ }
384
+ function verifyStatsByDrone(runs) {
385
+ const byName = /* @__PURE__ */ new Map();
386
+ for (const r of runs) {
387
+ if (!toVerifyVerdict(r.verify_verdict)) continue;
388
+ const name = (r.name ?? "").trim() || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)";
389
+ const bucket = byName.get(name);
390
+ if (bucket) bucket.push(r);
391
+ else byName.set(name, [r]);
392
+ }
393
+ return [...byName.entries()].map(([name, rs]) => ({ name, ...verifyStats(rs) })).sort((a, b) => b.negative - a.negative || b.total - a.total || a.name.localeCompare(b.name));
394
+ }
395
+ function verifyStatsLine(s) {
396
+ if (!s.total) return "\u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u0432 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442";
397
+ const pct = Math.round(s.negativeShare * 100);
398
+ return `\u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u0432 ${s.total} \xB7 \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0445 ${s.negative} (${pct}%): \u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u043B\u043E\u0441\u044C ${s.notReproduced}, \u043D\u0430\u0448\u043B\u0438 \u0434\u0435\u0444\u0435\u043A\u0442 ${s.foundIssues}`;
399
+ }
400
+ var VERIFY_VERDICTS, VERIFY_VERDICT_SET;
401
+ var init_verifyVerdict = __esm({
402
+ "../swarm-core/src/verifyVerdict.ts"() {
403
+ "use strict";
404
+ VERIFY_VERDICTS = ["confirmed", "not_reproduced", "found_issues"];
405
+ VERIFY_VERDICT_SET = new Set(VERIFY_VERDICTS);
406
+ }
407
+ });
408
+
341
409
  // ../swarm-core/src/mergeGate.ts
342
410
  function computeMergeGate(input) {
343
411
  if (!input.hasRun) {
@@ -346,17 +414,33 @@ function computeMergeGate(input) {
346
414
  if (!input.autoReview) {
347
415
  return { allowed: true, code: "auto_review_off", reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F" };
348
416
  }
349
- if (input.reviewVerdict === "approve") {
350
- return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
351
- }
352
417
  if (input.reviewVerdict === "changes") {
353
418
  return { allowed: false, code: "changes_requested", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043F\u0440\u0430\u0432\u043A\u0438 (REQUEST CHANGES) \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430" };
354
419
  }
355
- return {
356
- allowed: false,
357
- code: "no_verdict",
358
- reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
359
- };
420
+ if (input.reviewVerdict !== "approve") {
421
+ return {
422
+ allowed: false,
423
+ code: "no_verdict",
424
+ reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
425
+ };
426
+ }
427
+ if (input.autoVerify) {
428
+ if (input.verifyVerdict == null) {
429
+ return {
430
+ allowed: false,
431
+ code: "no_verify_verdict",
432
+ reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve, \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 (TASWM-344)"
433
+ };
434
+ }
435
+ if (isNegativeVerifyVerdict(input.verifyVerdict)) {
436
+ return {
437
+ allowed: false,
438
+ code: "verify_negative",
439
+ reason: `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u044B\u043D\u0435\u0441 \xAB${verifyVerdictLabel(input.verifyVerdict)}\xBB \u2014 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u043E\u0441\u044C, \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-344)`
440
+ };
441
+ }
442
+ }
443
+ return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
360
444
  }
361
445
  function mergeGateLine(d) {
362
446
  return `${d.allowed ? "\u2705 \u043C\u0435\u0440\u0436 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D" : "\u26D4 \u043C\u0435\u0440\u0436 \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D"}: ${d.reason}`;
@@ -364,6 +448,7 @@ function mergeGateLine(d) {
364
448
  var init_mergeGate = __esm({
365
449
  "../swarm-core/src/mergeGate.ts"() {
366
450
  "use strict";
451
+ init_verifyVerdict();
367
452
  }
368
453
  });
369
454
 
@@ -619,7 +704,7 @@ function splitQueue(items, ref) {
619
704
  return { queue, hosted };
620
705
  }
621
706
  function groupByHost(items, ref, opts = {}) {
622
- const { hosts = [], nowMs = Date.now(), staleMs = HOST_HEARTBEAT_STALE_MS } = opts;
707
+ const { hosts = [], nowMs = Date.now(), staleMs: staleMs2 = HOST_HEARTBEAT_STALE_MS } = opts;
623
708
  const infoById = new Map(hosts.map((h) => [h.hostId, h]));
624
709
  const byKey = /* @__PURE__ */ new Map();
625
710
  const ensure = (key, fallbackName) => {
@@ -648,25 +733,28 @@ function groupByHost(items, ref, opts = {}) {
648
733
  ensure(key, hostName ?? null).runs.push(item);
649
734
  }
650
735
  const groups = [...byKey.values()];
651
- for (const g of groups) g.offline = isHostOffline(g.lastSeenAt, nowMs, staleMs);
736
+ for (const g of groups) g.offline = isHostOffline(g.lastSeenAt, nowMs, staleMs2);
652
737
  return groups.sort((a, b) => {
653
738
  if (a.unknown !== b.unknown) return a.unknown ? 1 : -1;
654
739
  return a.name.localeCompare(b.name, "ru");
655
740
  });
656
741
  }
657
- function isHostOffline(lastSeenAt, nowMs, staleMs = HOST_HEARTBEAT_STALE_MS) {
742
+ function isHostOffline(lastSeenAt, nowMs, staleMs2 = HOST_HEARTBEAT_STALE_MS) {
658
743
  if (!lastSeenAt) return false;
659
744
  const at = Date.parse(lastSeenAt);
660
745
  if (Number.isNaN(at)) return false;
661
- return nowMs - at > staleMs;
746
+ return nowMs - at > staleMs2;
662
747
  }
663
- function hostStateText(host, nowMs = Date.now(), staleMs = HOST_HEARTBEAT_STALE_MS) {
748
+ function hostStateText(host, nowMs = Date.now(), staleMs2 = HOST_HEARTBEAT_STALE_MS) {
664
749
  if (host.draining) return host.drainReason ? `\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435 \u2014 ${host.drainReason}` : "\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435";
750
+ if (host.accessRevoked) return HOST_ACCESS_REVOKED_TEXT;
665
751
  if (!host.lastSeenAt) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
666
752
  const at = Date.parse(host.lastSeenAt);
667
753
  if (Number.isNaN(at)) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
668
- if (!isHostOffline(host.lastSeenAt, nowMs, staleMs)) {
669
- return host.paused ? host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435" : "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
754
+ if (!isHostOffline(host.lastSeenAt, nowMs, staleMs2)) {
755
+ if (host.paused) return host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435";
756
+ if (host.storm) return host.stormReason ? `\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 ${host.stormReason}` : "\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 \u043D\u043E\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442";
757
+ return "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
670
758
  }
671
759
  const min = Math.max(1, Math.round((nowMs - at) / 6e4));
672
760
  return `\u043C\u043E\u043B\u0447\u0438\u0442 ${min} \u043C\u0438\u043D`;
@@ -674,7 +762,7 @@ function hostStateText(host, nowMs = Date.now(), staleMs = HOST_HEARTBEAT_STALE_
674
762
  function shouldGroupByHost(groups) {
675
763
  return groups.length > 1;
676
764
  }
677
- var ANY_HOST_TOKENS, UNKNOWN_HOST_KEY, UNKNOWN_HOST_LABEL, QUEUE_LABEL, NOT_STARTED_STATUSES, HOST_HEARTBEAT_STALE_MS;
765
+ var ANY_HOST_TOKENS, UNKNOWN_HOST_KEY, UNKNOWN_HOST_LABEL, QUEUE_LABEL, NOT_STARTED_STATUSES, HOST_HEARTBEAT_STALE_MS, HOST_ACCESS_REVOKED_TEXT;
678
766
  var init_hosts = __esm({
679
767
  "../swarm-core/src/hosts.ts"() {
680
768
  "use strict";
@@ -684,6 +772,7 @@ var init_hosts = __esm({
684
772
  QUEUE_LABEL = "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438";
685
773
  NOT_STARTED_STATUSES = /* @__PURE__ */ new Set(["queued"]);
686
774
  HOST_HEARTBEAT_STALE_MS = 45e3;
775
+ HOST_ACCESS_REVOKED_TEXT = "\u0434\u043E\u0441\u0442\u0443\u043F \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u2014 \u0432\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 (swarm login)";
687
776
  }
688
777
  });
689
778
 
@@ -706,17 +795,17 @@ function parseHostResources(raw) {
706
795
  measuredAt: v.measuredAt
707
796
  };
708
797
  }
709
- function hostResourcesFresh(resources, nowMs, staleMs = HOST_RESOURCES_STALE_MS) {
798
+ function hostResourcesFresh(resources, nowMs, staleMs2 = HOST_RESOURCES_STALE_MS) {
710
799
  if (!resources) return false;
711
800
  const at = Date.parse(resources.measuredAt);
712
801
  if (Number.isNaN(at)) return false;
713
- return nowMs - at <= staleMs;
802
+ return nowMs - at <= staleMs2;
714
803
  }
715
804
  function gb(value) {
716
805
  return value < 10 ? value.toFixed(1) : String(Math.round(value));
717
806
  }
718
- function hostResourcesText(resources, nowMs = Date.now(), staleMs = HOST_RESOURCES_STALE_MS) {
719
- if (!hostResourcesFresh(resources, nowMs, staleMs)) return null;
807
+ function hostResourcesText(resources, nowMs = Date.now(), staleMs2 = HOST_RESOURCES_STALE_MS) {
808
+ if (!hostResourcesFresh(resources, nowMs, staleMs2)) return null;
720
809
  const r = resources;
721
810
  const parts = [];
722
811
  if (r.cpuBusyPct != null) parts.push(`CPU ${Math.round(r.cpuBusyPct)}%`);
@@ -1226,6 +1315,7 @@ var init_src = __esm({
1226
1315
  init_timeline();
1227
1316
  init_reviewPhase();
1228
1317
  init_mergeGate();
1318
+ init_verifyVerdict();
1229
1319
  init_status();
1230
1320
  init_deps();
1231
1321
  init_time();
@@ -1338,11 +1428,18 @@ var init_swarmFiles = __esm({
1338
1428
  });
1339
1429
 
1340
1430
  // src/pricing.ts
1341
- function costFromUsage(usage2, model, table = MODEL_PRICING) {
1342
- const pricing = model ? table[model] : void 0;
1343
- if (!pricing) {
1431
+ function pricingAt(pricing, at) {
1432
+ const intro = pricing.intro;
1433
+ if (!intro) return pricing;
1434
+ const day = at.toISOString().slice(0, 10);
1435
+ return day <= intro.until ? { inputPerMTok: intro.inputPerMTok, outputPerMTok: intro.outputPerMTok } : pricing;
1436
+ }
1437
+ function costFromUsage(usage2, model, table = MODEL_PRICING, at = /* @__PURE__ */ new Date()) {
1438
+ const entry = model ? table[model] : void 0;
1439
+ if (!entry) {
1344
1440
  return { costUsd: 0, warning: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0430: "${model ?? ""}" \u2014 cost \u043D\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D` };
1345
1441
  }
1442
+ const pricing = pricingAt(entry, at);
1346
1443
  const input = usage2.input_tokens ?? 0;
1347
1444
  const output = usage2.output_tokens ?? 0;
1348
1445
  const cacheWrite = usage2.cache_creation_input_tokens ?? 0;
@@ -1355,10 +1452,12 @@ var init_pricing = __esm({
1355
1452
  "src/pricing.ts"() {
1356
1453
  "use strict";
1357
1454
  MODEL_PRICING = {
1358
- // Вводная цена Sonnet 5 действует до 31.08.2026, после чего вернётся к 3/15.
1359
- // Дата не автоматизируется намеренно: одна строка раз в жизни дешевле, чем
1360
- // ветвление по времени внутри расчёта стоимости.
1361
- "claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
1455
+ // Sonnet 5: вводные 2/10 по 31.08.2026 включительно, дальше базовые 3/15.
1456
+ "claude-sonnet-5": {
1457
+ inputPerMTok: 3,
1458
+ outputPerMTok: 15,
1459
+ intro: { inputPerMTok: 2, outputPerMTok: 10, until: "2026-08-31" }
1460
+ },
1362
1461
  "claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
1363
1462
  // Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
1364
1463
  "claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
@@ -1802,7 +1901,7 @@ function tokenFromFile() {
1802
1901
  return "";
1803
1902
  }
1804
1903
  }
1805
- var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
1904
+ var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
1806
1905
  var init_config = __esm({
1807
1906
  "src/config.ts"() {
1808
1907
  "use strict";
@@ -1873,6 +1972,7 @@ var init_config = __esm({
1873
1972
  TASKLESS_TASK_URL_BASE = process.env.TASKLESS_TASK_URL_BASE || (TASKLESS_WORKSPACE_SLUG ? `${TASKLESS_API.replace(/\/$/, "")}/app/${TASKLESS_WORKSPACE_SLUG}` : "");
1874
1973
  SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
1875
1974
  SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
1975
+ SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
1876
1976
  REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join4(SWARM_HOME, "review-cutoff.json");
1877
1977
  SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
1878
1978
  SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
@@ -1997,65 +2097,13 @@ var init_repoRegistry = __esm({
1997
2097
  }
1998
2098
  });
1999
2099
 
2000
- // src/repoScan.ts
2001
- import { readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
2002
- import { join as join6 } from "path";
2003
- var init_repoScan = __esm({
2004
- "src/repoScan.ts"() {
2005
- "use strict";
2006
- init_repoRegistry();
2007
- }
2008
- });
2009
-
2010
- // src/repoDiscovery.ts
2011
- import { homedir as homedir3 } from "os";
2012
- import { readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
2013
- import { isAbsolute, join as join7, resolve as resolve2 } from "path";
2014
- function repoMapLines(rows) {
2015
- if (!rows?.length) return [];
2016
- const missing = rows.filter((r) => !r.path).length;
2017
- const head = `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F: ${rows.length}` + (missing ? `, \u0431\u0435\u0437 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${missing}` : "");
2018
- const body = rows.map((r) => {
2019
- if (!r.path) return ` \u2717 ${r.repoFullName} \u2014 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u041D\u0415\u0422 (\u0441\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u044E)`;
2020
- const how = r.source ? ` (${SOURCE_TEXT[r.source]})` : "";
2021
- const alt = r.alternatives.length ? ` \u26A0 \u0435\u0441\u0442\u044C \u0435\u0449\u0451 ${r.alternatives.length}: ${r.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C` : "";
2022
- return ` ${r.alternatives.length ? "\u26A0" : "\u2713"} ${r.repoFullName} \u2192 ${r.path}${how}${alt}`;
2023
- });
2024
- return [head, ...body];
2025
- }
2026
- function readRepoMapCache(path) {
2027
- try {
2028
- const raw = JSON.parse(readFileSync6(path, "utf8"));
2029
- if (!Array.isArray(raw?.entries)) return [];
2030
- return raw.entries.filter(
2031
- (e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
2032
- );
2033
- } catch {
2034
- return [];
2035
- }
2036
- }
2037
- var SOURCE_TEXT;
2038
- var init_repoDiscovery = __esm({
2039
- "src/repoDiscovery.ts"() {
2040
- "use strict";
2041
- init_src();
2042
- init_config();
2043
- init_repoScan();
2044
- SOURCE_TEXT = {
2045
- configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
2046
- cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
2047
- scan: "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C"
2048
- };
2049
- }
2050
- });
2051
-
2052
2100
  // package.json
2053
2101
  var package_default;
2054
2102
  var init_package = __esm({
2055
2103
  "package.json"() {
2056
2104
  package_default = {
2057
2105
  name: "@taskless-app/swarm",
2058
- version: "0.6.10",
2106
+ version: "0.6.12",
2059
2107
  type: "module",
2060
2108
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2061
2109
  keywords: [
@@ -2105,7 +2153,7 @@ var init_package = __esm({
2105
2153
  execa: "^10.0.0",
2106
2154
  zod: "3.25.76"
2107
2155
  },
2108
- gitHead: "06e2edbcc9a21f0a1ef402868ea37b2b7d591020"
2156
+ gitHead: "43c844efdbea61a27c54edc17a111d658ce6e2b4"
2109
2157
  };
2110
2158
  }
2111
2159
  });
@@ -2123,18 +2171,18 @@ var init_version = __esm({
2123
2171
 
2124
2172
  // src/droneChannel.ts
2125
2173
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
2126
- import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2174
+ import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
2127
2175
  import { dirname as dirname2 } from "path";
2128
2176
  import { serve } from "@hono/node-server";
2129
2177
  function loadChannelSecret(path) {
2130
2178
  try {
2131
- const s = readFileSync8(path, "utf8").trim();
2179
+ const s = readFileSync6(path, "utf8").trim();
2132
2180
  if (s.length >= 32) return s;
2133
2181
  } catch {
2134
2182
  }
2135
2183
  const secret = randomBytes(32).toString("hex");
2136
2184
  mkdirSync5(dirname2(path), { recursive: true });
2137
- writeFileSync6(path, secret, { mode: 384 });
2185
+ writeFileSync5(path, secret, { mode: 384 });
2138
2186
  try {
2139
2187
  chmodSync(path, 384);
2140
2188
  } catch {
@@ -2174,30 +2222,34 @@ var init_droneChannel = __esm({
2174
2222
  });
2175
2223
 
2176
2224
  // src/agentToken.ts
2177
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
2178
- import { join as join14 } from "path";
2225
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
2226
+ import { join as join8 } from "path";
2179
2227
  function loadAgentToken() {
2180
2228
  try {
2181
- const s = JSON.parse(readFileSync10(STORE_PATH2, "utf8"));
2229
+ const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
2182
2230
  return s.token ? s : null;
2183
2231
  } catch {
2184
2232
  return null;
2185
2233
  }
2186
2234
  }
2187
2235
  function saveAgentToken(s) {
2188
- writeFileSync8(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
2236
+ writeFileSync6(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
2189
2237
  }
2190
2238
  function clearAgentToken() {
2191
2239
  try {
2192
- unlinkSync3(STORE_PATH2);
2240
+ unlinkSync2(STORE_PATH);
2193
2241
  } catch {
2194
2242
  }
2195
2243
  }
2196
2244
  async function mintAgentToken(args, fetchFn = fetch) {
2197
- const res = await fetchFn(`${base2(args.api)}/api/swarm/agent/token`, {
2245
+ const res = await fetchFn(`${base(args.api)}/api/swarm/agent/token`, {
2198
2246
  method: "POST",
2199
2247
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
2200
- body: JSON.stringify(args.label ? { label: args.label } : {})
2248
+ body: JSON.stringify({
2249
+ ...args.label ? { label: args.label } : {},
2250
+ ...args.hostId ? { hostId: args.hostId } : {},
2251
+ ...args.hostName ? { hostName: args.hostName } : {}
2252
+ })
2201
2253
  });
2202
2254
  const text = await res.text();
2203
2255
  if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
@@ -2207,8 +2259,11 @@ async function mintAgentToken(args, fetchFn = fetch) {
2207
2259
  }
2208
2260
  async function probeAgentToken(args, fetchFn = fetch) {
2209
2261
  try {
2210
- const res = await fetchFn(`${base2(args.api)}/api/auth/me`, {
2211
- headers: { Authorization: `Bearer ${args.token}` }
2262
+ const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
2263
+ headers: { Authorization: `Bearer ${args.token}` },
2264
+ // Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
2265
+ // то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
2266
+ signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
2212
2267
  });
2213
2268
  if (res.status === 401 || res.status === 403) return "revoked";
2214
2269
  return res.ok ? "ok" : "unreachable";
@@ -2224,13 +2279,69 @@ function isRevokedTokenError(e) {
2224
2279
  function revokedTokenLine() {
2225
2280
  return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
2226
2281
  }
2227
- var STORE_PATH2, base2;
2282
+ function revokedTokenComplaint() {
2283
+ return [
2284
+ "",
2285
+ "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
2286
+ "\u2502 \u0422\u041E\u041A\u0415\u041D \u0420\u041E\u042F \u041D\u0415 \u041F\u0420\u0418\u041D\u042F\u0422 (401). \u0414\u043E\u0441\u0442\u0443\u043F \u043A Taskless \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D. \u2502",
2287
+ "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
2288
+ "\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u2014 \u043E\u0434\u043D\u0430 \u0438\u0437 \u0442\u0440\u0451\u0445, \u0432\u0441\u0435 \u0442\u0440\u0438 \u0440\u0435\u0448\u0430\u044E\u0442\u0441\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C:",
2289
+ " \u2022 \xAB\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\xBB \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0442\u043E\u043A\u0435\u043D \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u044B;",
2290
+ " \u2022 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u0440\u043E\u044F\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439) \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D\u044B \u0432\u0441\u0435;",
2291
+ " \u2022 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C \u2014 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C.",
2292
+ "",
2293
+ "\u0412\u0445\u043E\u0434 \u043D\u0430 \u0414\u0420\u0423\u0413\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435 \u044D\u0442\u043E\u0442 \u0442\u043E\u043A\u0435\u043D \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0433\u0430\u0441\u0438\u0442 (TASWM-324): \u0443 \u043A\u0430\u0436\u0434\u043E\u0439",
2294
+ "\u043C\u0430\u0448\u0438\u043D\u044B \u0441\u0432\u043E\u0439 \u0442\u043E\u043A\u0435\u043D, \u043F\u0440\u0438\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 \u043A \u0435\u0451 hostId.",
2295
+ "",
2296
+ "\u0414\u0435\u043C\u043E\u043D \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0438 \u043E\u0442\u0447\u0451\u0442\u044B",
2297
+ "\u043D\u0435 \u043F\u0438\u0448\u0443\u0442\u0441\u044F. \u041F\u043E\u0447\u0438\u043D\u0438 \u0432\u0445\u043E\u0434\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E: swarm login",
2298
+ "\u041F\u0440\u043E\u0431\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442, \u0430 \u0432\u043E\u0439\u0442\u0438 \u043C\u0435\u0448\u0430\u0435\u0442 \u0441\u0442\u0430\u0440\u044B\u0439 \u0444\u0430\u0439\u043B: swarm login --force",
2299
+ ""
2300
+ ].join("\n");
2301
+ }
2302
+ var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
2228
2303
  var init_agentToken = __esm({
2229
2304
  "src/agentToken.ts"() {
2230
2305
  "use strict";
2231
2306
  init_config();
2232
- STORE_PATH2 = join14(SWARM_HOME, "agent.json");
2233
- base2 = (api) => api.replace(/\/$/, "");
2307
+ STORE_PATH = join8(SWARM_HOME, "agent.json");
2308
+ base = (api) => api.replace(/\/$/, "");
2309
+ TOKEN_PROBE_TIMEOUT_MS = 8e3;
2310
+ }
2311
+ });
2312
+
2313
+ // src/linkFailure.ts
2314
+ function describeError(e, maxDepth = 4) {
2315
+ const parts = [];
2316
+ const seen = /* @__PURE__ */ new Set();
2317
+ let cur = e;
2318
+ for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
2319
+ seen.add(cur);
2320
+ const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2321
+ const code = cur.code;
2322
+ const codeText = code === void 0 || code === null ? "" : String(code);
2323
+ const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2324
+ if (label && !parts.includes(label)) parts.push(label);
2325
+ cur = cur.cause;
2326
+ }
2327
+ return parts.join(" \u2190 ") || String(e);
2328
+ }
2329
+ function explainLinkFailure(e) {
2330
+ const detail = describeError(e);
2331
+ if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
2332
+ if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
2333
+ if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
2334
+ if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
2335
+ return { kind: "rejected", detail };
2336
+ }
2337
+ var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
2338
+ var init_linkFailure = __esm({
2339
+ "src/linkFailure.ts"() {
2340
+ "use strict";
2341
+ init_agentToken();
2342
+ UNKNOWN_LINK_REASON = "\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B";
2343
+ OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout/i;
2344
+ MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
2234
2345
  }
2235
2346
  });
2236
2347
 
@@ -2443,6 +2554,90 @@ var init_daemonLink = __esm({
2443
2554
  }
2444
2555
  });
2445
2556
 
2557
+ // src/pauseState.ts
2558
+ import { existsSync as existsSync9, readFileSync as readFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
2559
+ import { join as join16 } from "path";
2560
+ function pauseStatePath(home) {
2561
+ return join16(home, "pause.json");
2562
+ }
2563
+ function loadPauseState(home, io = {}) {
2564
+ const exists = io.exists ?? existsSync9;
2565
+ const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
2566
+ const path = pauseStatePath(home);
2567
+ if (!exists(path)) return ACTIVE;
2568
+ try {
2569
+ const parsed = JSON.parse(readFile2(path));
2570
+ if (parsed.paused !== true) return ACTIVE;
2571
+ return {
2572
+ paused: true,
2573
+ at: typeof parsed.at === "string" ? parsed.at : "",
2574
+ source: parsed.source === "web" || parsed.source === "remote" ? parsed.source : "cli",
2575
+ ...typeof parsed.reason === "string" && parsed.reason.trim() ? { reason: parsed.reason.trim() } : {}
2576
+ };
2577
+ } catch {
2578
+ return ACTIVE;
2579
+ }
2580
+ }
2581
+ function savePauseState(home, state, io = {}) {
2582
+ const path = pauseStatePath(home);
2583
+ if (!state.paused) {
2584
+ const remove = io.remove ?? ((p) => existsSync9(p) && unlinkSync5(p));
2585
+ remove(path);
2586
+ return;
2587
+ }
2588
+ const writeFile3 = io.writeFile ?? ((p, data) => {
2589
+ writeFileSync9(`${p}.tmp`, data);
2590
+ renameSync4(`${p}.tmp`, p);
2591
+ });
2592
+ writeFile3(path, JSON.stringify(state, null, 2));
2593
+ }
2594
+ function makePauseState(source, reason, now = Date.now()) {
2595
+ return {
2596
+ paused: true,
2597
+ at: new Date(now).toISOString(),
2598
+ source,
2599
+ ...reason && reason.trim() ? { reason: reason.trim() } : {}
2600
+ };
2601
+ }
2602
+ function pauseText(state, now = Date.now()) {
2603
+ if (!state?.paused) return "\u0440\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u0435\u043D \u2014 \u0431\u0435\u0440\u0451\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443";
2604
+ const since = state.at ? sinceText(state.at, now) : "";
2605
+ const who = { cli: "\u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435", web: "\u0441 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0439 \u043C\u043E\u0440\u0434\u044B", remote: "\u0438\u0437 Taskless" }[state.source];
2606
+ const why = state.reason ? ` \u2014 ${state.reason}` : "";
2607
+ return `\u0440\u043E\u0439 \u041D\u0410 \u041F\u0410\u0423\u0417\u0415${since} (${who})${why}`;
2608
+ }
2609
+ function sinceText(at, now) {
2610
+ const ms = Date.parse(at);
2611
+ if (Number.isNaN(ms)) return "";
2612
+ const min = Math.max(0, Math.round((now - ms) / 6e4));
2613
+ if (min < 1) return " (\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E)";
2614
+ if (min < 60) return ` (${min} \u043C\u0438\u043D)`;
2615
+ return ` (${Math.round(min / 60)} \u0447)`;
2616
+ }
2617
+ var ACTIVE;
2618
+ var init_pauseState = __esm({
2619
+ "src/pauseState.ts"() {
2620
+ "use strict";
2621
+ ACTIVE = { paused: false, at: "", source: "cli" };
2622
+ }
2623
+ });
2624
+
2625
+ // src/heartbeat.ts
2626
+ import { readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync10 } from "fs";
2627
+ function readHeartbeat(path) {
2628
+ try {
2629
+ const ms = Number(readFileSync11(path, "utf8").trim());
2630
+ return Number.isFinite(ms) ? ms : null;
2631
+ } catch {
2632
+ return null;
2633
+ }
2634
+ }
2635
+ var init_heartbeat = __esm({
2636
+ "src/heartbeat.ts"() {
2637
+ "use strict";
2638
+ }
2639
+ });
2640
+
2446
2641
  // src/db.ts
2447
2642
  var db_exports = {};
2448
2643
  __export(db_exports, {
@@ -2450,7 +2645,7 @@ __export(db_exports, {
2450
2645
  });
2451
2646
  import { createRequire as createRequire2 } from "module";
2452
2647
  import { existsSync as existsSync10 } from "fs";
2453
- import { resolve as resolve3 } from "path";
2648
+ import { resolve as resolve2 } from "path";
2454
2649
  function rowToQuestion(row) {
2455
2650
  return {
2456
2651
  id: row.id,
@@ -2500,6 +2695,10 @@ function rowToRun(row) {
2500
2695
  resolves_run_id: row.resolves_run_id ?? null,
2501
2696
  reviews_run_id: row.reviews_run_id ?? null,
2502
2697
  review_verdict: row.review_verdict ?? null,
2698
+ verifies_run_id: row.verifies_run_id ?? null,
2699
+ // TASWM-344: через узкий парсер, а не сырой строкой — в зеркале может лежать значение,
2700
+ // записанное демоном другой версии; непонятное здесь значит «вердикта нет».
2701
+ verify_verdict: toVerifyVerdict(row.verify_verdict),
2503
2702
  review_phase: row.review_phase ?? null,
2504
2703
  // вычисляемая фаза ревью (TASWM-230)
2505
2704
  review_cycle: row.review_cycle ?? 0,
@@ -2568,6 +2767,8 @@ CREATE TABLE IF NOT EXISTS runs (
2568
2767
  resolves_run_id INTEGER,
2569
2768
  reviews_run_id INTEGER,
2570
2769
  review_verdict TEXT,
2770
+ verifies_run_id INTEGER,
2771
+ verify_verdict TEXT,
2571
2772
  review_cycle INTEGER NOT NULL DEFAULT 0,
2572
2773
  task_title TEXT,
2573
2774
  task_desc TEXT,
@@ -2644,6 +2845,12 @@ CREATE TABLE IF NOT EXISTS imports (
2644
2845
  // вердикт (approve|changes), который ревьюер пишет на проверяемый прогон.
2645
2846
  "ALTER TABLE runs ADD COLUMN reviews_run_id INTEGER",
2646
2847
  "ALTER TABLE runs ADD COLUMN review_verdict TEXT",
2848
+ // Верификатор (TASWM-344): прогон, который пытается ОПРОВЕРГНУТЬ работу
2849
+ // #verifies_run_id, и его вердикт (confirmed|not_reproduced|found_issues) на
2850
+ // проверяемом прогоне. Своя пара колонок, а не переиспользование reviews_*:
2851
+ // ревью и верификация идут на одном PR одновременно и считаются раздельно.
2852
+ "ALTER TABLE runs ADD COLUMN verifies_run_id INTEGER",
2853
+ "ALTER TABLE runs ADD COLUMN verify_verdict TEXT",
2647
2854
  // Авто-ревью v2 (TASWM-162): номер цикла ревью — пере-ревью после фикс-итерации.
2648
2855
  "ALTER TABLE runs ADD COLUMN review_cycle INTEGER NOT NULL DEFAULT 0",
2649
2856
  // Вычисляемая фаза ревью (TASWM-230): человеко-фраза «чего ждёт PR», её считает демон.
@@ -2680,7 +2887,7 @@ CREATE TABLE IF NOT EXISTS imports (
2680
2887
  createRun(input) {
2681
2888
  const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
2682
2889
  const res = this.db.prepare(
2683
- "INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
2890
+ "INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
2684
2891
  ).run(
2685
2892
  input.task_slug,
2686
2893
  name,
@@ -2698,6 +2905,8 @@ CREATE TABLE IF NOT EXISTS imports (
2698
2905
  input.decision ? JSON.stringify(input.decision) : null,
2699
2906
  input.resolves_run_id ?? null,
2700
2907
  input.reviews_run_id ?? null,
2908
+ input.verifies_run_id ?? null,
2909
+ // прогон-верификатор (TASWM-344); обычный прогон — null
2701
2910
  input.review_cycle ?? 0,
2702
2911
  // цикл ревью для ревьюера (авто-ревью v2, TASWM-162); обычный прогон — 0
2703
2912
  input.priority ?? 0,
@@ -2708,7 +2917,7 @@ CREATE TABLE IF NOT EXISTS imports (
2708
2917
  // воркспейс задачи прогона (TASWM-288); null → глобальный пин
2709
2918
  );
2710
2919
  const id = Number(res.lastInsertRowid);
2711
- const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
2920
+ const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
2712
2921
  this.addEvent(id, "spawned", spawnNote);
2713
2922
  return this.getRun(id);
2714
2923
  }
@@ -2734,13 +2943,14 @@ CREATE TABLE IF NOT EXISTS imports (
2734
2943
  */
2735
2944
  ensureRun(input) {
2736
2945
  this.db.prepare(
2737
- "INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?)"
2946
+ "INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, verifies_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?, ?)"
2738
2947
  ).run(
2739
2948
  input.id,
2740
2949
  input.task_slug,
2741
2950
  input.kind ?? "drone",
2742
2951
  input.name || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)",
2743
2952
  input.reviews_run_id ?? null,
2953
+ input.verifies_run_id ?? null,
2744
2954
  input.hero_run_id ?? null
2745
2955
  );
2746
2956
  }
@@ -2885,6 +3095,31 @@ CREATE TABLE IF NOT EXISTS imports (
2885
3095
  const rows = this.db.prepare(`SELECT * FROM runs WHERE reviews_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(reviewRunId);
2886
3096
  return rows.map(rowToRun);
2887
3097
  }
3098
+ /**
3099
+ * ВЕРИФИКАЦИЯ (TASWM-344) — те же три счётчика, что у ревью выше, но по своей связи
3100
+ * `verifies_run_id` и со своим капом. Скоуп «текущий цикл ревью» тот же и по той же
3101
+ * причине: фикс-итерация бампит review_cycle и обнуляет вердикты (reviewTransitionPatch),
3102
+ * значит исправленную работу обязан проверить СВЕЖИЙ верификатор, а не считаться уже
3103
+ * полученным подтверждением по коду, которого больше нет.
3104
+ */
3105
+ verifierCountFor(verifyRunId) {
3106
+ const row = this.db.prepare(
3107
+ "SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status NOT IN ('failed','stalled','stopped','superseded') AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
3108
+ ).get(verifyRunId, verifyRunId);
3109
+ return row.n;
3110
+ }
3111
+ /** Плоский кап попыток верификации на цикл — любой статус, кроме сброшенных оператором. */
3112
+ verifierAttemptCountFor(verifyRunId) {
3113
+ const row = this.db.prepare(
3114
+ "SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status != 'superseded' AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
3115
+ ).get(verifyRunId, verifyRunId);
3116
+ return row.n;
3117
+ }
3118
+ /** Нетерминальные верификаторы прогона — закрыть зависших, когда PR уже смержен/закрыт. */
3119
+ activeVerifiersFor(verifyRunId) {
3120
+ const rows = this.db.prepare(`SELECT * FROM runs WHERE verifies_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(verifyRunId);
3121
+ return rows.map(rowToRun);
3122
+ }
2888
3123
  /**
2889
3124
  * Последний живой (не терминальный) прогон по slug — для swarm cancel. Служебные
2890
3125
  * сабраны (reviews_run_id/resolves_run_id — ревьюер/резолвер, TASWM-273) исключены:
@@ -2899,7 +3134,7 @@ CREATE TABLE IF NOT EXISTS imports (
2899
3134
  */
2900
3135
  activeRunForSlug(slug) {
2901
3136
  const row = this.db.prepare(
2902
- `SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL ORDER BY id DESC LIMIT 1`
3137
+ `SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id DESC LIMIT 1`
2903
3138
  ).get(slug);
2904
3139
  return row ? rowToRun(row) : null;
2905
3140
  }
@@ -2939,7 +3174,7 @@ CREATE TABLE IF NOT EXISTS imports (
2939
3174
  if (!depends_on || depends_on.length === 0) return [];
2940
3175
  const slugs = [...new Set(depends_on)];
2941
3176
  const rows = this.db.prepare(
2942
- `SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL ORDER BY id`
3177
+ `SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id`
2943
3178
  ).all(...slugs);
2944
3179
  const bySlug = /* @__PURE__ */ new Map();
2945
3180
  for (const r of rows) {
@@ -2973,7 +3208,7 @@ CREATE TABLE IF NOT EXISTS imports (
2973
3208
  * транзакции: прервётся — маркер не встанет, повтор безопасен.
2974
3209
  */
2975
3210
  importDb(sourcePath, repo) {
2976
- const abs = resolve3(sourcePath);
3211
+ const abs = resolve2(sourcePath);
2977
3212
  const repoN = normalizeRepo(repo);
2978
3213
  const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
2979
3214
  if (already) return { imported: false, runs: already.run_count };
@@ -3303,24 +3538,554 @@ CREATE TABLE IF NOT EXISTS imports (
3303
3538
  }
3304
3539
  });
3305
3540
 
3306
- // src/platform.ts
3541
+ // src/repoScan.ts
3542
+ import { readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
3543
+ import { join as join17 } from "path";
3544
+ var init_repoScan = __esm({
3545
+ "src/repoScan.ts"() {
3546
+ "use strict";
3547
+ init_repoRegistry();
3548
+ }
3549
+ });
3550
+
3551
+ // src/repoDiscovery.ts
3552
+ import { homedir as homedir4 } from "os";
3553
+ import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
3554
+ import { isAbsolute as isAbsolute3, join as join18, resolve as resolve3 } from "path";
3555
+ function repoMapLines(rows) {
3556
+ if (!rows?.length) return [];
3557
+ const missing = rows.filter((r) => !r.path).length;
3558
+ const head = `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F: ${rows.length}` + (missing ? `, \u0431\u0435\u0437 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${missing}` : "");
3559
+ const body = rows.map((r) => {
3560
+ if (!r.path) return ` \u2717 ${r.repoFullName} \u2014 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u041D\u0415\u0422 (\u0441\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u044E)`;
3561
+ const how = r.source ? ` (${SOURCE_TEXT[r.source]})` : "";
3562
+ const alt = r.alternatives.length ? ` \u26A0 \u0435\u0441\u0442\u044C \u0435\u0449\u0451 ${r.alternatives.length}: ${r.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C` : "";
3563
+ return ` ${r.alternatives.length ? "\u26A0" : "\u2713"} ${r.repoFullName} \u2192 ${r.path}${how}${alt}`;
3564
+ });
3565
+ return [head, ...body];
3566
+ }
3567
+ function readRepoMapCache(path) {
3568
+ try {
3569
+ const raw = JSON.parse(readFileSync13(path, "utf8"));
3570
+ if (!Array.isArray(raw?.entries)) return [];
3571
+ return raw.entries.filter(
3572
+ (e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
3573
+ );
3574
+ } catch {
3575
+ return [];
3576
+ }
3577
+ }
3578
+ var SOURCE_TEXT;
3579
+ var init_repoDiscovery = __esm({
3580
+ "src/repoDiscovery.ts"() {
3581
+ "use strict";
3582
+ init_src();
3583
+ init_config();
3584
+ init_linkFailure();
3585
+ init_repoScan();
3586
+ SOURCE_TEXT = {
3587
+ configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
3588
+ cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
3589
+ scan: "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C"
3590
+ };
3591
+ }
3592
+ });
3593
+
3594
+ // src/release.ts
3595
+ async function fetchLatestVersion(opts = {}) {
3596
+ const fetchFn = opts.fetchFn ?? fetch;
3597
+ const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
3598
+ const pkg = opts.pkg ?? SWARM_PACKAGE;
3599
+ const ctrl = new AbortController();
3600
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
3601
+ try {
3602
+ const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
3603
+ signal: ctrl.signal,
3604
+ headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
3605
+ });
3606
+ if (!res.ok) return null;
3607
+ const body = await res.json();
3608
+ return typeof body.version === "string" && body.version ? body.version : null;
3609
+ } catch {
3610
+ return null;
3611
+ } finally {
3612
+ clearTimeout(timer);
3613
+ }
3614
+ }
3615
+ function isOutdated(current, latest) {
3616
+ if (!latest || !current) return false;
3617
+ return compareVersionDirs(latest, current) > 0;
3618
+ }
3619
+ var SWARM_PACKAGE, LATEST_TIMEOUT_MS;
3620
+ var init_release = __esm({
3621
+ "src/release.ts"() {
3622
+ "use strict";
3623
+ init_src();
3624
+ init_claudeBin();
3625
+ SWARM_PACKAGE = SWARM_PACKAGE_NAME;
3626
+ LATEST_TIMEOUT_MS = 4e3;
3627
+ }
3628
+ });
3629
+
3630
+ // src/swarmStatus.ts
3631
+ function agoText(iso, now) {
3632
+ if (!iso) return null;
3633
+ const ms = Date.parse(iso);
3634
+ if (Number.isNaN(ms)) return null;
3635
+ const sec = Math.max(0, Math.round((now - ms) / 1e3));
3636
+ if (sec < 60) return "\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E";
3637
+ const min = Math.round(sec / 60);
3638
+ if (min < 60) return `${min} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434`;
3639
+ const hr = Math.round(min / 60);
3640
+ return hr < 24 ? `${hr} \u0447 \u043D\u0430\u0437\u0430\u0434` : `${Math.round(hr / 24)} \u0434\u043D \u043D\u0430\u0437\u0430\u0434`;
3641
+ }
3642
+ function uptimeText(startedAt, now) {
3643
+ if (!startedAt) return null;
3644
+ const ms = Date.parse(startedAt);
3645
+ if (Number.isNaN(ms)) return null;
3646
+ const min = Math.max(0, Math.floor((now - ms) / 6e4));
3647
+ if (min < 60) return `${min} \u043C\u0438\u043D`;
3648
+ const hr = Math.floor(min / 60);
3649
+ return `${hr} \u0447 ${min % 60} \u043C\u0438\u043D`;
3650
+ }
3651
+ function linkTrouble(link) {
3652
+ if (link.kind === "ok" || link.kind === "no-prod" || link.kind === "starting") return null;
3653
+ if (link.kind === "unmeasured") {
3654
+ return {
3655
+ id: "link-unmeasured",
3656
+ why: `\u043F\u0440\u043E \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u0441\u043A\u0430\u0437\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0434\u0435\u043C\u043E\u043D \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 \u043A\u043E\u0434\u0435 \u0421\u0422\u0410\u0420\u0415\u0415 \u044D\u0442\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B${link.detail ? ` (${link.detail})` : ""} \u2014 \u0442\u043E\u043A\u0435\u043D \u043C\u043E\u0433 \u0431\u044B\u0442\u044C \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442, \u0430 \u0441\u0435\u0442\u044C \u043B\u0435\u0436\u0430\u0442\u044C, \u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u0443\u0432\u0438\u0434\u0438\u0442`,
3657
+ fixes: [
3658
+ { what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u043C \u043A\u043E\u0434\u0435 \u2014 \u043E\u043D \u0441\u0430\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u043D\u0435 \u043F\u0435\u0440\u0435\u0435\u0434\u0435\u0442", command: "swarm stop && swarm start" },
3659
+ { what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438 \u0442\u043E\u043A\u0435\u043D \u043D\u0430\u043F\u0440\u044F\u043C\u0443\u044E, \u043F\u043E\u043A\u0430 \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u044B\u0439", command: "swarm doctor" }
3660
+ ]
3661
+ };
3662
+ }
3663
+ if (link.kind === "no-token") {
3664
+ return {
3665
+ id: "link-no-token",
3666
+ why: "\u0432\u0445\u043E\u0434 \u0432 Taskless \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u2014 \u0431\u0440\u0430\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435\u043E\u0442\u043A\u0443\u0434\u0430",
3667
+ fixes: [{ what: "\u0432\u043E\u0439\u0442\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441", command: "swarm login" }]
3668
+ };
3669
+ }
3670
+ if (link.kind === "revoked") {
3671
+ return {
3672
+ id: "link-revoked",
3673
+ why: "Taskless \u041D\u0415 \u041F\u0420\u0418\u041D\u0418\u041C\u0410\u0415\u0422 \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F (401 invalid_token): \u043E\u043D \u0441\u0431\u0440\u043E\u0448\u0435\u043D \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C \u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C",
3674
+ fixes: [{ what: "\u0432\u043E\u0439\u0442\u0438 \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u0442\u043E\u043A\u0435\u043D \u0432\u044B\u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043D\u043E\u0432\u044B\u0439", command: "swarm login" }]
3675
+ };
3676
+ }
3677
+ if (link.kind === "server") {
3678
+ return {
3679
+ id: "link-server",
3680
+ why: `Taskless \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0448\u0438\u0431\u043A\u043E\u0439 (${link.detail}) \u2014 \u044D\u0442\u043E \u043D\u0435 \u043C\u0430\u0448\u0438\u043D\u0430 \u0438 \u043D\u0435 \u0442\u043E\u043A\u0435\u043D`,
3681
+ fixes: [
3682
+ { what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0447\u0442\u043E \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441\u0430\u043C \u043F\u0440\u043E\u0434 (\u043E\u0448\u0438\u0431\u043A\u0430 5xx \u2014 \u043D\u0430 \u0435\u0433\u043E \u0441\u0442\u043E\u0440\u043E\u043D\u0435)", command: `curl -sS -o /dev/null -w '%{http_code}\\n' ${link.api}/api/health` },
3683
+ { what: "\u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u043D\u0435 \u0442\u0435\u0440\u044F\u0435\u0442\u0441\u044F: \u0431\u0443\u0444\u0435\u0440 \u0434\u043E\u0436\u043C\u0451\u0442\u0441\u044F \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u043E\u0434 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F", command: "swarm status # \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043C\u0438\u043D\u0443\u0442" }
3684
+ ]
3685
+ };
3686
+ }
3687
+ if (link.kind === "down") {
3688
+ return {
3689
+ id: "link-down",
3690
+ why: `\u0441\u0432\u044F\u0437\u0438 \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u043D\u0435\u0442${link.detail ? `: ${link.detail}` : ""} \u2014 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B, \u043E\u0442\u0441\u044E\u0434\u0430 \u0435\u0451 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u0442\u044C \u0442\u043E\u0436\u0435`,
3691
+ fixes: [
3692
+ { what: "\u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u0441\u0430\u043C \u0434\u0435\u043C\u043E\u043D \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 \u0435\u0433\u043E \u043B\u043E\u0433", command: "swarm logs | tail -50" },
3693
+ { what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \u0446\u0435\u043B\u0438\u043A\u043E\u043C (\u0441\u0435\u0442\u044C, \u0442\u043E\u043A\u0435\u043D, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435)", command: "swarm doctor" }
3694
+ ]
3695
+ };
3696
+ }
3697
+ return {
3698
+ id: "link-network",
3699
+ why: `\u0434\u043E Taskless \u043D\u0435 \u0434\u043E\u0441\u0442\u0443\u0447\u0430\u0442\u044C\u0441\u044F (${link.detail}) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u043D\u0438 \u0437\u0430\u0434\u0430\u0447, \u043D\u0438 \u043C\u0430\u0448\u0438\u043D`,
3700
+ fixes: [
3701
+ {
3702
+ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u0435\u0442\u044C \u0422\u0415\u041C \u0416\u0415 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C, \u043A\u0430\u043A\u0438\u043C \u0445\u043E\u0434\u0438\u0442 \u0434\u0435\u043C\u043E\u043D (\u0432 WSL curl \u043C\u043E\u0436\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C, \u043A\u043E\u0433\u0434\u0430 fetch \u0438\u0437 Node \u2014 \u043D\u0435\u0442: \u0434\u0440\u0443\u0433\u043E\u0439 \u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u0438 \u043F\u0440\u043E\u043A\u0441\u0438)",
3703
+ command: `node -e "fetch('${link.api}/api/health').then(r=>console.log('HTTP',r.status)).catch(e=>console.log('\u041D\u0415 \u0425\u041E\u0414\u0418\u0422:',e.message))"`
3704
+ },
3705
+ { what: "\u0435\u0441\u043B\u0438 fetch \u043D\u0435 \u0445\u043E\u0434\u0438\u0442, \u0430 curl \u0445\u043E\u0434\u0438\u0442 \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 HTTPS_PROXY/NO_PROXY \u0438 DNS \u0432 \u044D\u0442\u043E\u043C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0438", command: "env | grep -i proxy; cat /etc/resolv.conf" }
3706
+ ]
3707
+ };
3708
+ }
3709
+ function waitingForDrone(work) {
3710
+ return work.queuedReady ?? work.queued;
3711
+ }
3712
+ function blockedTroubles(f) {
3713
+ const out = [];
3714
+ const { work, daemon } = f;
3715
+ if (daemon.restartWaiting) {
3716
+ out.push({
3717
+ id: "restart-waiting",
3718
+ why: "\u0434\u0435\u043C\u043E\u043D \u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439, \u0447\u0442\u043E\u0431\u044B \u043F\u043E\u0433\u0430\u0441\u043D\u0443\u0442\u044C/\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\u0441\u044F \u2014 \u043D\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D",
3719
+ fixes: [{ what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u043A\u043E\u0433\u043E \u043E\u043D \u0436\u0434\u0451\u0442", command: "swarm ls" }]
3720
+ });
3721
+ }
3722
+ if (work.storm?.reason === "limit") {
3723
+ out.push({
3724
+ id: "storm-limit",
3725
+ why: `\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${work.storm.hint ? ` (${work.storm.hint})` : ""} \u2014 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u0436\u0438\u0432, \u043D\u043E \u0434\u0440\u043E\u043D\u044B \u043D\u0435 \u043F\u043E\u0435\u0434\u0443\u0442 \u0434\u043E \u0440\u0435\u0437\u0435\u0442\u0430`,
3726
+ 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" }]
3727
+ });
3728
+ }
3729
+ if (work.storm?.reason === "provider") {
3730
+ out.push({
3731
+ id: "storm-provider",
3732
+ why: "\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) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0434\u0440\u043E\u043D\u043E\u0432, \u043F\u043E\u043A\u0430 \u043E\u043D \u043B\u0435\u0436\u0438\u0442",
3733
+ fixes: [{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F \u0446\u0435\u043B\u0438\u043A\u043E\u043C", command: "swarm doctor" }]
3734
+ });
3735
+ }
3736
+ if (work.maxDrones != null && work.maxDrones <= 0) {
3737
+ out.push({
3738
+ id: "cap-zero",
3739
+ why: `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0434\u0440\u043E\u043D\u043E\u0432 \u0440\u043E\u044F \u0440\u0430\u0432\u0435\u043D ${work.maxDrones} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u043D\u0435\u043A\u043E\u0433\u043E \u043F\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435, \u0430 \u043D\u0435 \u043F\u043E \u043F\u043E\u043B\u043E\u043C\u043A\u0435`,
3740
+ fixes: [{ what: "\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439 \u2192 maxDrones)", command: "swarm hosts # \u043A\u0430\u043A\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B \u0438 \u0441 \u043A\u0430\u043A\u043E\u0439 \u0451\u043C\u043A\u043E\u0441\u0442\u044C\u044E" }]
3741
+ });
3742
+ }
3743
+ return out;
3744
+ }
3745
+ function idleTrouble(f) {
3746
+ const { work, daemon } = f;
3747
+ if (waitingForDrone(work) === 0 || work.busy > 0) return null;
3748
+ if (daemon.pause?.paused || daemon.restartWaiting || work.storm || work.maxDrones != null && work.maxDrones <= 0) return null;
3749
+ if (work.questions > 0) {
3750
+ return {
3751
+ id: "idle-questions",
3752
+ why: `\u0440\u043E\u0439 \u0436\u0434\u0451\u0442 \u0427\u0415\u041B\u041E\u0412\u0415\u041A\u0410: \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 ${work.questions}`,
3753
+ fixes: [
3754
+ { what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0432\u043E\u043F\u0440\u043E\u0441\u044B", command: "swarm questions" },
3755
+ { what: "\u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C", command: "swarm answer <id> <\u043E\u0442\u0432\u0435\u0442>" }
3756
+ ]
3757
+ };
3758
+ }
3759
+ return null;
3760
+ }
3761
+ function troubles(f) {
3762
+ const out = [];
3763
+ if (!f.daemon.alive) {
3764
+ const pause = f.daemon.pause?.paused;
3765
+ const port = f.daemon.webPort ?? 7420;
3766
+ out.push(
3767
+ f.daemon.processAlive ? {
3768
+ // Процесс жив и пульсит, а морда молчит. Советовать здесь `swarm start` —
3769
+ // значит предложить поднять ВТОРОЙ демон на занятый порт: он не поднимется,
3770
+ // а человек решит, что рой не чинится. Ведём туда, где ответ: чем занят порт
3771
+ // и что в логе висящего процесса.
3772
+ id: "web-silent",
3773
+ why: `\u0434\u0435\u043C\u043E\u043D \u0416\u0418\u0412 (\u043F\u0443\u043B\u044C\u0441 \u0441\u0432\u0435\u0436\u0438\u0439), \u043D\u043E \u0435\u0433\u043E \u043C\u043E\u0440\u0434\u0430 127.0.0.1:${port} \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u2014 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0440\u043E\u044F \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435 \u0443 \u043A\u043E\u0433\u043E, \u0438 \u0441\u0430\u043C \u043E\u043D \u043C\u043E\u0433 \u0437\u0430\u0432\u0438\u0441\u043D\u0443\u0442\u044C`,
3774
+ fixes: [
3775
+ { what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u0447\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0442\u0438\u043A", command: "swarm logs | tail -50" },
3776
+ { what: `\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u043A\u0442\u043E \u0434\u0435\u0440\u0436\u0438\u0442 \u043F\u043E\u0440\u0442 (\u0447\u0443\u0436\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 / \u0434\u0440\u0443\u0433\u043E\u0439 SWARM_WEB_PORT \u0432 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0438)`, command: `ss -ltnp | grep ${port}` },
3777
+ { what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430, \u0435\u0441\u043B\u0438 \u043E\u043D \u0437\u0430\u0432\u0438\u0441", command: "swarm stop && swarm start" }
3778
+ ]
3779
+ } : {
3780
+ id: "daemon-dead",
3781
+ why: "\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
3782
+ fixes: [
3783
+ { what: "\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439", command: "swarm start" },
3784
+ ...pause ? [{ what: "\u0441\u043D\u044F\u0442\u044C \u043F\u0430\u0443\u0437\u0443 \u2014 \u0438\u043D\u0430\u0447\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0432\u043E\u0437\u044C\u043C\u0451\u0442", command: "swarm resume" }] : []
3785
+ ]
3786
+ }
3787
+ );
3788
+ const linkWhenDead = linkTrouble(f.link);
3789
+ if (linkWhenDead) out.push(linkWhenDead);
3790
+ return out;
3791
+ }
3792
+ const link = linkTrouble(f.link);
3793
+ if (link) out.push(link);
3794
+ if (f.daemon.pause?.paused) {
3795
+ out.push({ id: "paused", why: pauseText(f.daemon.pause, f.now), fixes: [{ what: "\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0440\u043E\u0439 \u0432 \u0440\u0430\u0431\u043E\u0442\u0443", command: "swarm resume" }] });
3796
+ }
3797
+ const up = staleMs(f.daemon.startedAt, f.now);
3798
+ const heard = staleMs(f.park.lastHeardAt, f.now);
3799
+ const neverHeard = (f.link.kind === "ok" || f.link.kind === "starting") && f.park.lastHeardAt == null && up != null && up > PARK_STALE_MS;
3800
+ if (!link && (neverHeard || f.park.lastHeardAt && heard != null && heard > PARK_STALE_MS)) {
3801
+ out.push({
3802
+ id: "park-stale",
3803
+ why: neverHeard ? `\u0434\u0435\u043C\u043E\u043D \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 ${uptimeText(f.daemon.startedAt, f.now)}, \u0430 \u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043B\u044B\u0448\u0430\u043B \u044D\u0442\u0443 \u043C\u0430\u0448\u0438\u043D\u0443 \u041D\u0418 \u0420\u0410\u0417\u0423 \u2014 \u043F\u0443\u043B\u044C\u0441 \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u0435\u0442, \u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u0435\u0439 \u043D\u0435 \u0434\u0430\u044E\u0442` : `\u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432, \u0430 \u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043B\u044B\u0448\u0430\u043B \u044D\u0442\u0443 \u043C\u0430\u0448\u0438\u043D\u0443 ${agoText(f.park.lastHeardAt, f.now)} \u2014 \u043F\u0443\u043B\u044C\u0441 \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u0435\u0442, \u0438 \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D\u0430 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043C\u043E\u043B\u0447\u0430\u0449\u0435\u0439`,
3804
+ fixes: [
3805
+ { what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u043D\u0430 \u0447\u0451\u043C \u0441\u043F\u043E\u0442\u044B\u043A\u0430\u0435\u0442\u0441\u044F \u043F\u0443\u043B\u044C\u0441", command: "swarm logs | tail -50" },
3806
+ { what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430, \u0435\u0441\u043B\u0438 \u0432 \u043B\u043E\u0433\u0435 \u043F\u0443\u0441\u0442\u043E", command: "swarm stop && swarm start" }
3807
+ ]
3808
+ });
3809
+ }
3810
+ out.push(...blockedTroubles(f));
3811
+ const idle = idleTrouble(f);
3812
+ if (idle) out.push(idle);
3813
+ else if (waitingForDrone(f.work) > 0 && f.work.busy === 0 && !link && out.length === 0) {
3814
+ out.push({
3815
+ id: "idle-unknown",
3816
+ why: `\u0436\u0434\u0443\u0442 \u0434\u0440\u043E\u043D\u0430 ${waitingForDrone(f.work)} \u0437\u0430\u0434\u0430\u0447(\u0438) (\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0432\u0441\u0435\u0433\u043E ${f.work.queued}), \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043D\u044F\u0442\u043E 0, \u0430 \u0432\u0438\u0434\u0438\u043C\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u044B \u043D\u0435\u0442 \u2014 \u043E\u0442\u0441\u044E\u0434\u0430 \u0435\u0451 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u0442\u044C. \u0427\u0430\u0441\u0442\u044C \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u043E\u0433\u043B\u0430 \u0431\u044B\u0442\u044C \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u0430 \u0437\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u043E\u0439`,
3817
+ fixes: [
3818
+ { what: "\u043E\u0442\u043A\u0430\u0437 \u0441\u043F\u0430\u0432\u043D\u0430 \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u0441\u0430\u043C \u0434\u0435\u043C\u043E\u043D \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 \u0435\u0433\u043E \u043B\u043E\u0433", command: "swarm logs | tail -50" },
3819
+ { what: "\u0447\u044C\u044F \u044D\u0442\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u044C: \u043A\u0430\u043A\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B \u0432 \u0440\u043E\u0435 \u0438 \u043A\u0442\u043E \u043D\u0430 \u0441\u0432\u044F\u0437\u0438", command: "swarm hosts" },
3820
+ { what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B (\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C, gh, \u043A\u043B\u043E\u043D\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432)", command: "swarm doctor" }
3821
+ ]
3822
+ });
3823
+ }
3824
+ return out;
3825
+ }
3826
+ function verdictLine(f, list) {
3827
+ if (list.length === 0) {
3828
+ const busy = f.work.busy;
3829
+ const other = Object.entries(f.work.other).filter(([, n]) => n > 0);
3830
+ const otherTail = other.map(([st, n]) => `${st} ${n}`).join(", ");
3831
+ const tail = busy > 0 ? `${busy} \u0434\u0440\u043E\u043D(\u043E\u0432) \u0432 \u0440\u0430\u0431\u043E\u0442\u0435` : f.work.queued > 0 ? `\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F (${f.work.queued} \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438)` : otherTail ? `\u0436\u0438\u0432\u044B\u0445 \u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0435\u0442, \u043D\u043E \u0440\u043E\u0439 \u0437\u0430\u043D\u044F\u0442: ${otherTail}` : "\u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0435\u0442, \u0436\u0434\u0451\u0442 \u0437\u0430\u0434\u0430\u0447";
3832
+ return `\u2713 \u0420\u041E\u0419 \u0420\u0410\u0411\u041E\u0422\u0410\u0415\u0422 \u2014 ${tail}`;
3833
+ }
3834
+ return `\u2715 \u0420\u041E\u0419 \u041D\u0415 \u0420\u0410\u0411\u041E\u0422\u0410\u0415\u0422, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E ${list[0].why}`;
3835
+ }
3836
+ function detailLines(f) {
3837
+ const lines = [];
3838
+ const d = f.daemon;
3839
+ if (!d.alive) {
3840
+ lines.push(d.processAlive ? `\u0434\u0435\u043C\u043E\u043D: \u041F\u0420\u041E\u0426\u0415\u0421\u0421 \u0416\u0418\u0412 (\u043F\u0443\u043B\u044C\u0441 \u0441\u0432\u0435\u0436\u0438\u0439), \u043D\u043E \u043C\u043E\u0440\u0434\u0430 127.0.0.1:${d.webPort ?? 7420} \u043C\u043E\u043B\u0447\u0438\u0442` : "\u0434\u0435\u043C\u043E\u043D: \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422");
3841
+ lines.push(` ${d.pause?.paused ? `${pauseText(d.pause, f.now)} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435` : "\u043F\u0430\u0443\u0437\u044B \u043D\u0435\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u0440\u0430\u0431\u043E\u0442\u0443"}`);
3842
+ } else {
3843
+ const up = uptimeText(d.startedAt, f.now);
3844
+ const ver = d.latestVersion == null ? `v${d.version ?? "?"} (\u0441 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u043D\u0435 \u0441\u0432\u0435\u0440\u0438\u043B\u0438 \u2014 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u0434\u043E npm)` : isOutdated(d.version ?? "", d.latestVersion) ? `v${d.version} \u2014 \u041E\u0422\u0421\u0422\u0410\u0401\u0422 \u043E\u0442 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 v${d.latestVersion}; \u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432\u0451\u0442 \u043D\u0430 \u043A\u043E\u0434\u0435 \u0441 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u0441\u0442\u0430\u0440\u0442\u0430, \u0441\u0430\u043C \u043D\u0435 \u043F\u0435\u0440\u0435\u0435\u0434\u0435\u0442` : d.version === d.latestVersion ? `v${d.version} (\u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439)` : `v${d.version} (\u043D\u043E\u0432\u0435\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 v${d.latestVersion})`;
3845
+ lines.push(`\u0434\u0435\u043C\u043E\u043D: \u0436\u0438\u0432${d.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${d.hostName}\xBB` : ""}${up ? `, \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 ${up}` : ""}`);
3846
+ lines.push(` ${ver}${d.backend ? `, \u0431\u044D\u043A\u0435\u043D\u0434 ${d.backend}` : ""}`);
3847
+ if (d.restartWaiting) lines.push(" \u0416\u0414\u0401\u0422 \u0441\u043B\u0438\u0432\u0430 \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (\u0440\u0435\u0441\u0442\u0430\u0440\u0442/\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430) \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442");
3848
+ lines.push(` ${pauseText(d.pause, f.now)}`);
3849
+ }
3850
+ const buf = f.link.pending == null ? "" : f.link.pending > 0 ? `; \u0432 \u0431\u0443\u0444\u0435\u0440\u0435 ${f.link.pending} \u043E\u043F\u0435\u0440\u0430\u0446(\u0438\u0439) \u041D\u0415 \u0414\u041E\u0415\u0425\u0410\u041B\u041E \u0434\u043E \u043F\u0440\u043E\u0434\u0430` : "; \u0431\u0443\u0444\u0435\u0440 \u043F\u0443\u0441\u0442 \u2014 \u0432\u0441\u0451 \u0434\u043E\u0435\u0445\u0430\u043B\u043E";
3851
+ lines.push(`\u0441\u0432\u044F\u0437\u044C: ${f.link.kind === "no-prod" ? f.link.detail || LINK_TEXT["no-prod"] : `${LINK_TEXT[f.link.kind]}${f.link.detail ? ` (${f.link.detail})` : ""}`}${buf}`);
3852
+ const ok2 = agoText(f.link.lastOkAt, f.now);
3853
+ if (ok2) lines.push(` \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0443\u0434\u0430\u0447\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442 \u043F\u0440\u043E\u0434\u0430: ${ok2}`);
3854
+ const heard = agoText(f.park.lastHeardAt, f.now);
3855
+ const parkLine = heard ? `\u043F\u0440\u043E\u0434 \u0441\u043B\u044B\u0448\u0430\u043B \u0435\u0451 ${heard}` : !f.daemon.alive ? "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u043F\u0443\u043B\u044C\u0441 \u0448\u043B\u0451\u0442 \u0434\u0435\u043C\u043E\u043D, \u0430 \u0435\u0433\u043E \u043D\u0435\u0442" : f.link.kind === "no-prod" || f.link.kind === "unmeasured" ? f.link.kind === "no-prod" ? "\u043F\u0430\u0440\u043A\u0430 \u043C\u0430\u0448\u0438\u043D \u0432 sqlite-\u0440\u0435\u0436\u0438\u043C\u0435 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u044D\u0442\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0438 \u043F\u0443\u043B\u044C\u0441 \u043D\u0430\u0440\u0443\u0436\u0443 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442" : "\u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043B\u044B\u0448\u0430\u043B \u0435\u0451 \u041D\u0418 \u0420\u0410\u0417\u0423 \u2014 \u0441 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u044B \u0435\u0449\u0451 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0443\u043B\u044C\u0441\u0430";
3856
+ lines.push(`\u043C\u0430\u0448\u0438\u043D\u0430: ${parkLine}`);
3857
+ const w = f.work;
3858
+ const queueTail = w.queuedReady != null && w.queuedReady !== w.queued ? ` (\u0436\u0434\u0443\u0442 \u0434\u0440\u043E\u043D\u0430 ${w.queuedReady}, \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u2014 \u0431\u043B\u043E\u043A\u0435\u0440\u043E\u0432)` : "";
3859
+ lines.push(
3860
+ `\u0440\u0430\u0431\u043E\u0442\u0430: \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043D\u044F\u0442\u043E ${w.busy}${w.maxDrones != null ? ` \u0438\u0437 ${w.maxDrones}` : ""}, \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 ${w.queued}${queueTail}, \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 ${w.questions}`
3861
+ );
3862
+ const otherLine = Object.entries(w.other).filter(([, n]) => n > 0);
3863
+ if (otherLine.length) lines.push(` \u0435\u0449\u0451 \u0432 \u0440\u043E\u0435: ${otherLine.map(([st, n]) => `${st} ${n}`).join(", ")}`);
3864
+ if (w.storm) {
3865
+ lines.push(
3866
+ ` ${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)"}`
3867
+ );
3868
+ }
3869
+ return lines;
3870
+ }
3871
+ function renderStatus(f) {
3872
+ const list = troubles(f);
3873
+ const lines = [verdictLine(f, list), ""];
3874
+ lines.push(...detailLines(f));
3875
+ if (list.length > 1) {
3876
+ lines.push("");
3877
+ lines.push("\u043A\u0440\u043E\u043C\u0435 \u0442\u043E\u0433\u043E:");
3878
+ for (const t of list.slice(1)) lines.push(` \u2022 ${t.why}`);
3879
+ }
3880
+ const fixes = list.flatMap((t) => t.fixes);
3881
+ if (fixes.length) {
3882
+ lines.push("");
3883
+ lines.push("\u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:");
3884
+ const seen = /* @__PURE__ */ new Set();
3885
+ for (const fix of fixes) {
3886
+ if (seen.has(fix.command)) continue;
3887
+ seen.add(fix.command);
3888
+ lines.push(` ${fix.what}`);
3889
+ lines.push(` $ ${fix.command}`);
3890
+ }
3891
+ }
3892
+ return { ok: list.length === 0, lines, exitCode: list.length === 0 ? 0 : 1 };
3893
+ }
3894
+ var PARK_STALE_MS, staleMs, LINK_TEXT;
3895
+ var init_swarmStatus = __esm({
3896
+ "src/swarmStatus.ts"() {
3897
+ "use strict";
3898
+ init_pauseState();
3899
+ init_linkFailure();
3900
+ init_release();
3901
+ init_src();
3902
+ PARK_STALE_MS = HOST_HEARTBEAT_STALE_MS;
3903
+ staleMs = (iso, now) => {
3904
+ if (!iso) return null;
3905
+ const ms = Date.parse(iso);
3906
+ return Number.isNaN(ms) ? null : now - ms;
3907
+ };
3908
+ LINK_TEXT = {
3909
+ ok: "\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0442\u043E\u043A\u0435\u043D \u043F\u0440\u0438\u043D\u044F\u0442",
3910
+ "no-token": "\u0432\u0445\u043E\u0434 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D (\u0442\u043E\u043A\u0435\u043D\u0430 \u0440\u043E\u044F \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442)",
3911
+ revoked: "\u0442\u043E\u043A\u0435\u043D \u041E\u0422\u0412\u0415\u0420\u0413\u041D\u0423\u0422 (401 invalid_token)",
3912
+ network: "\u0434\u043E \u043F\u0440\u043E\u0434\u0430 \u043D\u0435 \u0434\u043E\u0441\u0442\u0443\u0447\u0430\u0442\u044C\u0441\u044F",
3913
+ server: "\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
3914
+ down: "\u0441\u0432\u044F\u0437\u0438 \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B",
3915
+ starting: "\u0441\u0432\u044F\u0437\u044C \u0435\u0449\u0451 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C \u2014 \u0434\u0435\u043C\u043E\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F",
3916
+ "no-prod": "\u043F\u0440\u043E\u0434\u0430 \u0443 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 (sqlite-\u0440\u0435\u0436\u0438\u043C)",
3917
+ unmeasured: "\u0421\u041A\u0410\u0417\u0410\u0422\u042C \u041D\u0415\u0427\u0415\u041C \u2014 \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u044D\u0442\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0438 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442"
3918
+ };
3919
+ }
3920
+ });
3921
+
3922
+ // src/statusCli.ts
3923
+ var statusCli_exports = {};
3924
+ __export(statusCli_exports, {
3925
+ collectStatus: () => collectStatus,
3926
+ outboxPending: () => outboxPending,
3927
+ probeLink: () => probeLink,
3928
+ runStatusCli: () => runStatusCli
3929
+ });
3307
3930
  import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
3931
+ function outboxPending(path) {
3932
+ try {
3933
+ if (!existsSync11(path)) return 0;
3934
+ return readFileSync14(path, "utf8").split("\n").filter((l) => l.trim()).length;
3935
+ } catch {
3936
+ return null;
3937
+ }
3938
+ }
3939
+ async function probeLink() {
3940
+ const base3 = { lastOkAt: null, pending: outboxPending(SWARM_OUTBOX), api: TASKLESS_API };
3941
+ const store = loadAgentToken();
3942
+ if (!store) return { kind: "no-token", detail: "", ...base3 };
3943
+ const probe = await probeAgentToken({ api: store.api ?? TASKLESS_API, token: store.token });
3944
+ if (probe === "ok") return { kind: "ok", detail: "", ...base3, lastOkAt: (/* @__PURE__ */ new Date()).toISOString() };
3945
+ if (probe === "revoked") return { kind: "revoked", detail: "401 invalid_token", ...base3 };
3946
+ return { kind: "network", detail: "\u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (\u0441\u0435\u0442\u044C \u0438\u043B\u0438 \u0441\u0430\u043C \u043F\u0440\u043E\u0434)", ...base3 };
3947
+ }
3948
+ function factsFromDaemon(st, link, now) {
3949
+ const counts = st.counts ?? {};
3950
+ return {
3951
+ daemon: {
3952
+ alive: true,
3953
+ version: st.version ?? null,
3954
+ latestVersion: st.latestVersion ?? null,
3955
+ startedAt: st.startedAt ?? null,
3956
+ hostName: st.hostName ?? null,
3957
+ backend: st.backend ?? null,
3958
+ pause: st.pause ?? (st.paused ? { paused: true, at: "", source: "web" } : null),
3959
+ restartWaiting: st.restart?.phase === "waiting"
3960
+ },
3961
+ link,
3962
+ park: { lastHeardAt: st.link?.heardAt ?? null },
3963
+ work: {
3964
+ busy: st.live?.length ?? 0,
3965
+ queued: counts.queued ?? 0,
3966
+ // Демон старее команды счётчика не отдаёт — тогда null, и меру берём по всей очереди
3967
+ // (см. waitingForDrone): промолчать о вставшем рое хуже, чем сказать лишнее.
3968
+ queuedReady: st.queuedReady ?? null,
3969
+ questions: st.questions ?? 0,
3970
+ // Нетерминальные прогоны, кроме очереди и живых сессий: терминальные знает ядро
3971
+ // (TERMINAL_STATUSES), второго списка тут не заводим.
3972
+ other: Object.fromEntries(
3973
+ Object.entries(counts).filter(([st2, n]) => n > 0 && st2 !== "queued" && st2 !== "running" && !TERMINAL_STATUSES.includes(st2))
3974
+ ),
3975
+ maxDrones: st.maxDrones ?? null,
3976
+ storm: st.storm ?? null
3977
+ },
3978
+ now
3979
+ };
3980
+ }
3981
+ function linkFromDaemon(st) {
3982
+ const api = st.api || TASKLESS_API;
3983
+ if (!st.link) {
3984
+ if (st.authDenied) return { kind: "revoked", detail: st.authDenied, lastOkAt: null, pending: null, api };
3985
+ return {
3986
+ kind: st.backend === "sqlite" ? "no-prod" : "unmeasured",
3987
+ // Только версия: «состояние связи не отдаёт» уже сказано формулировкой состояния, и
3988
+ // дубль в скобках человек перечитывает зря.
3989
+ detail: st.backend === "sqlite" ? "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 sqlite-\u0440\u0435\u0436\u0438\u043C \u2014 \u043F\u0440\u043E\u0434\u0430 \u0443 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435" : `\u0434\u0435\u043C\u043E\u043D v${st.version ?? "?"}`,
3990
+ lastOkAt: null,
3991
+ pending: null,
3992
+ api
3993
+ };
3994
+ }
3995
+ const kind = st.link.kind in KNOWN_LINK_KINDS ? st.link.kind : "unmeasured";
3996
+ return { kind, detail: st.link.detail ?? "", lastOkAt: st.link.lastOkAt ?? null, pending: st.link.pending ?? null, api };
3997
+ }
3998
+ function daemonProcessAlive(now) {
3999
+ const beat = readHeartbeat(HEARTBEAT_PATH);
4000
+ return beat != null && now - beat < HEARTBEAT_FRESH_MS;
4001
+ }
4002
+ async function factsWithoutDaemon(now) {
4003
+ const link = await probeLink();
4004
+ return {
4005
+ daemon: {
4006
+ alive: false,
4007
+ // Ключевое различие (ревью TASWM-325): «морда молчит» — это НЕ «демон не поднят».
4008
+ // Совет `swarm start` на живом процессе даёт второго демона или падение старта.
4009
+ processAlive: daemonProcessAlive(now),
4010
+ webPort: WEB_PORT,
4011
+ version: null,
4012
+ latestVersion: null,
4013
+ startedAt: null,
4014
+ hostName: null,
4015
+ backend: null,
4016
+ pause: loadPauseState(SWARM_HOME),
4017
+ restartWaiting: false
4018
+ },
4019
+ link,
4020
+ // Пульса нет по построению: пульсит демон, а его нет. Не выдаём это за «прод молчит».
4021
+ park: { lastHeardAt: null },
4022
+ work: { busy: 0, queued: 0, queuedReady: null, questions: 0, other: {}, maxDrones: null, storm: null },
4023
+ now
4024
+ };
4025
+ }
4026
+ async function collectStatus(now = Date.now()) {
4027
+ const st = await daemonStatus2().catch(() => null);
4028
+ if (!st) return { facts: await factsWithoutDaemon(now), daemon: null };
4029
+ return { facts: factsFromDaemon(st, linkFromDaemon(st), now), daemon: st };
4030
+ }
4031
+ async function runStatusCli(_argv = []) {
4032
+ const { facts, daemon } = await collectStatus();
4033
+ const report = renderStatus(facts);
4034
+ for (const line of report.lines) console.log(line);
4035
+ const repoLines = repoMapLines(daemon?.repos);
4036
+ if (repoLines.length) {
4037
+ console.log("");
4038
+ for (const line of repoLines) console.log(line);
4039
+ }
4040
+ if (!facts.daemon.alive) console.log(`
4041
+ (\u043C\u043E\u0440\u0434\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 127.0.0.1:${WEB_PORT} \u043C\u043E\u043B\u0447\u0438\u0442 \u2014 \u0432\u0441\u0451 \u0432\u044B\u0448\u0435 \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0441 \u043C\u0430\u0448\u0438\u043D\u044B)`);
4042
+ return report.exitCode;
4043
+ }
4044
+ var KNOWN_LINK_KINDS, HEARTBEAT_FRESH_MS;
4045
+ var init_statusCli = __esm({
4046
+ "src/statusCli.ts"() {
4047
+ "use strict";
4048
+ init_config();
4049
+ init_heartbeat();
4050
+ init_src();
4051
+ init_daemonLink();
4052
+ init_pauseState();
4053
+ init_agentToken();
4054
+ init_repoDiscovery();
4055
+ init_swarmStatus();
4056
+ KNOWN_LINK_KINDS = {
4057
+ ok: true,
4058
+ starting: true,
4059
+ "no-token": true,
4060
+ revoked: true,
4061
+ network: true,
4062
+ server: true,
4063
+ down: true,
4064
+ "no-prod": true,
4065
+ unmeasured: true
4066
+ };
4067
+ HEARTBEAT_FRESH_MS = 6e4;
4068
+ }
4069
+ });
4070
+
4071
+ // src/platform.ts
4072
+ import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
3308
4073
  import { delimiter, join as join19 } from "path";
3309
4074
  function safeReadText(path) {
3310
4075
  try {
3311
- return readFileSync14(path, "utf8");
4076
+ return readFileSync15(path, "utf8");
3312
4077
  } catch {
3313
4078
  return null;
3314
4079
  }
3315
4080
  }
3316
4081
  function resolveCommandPath(name, env = process.env, platform = process.platform) {
3317
- if (name.includes("/") || name.includes("\\")) return existsSync11(name) ? name : null;
4082
+ if (name.includes("/") || name.includes("\\")) return existsSync12(name) ? name : null;
3318
4083
  const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
3319
4084
  const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
3320
4085
  for (const dir of dirs) {
3321
4086
  for (const ext of exts) {
3322
4087
  const candidate = join19(dir, name + ext);
3323
- if (existsSync11(candidate)) return candidate;
4088
+ if (existsSync12(candidate)) return candidate;
3324
4089
  }
3325
4090
  }
3326
4091
  return null;
@@ -3423,12 +4188,12 @@ var init_platform = __esm({
3423
4188
  });
3424
4189
 
3425
4190
  // src/envGate.ts
3426
- import { existsSync as existsSync12 } from "fs";
4191
+ import { existsSync as existsSync13 } from "fs";
3427
4192
  import { join as join20 } from "path";
3428
4193
  import { createConnection } from "net";
3429
4194
  import { execa as execa3 } from "execa";
3430
4195
  function detectRequirements(repo, io = {}) {
3431
- const exists = io.exists ?? existsSync12;
4196
+ const exists = io.exists ?? existsSync13;
3432
4197
  const marker = COMPOSE_MARKERS.find((f) => exists(join20(repo, f)));
3433
4198
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
3434
4199
  }
@@ -3460,42 +4225,6 @@ var init_envGate = __esm({
3460
4225
  }
3461
4226
  });
3462
4227
 
3463
- // src/release.ts
3464
- async function fetchLatestVersion(opts = {}) {
3465
- const fetchFn = opts.fetchFn ?? fetch;
3466
- const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
3467
- const pkg = opts.pkg ?? SWARM_PACKAGE;
3468
- const ctrl = new AbortController();
3469
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
3470
- try {
3471
- const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
3472
- signal: ctrl.signal,
3473
- headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
3474
- });
3475
- if (!res.ok) return null;
3476
- const body = await res.json();
3477
- return typeof body.version === "string" && body.version ? body.version : null;
3478
- } catch {
3479
- return null;
3480
- } finally {
3481
- clearTimeout(timer);
3482
- }
3483
- }
3484
- function isOutdated(current, latest) {
3485
- if (!latest || !current) return false;
3486
- return compareVersionDirs(latest, current) > 0;
3487
- }
3488
- var SWARM_PACKAGE, LATEST_TIMEOUT_MS;
3489
- var init_release = __esm({
3490
- "src/release.ts"() {
3491
- "use strict";
3492
- init_src();
3493
- init_claudeBin();
3494
- SWARM_PACKAGE = SWARM_PACKAGE_NAME;
3495
- LATEST_TIMEOUT_MS = 4e3;
3496
- }
3497
- });
3498
-
3499
4228
  // src/doctor.ts
3500
4229
  import { isAbsolute as isAbsolute4, join as join21 } from "path";
3501
4230
  function ok(id, title, detail) {
@@ -3878,12 +4607,12 @@ var init_doctor = __esm({
3878
4607
  });
3879
4608
 
3880
4609
  // src/repoMemory.ts
3881
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
4610
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
3882
4611
  import { join as join22 } from "path";
3883
4612
  function loadRememberedRepos(home) {
3884
4613
  let raw;
3885
4614
  try {
3886
- raw = readFileSync15(join22(home, FILE), "utf8");
4615
+ raw = readFileSync16(join22(home, FILE), "utf8");
3887
4616
  } catch {
3888
4617
  return [];
3889
4618
  }
@@ -3918,7 +4647,7 @@ __export(installCli_exports, {
3918
4647
  runInstallCli: () => runInstallCli,
3919
4648
  shortenError: () => shortenError
3920
4649
  });
3921
- import { existsSync as existsSync13 } from "fs";
4650
+ import { existsSync as existsSync14 } from "fs";
3922
4651
  function classifyChromiumError(message) {
3923
4652
  if (/playwright-core не установлен|Cannot find (module|package)/i.test(message)) return "no-module";
3924
4653
  if (/error while loading shared libraries|cannot open shared object file|missing dependencies|\blib[\w.+-]*\.so[.\d]*\b/i.test(message)) return "no-libs";
@@ -3973,7 +4702,7 @@ function defaultDoctorIO(packageVersion) {
3973
4702
  packageVersion,
3974
4703
  entry,
3975
4704
  hasCommand: (name) => hasCommandInPath(name),
3976
- exists: existsSync13,
4705
+ exists: existsSync14,
3977
4706
  run: (command) => runQuiet(command),
3978
4707
  latestVersion: () => latestMemo ??= fetchLatestVersion(),
3979
4708
  probeChromium: () => probeChromium(),
@@ -4183,7 +4912,7 @@ var init_updateCli = __esm({
4183
4912
  // src/cli.ts
4184
4913
  init_cliIo();
4185
4914
  import { basename as basename4, join as join23 } from "path";
4186
- import { existsSync as existsSync14, readFileSync as readFileSync16, readdirSync as readdirSync9, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4, rmSync as rmSync2 } from "fs";
4915
+ import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4, rmSync as rmSync2 } from "fs";
4187
4916
  import { setTimeout as sleep2 } from "timers/promises";
4188
4917
 
4189
4918
  // src/swarmClient.ts
@@ -4524,7 +5253,16 @@ var SwarmClient = class _SwarmClient {
4524
5253
  static REJECT_LIMIT = 5;
4525
5254
  static BACKOFF_BASE = 500;
4526
5255
  static BACKOFF_MAX = 3e4;
5256
+ // TASWM-325: момент УДАЧНОГО пульса — «когда прод последний раз слышал эту машину»
5257
+ // (прод пишет SwarmHost.lastSeenAt ровно на swarm_heartbeat). Единственное, что про
5258
+ // связь знает КЛИЕНТ: жива ли она вообще — знает транспорт (mcpResilient.link), а
5259
+ // отвергнут ли токен — authState. Здесь их не угадываем: первая редакция считала
5260
+ // успехом любой возврат call и на боевом каллере (который не бросает, а возвращает
5261
+ // isError) всегда показывала «связь есть».
5262
+ heartbeatOkAt = null;
5263
+ now;
4527
5264
  constructor(opts) {
5265
+ this.now = opts.now ?? Date.now;
4528
5266
  this.call = opts.call;
4529
5267
  this.defaultRepo = normalizeRepo(opts.defaultRepo ?? "");
4530
5268
  this.autoFlush = opts.autoFlush ?? true;
@@ -4667,6 +5405,7 @@ var SwarmClient = class _SwarmClient {
4667
5405
  if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
4668
5406
  if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
4669
5407
  if ("review_verdict" in patch) out.reviewVerdict = patch.review_verdict ?? null;
5408
+ if ("verify_verdict" in patch) out.verifyVerdict = patch.verify_verdict ?? null;
4670
5409
  if ("host_id" in patch && patch.host_id) {
4671
5410
  out.hostId = patch.host_id;
4672
5411
  out.hostPinned = patch.host_pinned === true;
@@ -4677,6 +5416,7 @@ var SwarmClient = class _SwarmClient {
4677
5416
  this.setRunRefField(out, "resolvesRunId", patch, "resolves_run_id");
4678
5417
  this.setRunRefField(out, "supersededById", patch, "superseded_by");
4679
5418
  this.setRunRefField(out, "reviewsRunId", patch, "reviews_run_id");
5419
+ this.setRunRefField(out, "verifiesRunId", patch, "verifies_run_id");
4680
5420
  return out;
4681
5421
  }
4682
5422
  // Положить self-ref прогона в прод-патч: локальный number → отложенная ссылка
@@ -4739,6 +5479,9 @@ var SwarmClient = class _SwarmClient {
4739
5479
  resolves_run_id: input.resolves_run_id ?? null,
4740
5480
  reviews_run_id: input.reviews_run_id ?? null,
4741
5481
  review_verdict: null,
5482
+ verifies_run_id: input.verifies_run_id ?? null,
5483
+ // прогон-верификатор (TASWM-344)
5484
+ verify_verdict: null,
4742
5485
  review_phase: null,
4743
5486
  // вычисляемая фаза ревью (TASWM-230) — демон посчитает на review-тике
4744
5487
  review_cycle: input.review_cycle ?? 0,
@@ -4790,6 +5533,8 @@ var SwarmClient = class _SwarmClient {
4790
5533
  // резолвер конфликта → связь на review-прогон
4791
5534
  reviews_run_id: d.reviews_run_id,
4792
5535
  // ревьюер (авто-ревью, TASWM-148) → связь на review-прогон
5536
+ verifies_run_id: d.verifies_run_id,
5537
+ // верификатор (TASWM-344) → связь на проверяемый прогон
4793
5538
  host_id: d.host_id,
4794
5539
  // машина прогона (TASWM-281) — своя с рождения либо заданная --host
4795
5540
  host_pinned: d.host_pinned,
@@ -4797,7 +5542,7 @@ var SwarmClient = class _SwarmClient {
4797
5542
  base_branch: d.base_branch
4798
5543
  // базовая ветка PR (TASWM-285), если задана на спавне
4799
5544
  }));
4800
- const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
5545
+ const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
4801
5546
  this.addEvent(id, "spawned", spawnNote);
4802
5547
  return this.mat(d);
4803
5548
  }
@@ -4901,6 +5646,24 @@ var SwarmClient = class _SwarmClient {
4901
5646
  activeReviewersFor(reviewRunId) {
4902
5647
  return [...this.runs.values()].filter((r) => r.reviews_run_id === reviewRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
4903
5648
  }
5649
+ // Верификация (TASWM-344) — зеркало трёх счётчиков ревью выше по своей связи
5650
+ // verifies_run_id; правила «кто не в счёт» те же (см. db.ts:verifierCountFor).
5651
+ verifierCountFor(verifyRunId) {
5652
+ const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
5653
+ const dead = /* @__PURE__ */ new Set(["failed", "stalled", "stopped", "superseded"]);
5654
+ return [...this.runs.values()].filter(
5655
+ (r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && !dead.has(r.status)
5656
+ ).length;
5657
+ }
5658
+ verifierAttemptCountFor(verifyRunId) {
5659
+ const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
5660
+ return [...this.runs.values()].filter(
5661
+ (r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && r.status !== "superseded"
5662
+ ).length;
5663
+ }
5664
+ activeVerifiersFor(verifyRunId) {
5665
+ return [...this.runs.values()].filter((r) => r.verifies_run_id === verifyRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
5666
+ }
4904
5667
  activeRunForSlug(slug) {
4905
5668
  const cands = [...this.runs.values()].filter((r) => r.task_slug === slug && !SLUG_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
4906
5669
  return cands.length ? this.mat(cands[0]) : null;
@@ -4936,7 +5699,9 @@ var SwarmClient = class _SwarmClient {
4936
5699
  if (!depends_on || depends_on.length === 0) return [];
4937
5700
  const doneInMirror = /* @__PURE__ */ new Set();
4938
5701
  for (const r of this.runs.values()) {
4939
- if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null) doneInMirror.add(r.task_slug);
5702
+ if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null) {
5703
+ doneInMirror.add(r.task_slug);
5704
+ }
4940
5705
  }
4941
5706
  return depends_on.map((slug) => {
4942
5707
  if (doneInMirror.has(slug)) {
@@ -4982,7 +5747,7 @@ var SwarmClient = class _SwarmClient {
4982
5747
  // Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
4983
5748
  // не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
4984
5749
  // с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
4985
- service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
5750
+ service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId) || Boolean(r.verifiesRunId),
4986
5751
  // В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
4987
5752
  // запасной вариант ради фикстур и старых ответов.
4988
5753
  name: r.droneName ?? r.name ?? null,
@@ -5198,7 +5963,7 @@ var SwarmClient = class _SwarmClient {
5198
5963
  return this.eventsLog.filter((e) => {
5199
5964
  if (e.kind !== "failed" || e.run_id == null || e.at < cutoff) return false;
5200
5965
  const r = this.runs.get(e.run_id);
5201
- return !!r && r.resolves_run_id == null && r.reviews_run_id == null;
5966
+ return !!r && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null;
5202
5967
  }).length;
5203
5968
  }
5204
5969
  recordStormProbe(probe, ext, reason, resetHint) {
@@ -5323,7 +6088,7 @@ var SwarmClient = class _SwarmClient {
5323
6088
  const workerWt = droneWorktreePath(opts.worktreesDir, d.repo || this.defaultRepo, d.task_slug);
5324
6089
  let matchWt = null;
5325
6090
  let matchPid = null;
5326
- if (d.resolves_run_id == null && d.reviews_run_id == null) {
6091
+ if (d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null) {
5327
6092
  const p = pidByWorktree.get(workerWt);
5328
6093
  if (p != null) {
5329
6094
  matchWt = workerWt;
@@ -5339,9 +6104,10 @@ var SwarmClient = class _SwarmClient {
5339
6104
  }
5340
6105
  }
5341
6106
  } else {
5342
- const reviewed = this.runs.get(d.reviews_run_id);
6107
+ const checkedId = d.reviews_run_id ?? d.verifies_run_id;
6108
+ const reviewed = this.runs.get(checkedId);
5343
6109
  const reviewedWt = reviewed ? droneWorktreePath(opts.worktreesDir, reviewed.repo || this.defaultRepo, reviewed.task_slug) : workerWt;
5344
- const prefix = `${reviewedWt}-review-`;
6110
+ const prefix = d.reviews_run_id != null ? `${reviewedWt}-review-` : `${reviewedWt}-verify-`;
5345
6111
  for (const [wt, p] of pidByWorktree) {
5346
6112
  if (wt.startsWith(prefix)) {
5347
6113
  matchWt = wt;
@@ -5354,7 +6120,7 @@ var SwarmClient = class _SwarmClient {
5354
6120
  this.runtime.set(d.id, { pid: matchPid, worktree_path: matchWt });
5355
6121
  alive++;
5356
6122
  } else {
5357
- const wt = d.resolves_run_id == null && d.reviews_run_id == null && wtExists(workerWt) ? workerWt : null;
6123
+ const wt = d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null && wtExists(workerWt) ? workerWt : null;
5358
6124
  this.runtime.set(d.id, { pid: null, worktree_path: wt });
5359
6125
  dead++;
5360
6126
  }
@@ -5375,6 +6141,8 @@ var SwarmClient = class _SwarmClient {
5375
6141
  if (sup) local.superseded_by = this.localIdForProdId(sup) ?? local.superseded_by;
5376
6142
  const rev = pr.reviewsRunId ?? null;
5377
6143
  if (rev) local.reviews_run_id = this.localIdForProdId(rev) ?? local.reviews_run_id;
6144
+ const ver = pr.verifiesRunId ?? null;
6145
+ if (ver) local.verifies_run_id = this.localIdForProdId(ver) ?? local.verifies_run_id;
5378
6146
  }
5379
6147
  /**
5380
6148
  * Авто-интейк (TASWM-100): взять следующую claimable-задачу владельца токена через
@@ -5871,9 +6639,16 @@ var SwarmClient = class _SwarmClient {
5871
6639
  * простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
5872
6640
  * не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
5873
6641
  *
6642
+ * TASWM-333: тем же пульсом уходит ШТОРМ этой машины (`storm` — штормим ли сейчас, и с
6643
+ * какой формулировкой мы в него вошли). Состояние живёт в наших событиях и переживает
6644
+ * рестарт, а в проде его не было вовсе: рой стоял, а парк машин показывал живую свободную
6645
+ * машину — 10.08 это стоило десяти часов простоя, замеченных человеком вручную. Шлём
6646
+ * ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
6647
+ * означало бы «прод помнит вчерашний шторм», когда рой давно работает.
6648
+ *
5874
6649
  * Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
5875
6650
  */
5876
- async heartbeat(daemonVersion, pause, capacity, resources) {
6651
+ async heartbeat(daemonVersion, pause, capacity, resources, storm) {
5877
6652
  try {
5878
6653
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
5879
6654
  if (daemonVersion) args.daemonVersion = daemonVersion;
@@ -5883,12 +6658,17 @@ var SwarmClient = class _SwarmClient {
5883
6658
  args.paused = !!pause?.paused;
5884
6659
  if (pause?.reason) args.pauseReason = pause.reason;
5885
6660
  }
6661
+ if (storm !== void 0 && storm !== null) {
6662
+ args.storm = storm.active;
6663
+ if (storm.reason) args.stormReason = storm.reason;
6664
+ }
5886
6665
  if (this.host) {
5887
6666
  args.hostId = this.host.hostId;
5888
6667
  args.hostName = this.host.hostName;
5889
6668
  }
5890
6669
  const res = await this.call("swarm_heartbeat", args);
5891
6670
  if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
6671
+ this.heartbeatOkAt = this.now();
5892
6672
  const data = res?.structuredContent ?? {};
5893
6673
  return {
5894
6674
  commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
@@ -6100,6 +6880,9 @@ var SwarmClient = class _SwarmClient {
6100
6880
  resolves_run_id: null,
6101
6881
  reviews_run_id: null,
6102
6882
  review_verdict: null,
6883
+ verifies_run_id: null,
6884
+ // прогон-верификатор (TASWM-344) — прод-поле, заполняется ниже
6885
+ verify_verdict: null,
6103
6886
  review_phase: null,
6104
6887
  // вычисляемая фаза ревью (TASWM-230) — прод хранит и раздаёт
6105
6888
  // Локальная бухгалтерия (в прод-схему не уходит) — на гидрации из прода дефолт 0;
@@ -6143,6 +6926,9 @@ var SwarmClient = class _SwarmClient {
6143
6926
  const revProd = pr.reviewsRunId ?? null;
6144
6927
  if (revProd) local.reviews_run_id = this.localIdForProdId(revProd) ?? local.reviews_run_id;
6145
6928
  local.review_verdict = pr.reviewVerdict ?? local.review_verdict;
6929
+ const verProd = pr.verifiesRunId ?? null;
6930
+ if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
6931
+ local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
6146
6932
  local.review_phase = pr.reviewPhase ?? local.review_phase;
6147
6933
  local.repo = this.resolveRepo(pr.repo ?? null);
6148
6934
  local.host_id = pr.hostId ?? null;
@@ -6431,6 +7217,18 @@ var SwarmClient = class _SwarmClient {
6431
7217
  pendingCount() {
6432
7218
  return this.pending.length;
6433
7219
  }
7220
+ /**
7221
+ * Когда прод в последний раз ПРИНЯЛ пульс этой машины (ISO) — вход `swarm status`.
7222
+ * null — ни разу за жизнь процесса.
7223
+ *
7224
+ * Почему именно пульс, а не «последний удачный вызов»: в парке машин lastSeenAt прод
7225
+ * пишет ровно на swarm_heartbeat. Спрашивать за тем же самым swarm_list_hosts значило бы
7226
+ * завести второй источник правды и платить прод-вызовом в команде, которую зовут именно
7227
+ * тогда, когда прод не отвечает.
7228
+ */
7229
+ lastHeardAt() {
7230
+ return this.heartbeatOkAt == null ? null : new Date(this.heartbeatOkAt).toISOString();
7231
+ }
6434
7232
  close() {
6435
7233
  if (this.flushTimer) {
6436
7234
  clearTimeout(this.flushTimer);
@@ -6878,22 +7676,21 @@ async function cleanupWorktreeProcesses(worktree, opts = {}) {
6878
7676
 
6879
7677
  // src/cli.ts
6880
7678
  init_repoRegistry();
6881
- init_repoDiscovery();
6882
7679
 
6883
7680
  // src/hostIdentity.ts
6884
7681
  import { hostname } from "os";
6885
- import { existsSync as existsSync6, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
6886
- import { join as join8 } from "path";
7682
+ import { existsSync as existsSync6, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
7683
+ import { join as join6 } from "path";
6887
7684
  import { randomUUID as randomUUID2 } from "crypto";
6888
7685
  function hostIdentityPath(home) {
6889
- return join8(home, "host.json");
7686
+ return join6(home, "host.json");
6890
7687
  }
6891
7688
  function loadHostIdentity(deps) {
6892
7689
  const exists = deps.exists ?? existsSync6;
6893
- const readFile2 = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
7690
+ const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
6894
7691
  const writeFile3 = deps.writeFile ?? ((p, data) => {
6895
- writeFileSync5(`${p}.tmp`, data);
6896
- renameSync4(`${p}.tmp`, p);
7692
+ writeFileSync4(`${p}.tmp`, data);
7693
+ renameSync3(`${p}.tmp`, p);
6897
7694
  });
6898
7695
  const host = deps.hostname ?? hostname;
6899
7696
  const newId = deps.newId ?? randomUUID2;
@@ -7007,8 +7804,8 @@ import { basename as basename2 } from "path";
7007
7804
  import { serve as serve2 } from "@hono/node-server";
7008
7805
 
7009
7806
  // src/diff.ts
7010
- import { statSync as statSync6 } from "fs";
7011
- import { join as join9 } from "path";
7807
+ import { statSync as statSync5 } from "fs";
7808
+ import { join as join7 } from "path";
7012
7809
  import { $ as $3 } from "execa";
7013
7810
  var MAX_FILES = 200;
7014
7811
  var MAX_LINES_PER_FILE = 1500;
@@ -7151,7 +7948,7 @@ async function resolveBase(wt, baseBranch) {
7151
7948
  }
7152
7949
  function mtime(path) {
7153
7950
  try {
7154
- return statSync6(path).mtimeMs;
7951
+ return statSync5(path).mtimeMs;
7155
7952
  } catch {
7156
7953
  return 0;
7157
7954
  }
@@ -7176,7 +7973,7 @@ async function collectRunDiff(worktree, opts = {}) {
7176
7973
  files.push(pf);
7177
7974
  }
7178
7975
  }
7179
- files.sort((a, b) => mtime(join9(worktree, b.path)) - mtime(join9(worktree, a.path)));
7976
+ files.sort((a, b) => mtime(join7(worktree, b.path)) - mtime(join7(worktree, a.path)));
7180
7977
  const truncatedFiles = files.length > MAX_FILES;
7181
7978
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
7182
7979
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -7271,10 +8068,23 @@ function makeMergeGateResolver(deps) {
7271
8068
  const run = findRunByPr(deps.listRuns(), ref);
7272
8069
  if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
7273
8070
  const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
7274
- const decision = computeMergeGate({ hasRun: true, autoReview, reviewVerdict: run.review_verdict });
8071
+ const decision = computeMergeGate({
8072
+ hasRun: true,
8073
+ autoReview,
8074
+ reviewVerdict: run.review_verdict,
8075
+ autoVerify: deps.autoVerify === true,
8076
+ verifyVerdict: run.verify_verdict ?? null
8077
+ });
7275
8078
  return {
7276
8079
  ...decision,
7277
- run: { id: run.id, task_slug: run.task_slug, name: run.name, pr_url: run.pr_url, review_verdict: run.review_verdict }
8080
+ run: {
8081
+ id: run.id,
8082
+ task_slug: run.task_slug,
8083
+ name: run.name,
8084
+ pr_url: run.pr_url,
8085
+ review_verdict: run.review_verdict,
8086
+ verify_verdict: run.verify_verdict ?? null
8087
+ }
7278
8088
  };
7279
8089
  };
7280
8090
  }
@@ -7282,25 +8092,55 @@ function makeMergeGateResolver(deps) {
7282
8092
  // src/web.ts
7283
8093
  init_droneChannel();
7284
8094
 
8095
+ // src/authState.ts
8096
+ init_agentToken();
8097
+ var SHOUT_EVERY_MS = 10 * 6e4;
8098
+ var denial = null;
8099
+ function noteTokenRejected(nowMs = Date.now()) {
8100
+ if (!denial) {
8101
+ denial = { since: nowMs, lastShoutAt: nowMs, count: 1 };
8102
+ return revokedTokenComplaint();
8103
+ }
8104
+ denial.count += 1;
8105
+ if (nowMs - denial.lastShoutAt < SHOUT_EVERY_MS) return null;
8106
+ denial.lastShoutAt = nowMs;
8107
+ return `${revokedTokenComplaint()}
8108
+ ${deniedSinceLine(nowMs)}
8109
+ `;
8110
+ }
8111
+ function noteTokenAccepted() {
8112
+ denial = null;
8113
+ }
8114
+ function authStateLine(nowMs = Date.now()) {
8115
+ if (!denial) return null;
8116
+ return `\u0414\u041E\u0421\u0422\u0423\u041F \u041A TASKLESS \u041E\u0422\u041E\u0417\u0412\u0410\u041D \u2014 ${deniedSinceLine(nowMs)}. \u041F\u043E\u0447\u0438\u043D\u0438 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: swarm login`;
8117
+ }
8118
+ function deniedSinceLine(nowMs) {
8119
+ const d = denial;
8120
+ if (!d) return "";
8121
+ const min = Math.max(1, Math.round((nowMs - d.since) / 6e4));
8122
+ return `\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0442\u043E\u043A\u0435\u043D ${min} \u043C\u0438\u043D, \u043E\u0442\u043A\u0430\u0437\u043E\u0432 ${d.count}`;
8123
+ }
8124
+
7285
8125
  // src/oauth.ts
7286
8126
  init_config();
7287
8127
  import { createHash, randomBytes as randomBytes2 } from "crypto";
7288
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
7289
- import { join as join10 } from "path";
8128
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
8129
+ import { join as join9 } from "path";
7290
8130
  var REGISTER = "/api/oauth/register";
7291
8131
  var AUTHORIZE = "/api/oauth/authorize";
7292
8132
  var TOKEN = "/api/oauth/token";
7293
8133
  var SCOPE = "openid profile email taskless:full";
7294
- var STORE_PATH = join10(SWARM_HOME, "oauth.json");
7295
- var CLIENT_PATH = join10(SWARM_HOME, "oauth-client.json");
8134
+ var STORE_PATH2 = join9(SWARM_HOME, "oauth.json");
8135
+ var CLIENT_PATH = join9(SWARM_HOME, "oauth-client.json");
7296
8136
  function makePkce() {
7297
8137
  const verifier = randomBytes2(48).toString("base64url");
7298
8138
  const challenge = createHash("sha256").update(verifier).digest("base64url");
7299
8139
  return { verifier, challenge };
7300
8140
  }
7301
- var base = (api) => api.replace(/\/$/, "");
8141
+ var base2 = (api) => api.replace(/\/$/, "");
7302
8142
  async function registerClient(args, fetchFn = fetch) {
7303
- const res = await fetchFn(`${base(args.api)}${REGISTER}`, {
8143
+ const res = await fetchFn(`${base2(args.api)}${REGISTER}`, {
7304
8144
  method: "POST",
7305
8145
  headers: { "Content-Type": "application/json" },
7306
8146
  body: JSON.stringify({
@@ -7315,7 +8155,7 @@ async function registerClient(args, fetchFn = fetch) {
7315
8155
  return j.client_id;
7316
8156
  }
7317
8157
  function authorizeUrl(args) {
7318
- const u = new URL(`${base(args.api)}${AUTHORIZE}`);
8158
+ const u = new URL(`${base2(args.api)}${AUTHORIZE}`);
7319
8159
  u.searchParams.set("response_type", "code");
7320
8160
  u.searchParams.set("client_id", args.clientId);
7321
8161
  u.searchParams.set("redirect_uri", args.redirectUri);
@@ -7326,7 +8166,7 @@ function authorizeUrl(args) {
7326
8166
  return u.toString();
7327
8167
  }
7328
8168
  async function postToken(api, params, fetchFn) {
7329
- const res = await fetchFn(`${base(api)}${TOKEN}`, {
8169
+ const res = await fetchFn(`${base2(api)}${TOKEN}`, {
7330
8170
  method: "POST",
7331
8171
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
7332
8172
  body: new URLSearchParams(params).toString()
@@ -7377,23 +8217,23 @@ function toStore(clientId, t, now) {
7377
8217
  }
7378
8218
  function loadStore() {
7379
8219
  try {
7380
- return JSON.parse(readFileSync9(STORE_PATH, "utf8"));
8220
+ return JSON.parse(readFileSync8(STORE_PATH2, "utf8"));
7381
8221
  } catch {
7382
8222
  return null;
7383
8223
  }
7384
8224
  }
7385
8225
  function saveStore(s) {
7386
- writeFileSync7(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
8226
+ writeFileSync7(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
7387
8227
  }
7388
8228
  function clearStore() {
7389
8229
  try {
7390
- unlinkSync2(STORE_PATH);
8230
+ unlinkSync3(STORE_PATH2);
7391
8231
  } catch {
7392
8232
  }
7393
8233
  }
7394
8234
  function loadClientId() {
7395
8235
  try {
7396
- return JSON.parse(readFileSync9(CLIENT_PATH, "utf8")).clientId;
8236
+ return JSON.parse(readFileSync8(CLIENT_PATH, "utf8")).clientId;
7397
8237
  } catch {
7398
8238
  return null;
7399
8239
  }
@@ -7428,6 +8268,15 @@ var sqliteIso = (ts) => {
7428
8268
  const ms = sqliteMs(ts);
7429
8269
  return Number.isNaN(ms) ? ts : new Date(ms).toISOString();
7430
8270
  };
8271
+ function stormFacts(db2) {
8272
+ if (!db2.stormActive()) return null;
8273
+ const since = db2.currentStormStartedAt();
8274
+ const recs = since ? db2.stormProbeRecordsSince(since) : [];
8275
+ return {
8276
+ reason: lastProbeReason(recs) === "limit" ? "limit" : "provider",
8277
+ hint: recs.length ? recs[recs.length - 1].resetHint ?? null : null
8278
+ };
8279
+ }
7431
8280
  function roySnapshot(db2, now = Date.now(), overmindLog = [], paused = false, daemonVersion = null, restart = null, selfHost = null) {
7432
8281
  const all = db2.listRuns();
7433
8282
  const c = (s) => all.filter((r) => r.status === s).length;
@@ -7437,11 +8286,11 @@ function roySnapshot(db2, now = Date.now(), overmindLog = [], paused = false, da
7437
8286
  if (r.status === "done" || r.status === "stopped" || r.status === "failed") return sqliteMs(r.updated_at) >= now - DAY_MS;
7438
8287
  return true;
7439
8288
  });
7440
- const stormActive = db2.stormActive();
8289
+ const storm = stormFacts(db2);
8290
+ const stormActive = storm !== null;
7441
8291
  const stormSince = db2.currentStormStartedAt();
7442
- const stormRecs = stormActive && stormSince ? db2.stormProbeRecordsSince(stormSince) : [];
7443
- const stormReason = stormActive ? lastProbeReason(stormRecs) === "limit" ? "limit" : "provider" : null;
7444
- const stormResetHint = stormRecs.length ? stormRecs[stormRecs.length - 1].resetHint ?? null : null;
8292
+ const stormReason = storm?.reason ?? null;
8293
+ const stormResetHint = storm?.hint ?? null;
7445
8294
  return {
7446
8295
  metrics: {
7447
8296
  running: c("running"),
@@ -8724,7 +9573,17 @@ function daemonStatus(deps) {
8724
9573
  const counts = {};
8725
9574
  for (const r of deps.db.listRuns()) counts[r.status] = (counts[r.status] ?? 0) + 1;
8726
9575
  const live = deps.liveSessionIds?.() ?? [];
9576
+ const ver = deps.daemonVersion?.() ?? null;
9577
+ const queuedReady = deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0).length;
8727
9578
  return {
9579
+ api: deps.api ?? TASKLESS_API,
9580
+ queuedReady,
9581
+ startedAt: ver?.startedAt ?? null,
9582
+ latestVersion: ver?.latestVersion ?? null,
9583
+ link: deps.link?.() ?? null,
9584
+ questions: deps.db.openQuestions().length,
9585
+ maxDrones: deps.maxDrones?.() ?? null,
9586
+ storm: stormFacts(deps.db),
8728
9587
  hostId: deps.selfHost?.hostId ?? null,
8729
9588
  hostName: deps.selfHost?.hostName ?? null,
8730
9589
  backend: SWARM_BACKEND,
@@ -8734,7 +9593,9 @@ function daemonStatus(deps) {
8734
9593
  counts,
8735
9594
  live,
8736
9595
  version: PACKAGE_VERSION,
8737
- repos: deps.repoMap?.() ?? []
9596
+ repos: deps.repoMap?.() ?? [],
9597
+ // TASWM-324: «нас не пускают» — то же состояние процесса, что кричит рамкой в лог.
9598
+ authDenied: authStateLine()
8738
9599
  };
8739
9600
  }
8740
9601
  function eventsStream(db2, narratorLog, control, daemonVersion, restart, liveSessionIds, selfHost) {
@@ -8834,7 +9695,12 @@ function makeHandler(deps) {
8834
9695
  if (p === "/merge-gate") {
8835
9696
  const ref = url.searchParams.get("pr") ?? "";
8836
9697
  if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
8837
- const resolve4 = deps.mergeGate ?? makeMergeGateResolver({ listRuns: () => deps.db.listRuns(), fallbackAutoReview: SWARM_AUTO_REVIEW });
9698
+ const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
9699
+ listRuns: () => deps.db.listRuns(),
9700
+ fallbackAutoReview: SWARM_AUTO_REVIEW,
9701
+ autoVerify: SWARM_AUTO_VERIFY
9702
+ // TASWM-344
9703
+ });
8838
9704
  return Response.json(await resolve4(ref));
8839
9705
  }
8840
9706
  if (p === "/resources") return Response.json(hostResourceSnapshot());
@@ -8993,15 +9859,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
8993
9859
 
8994
9860
  // src/launcher.ts
8995
9861
  import { spawn as spawn2 } from "child_process";
8996
- import { closeSync as closeSync3, existsSync as existsSync7, mkdirSync as mkdirSync7, openSync as openSync3, readdirSync as readdirSync7 } from "fs";
9862
+ import { closeSync as closeSync3, existsSync as existsSync7, mkdirSync as mkdirSync7, openSync as openSync3, readdirSync as readdirSync6 } from "fs";
8997
9863
  import { createRequire } from "module";
8998
- import { homedir as homedir4 } from "os";
8999
- import { dirname as dirname3, join as join12 } from "path";
9864
+ import { homedir as homedir3 } from "os";
9865
+ import { dirname as dirname3, join as join11 } from "path";
9000
9866
  import { fileURLToPath } from "url";
9001
9867
 
9002
9868
  // src/droneHome.ts
9003
- import { lstatSync, mkdirSync as mkdirSync6, readdirSync as readdirSync6, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
9004
- import { join as join11, relative, sep } from "path";
9869
+ import { lstatSync, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync, statSync as statSync6, symlinkSync as symlinkSync2 } from "fs";
9870
+ import { join as join10, relative, sep } from "path";
9005
9871
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
9006
9872
  function droneHomeDeny(env = process.env) {
9007
9873
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -9020,28 +9886,28 @@ function buildDroneHome(opts) {
9020
9886
  const deny = opts.deny ?? droneHomeDeny();
9021
9887
  const platform = opts.platform ?? process.platform;
9022
9888
  mkdirSync6(droneHome, { recursive: true, mode: 448 });
9023
- const wanted = new Set(mirroredEntries(readdirSync6(realHome), deny, platform));
9889
+ const wanted = new Set(mirroredEntries(readdirSync5(realHome), deny, platform));
9024
9890
  const rel = relative(realHome, droneHome);
9025
9891
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
9026
- for (const existing of readdirSync6(droneHome)) {
9892
+ for (const existing of readdirSync5(droneHome)) {
9027
9893
  if (wanted.has(existing)) continue;
9028
- const p = join11(droneHome, existing);
9894
+ const p = join10(droneHome, existing);
9029
9895
  try {
9030
9896
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
9031
9897
  } catch {
9032
9898
  }
9033
9899
  }
9034
9900
  for (const name of wanted) {
9035
- const link = join11(droneHome, name);
9901
+ const link = join10(droneHome, name);
9036
9902
  try {
9037
9903
  lstatSync(link);
9038
9904
  continue;
9039
9905
  } catch {
9040
9906
  }
9041
- const target = join11(realHome, name);
9907
+ const target = join10(realHome, name);
9042
9908
  let isDir;
9043
9909
  try {
9044
- isDir = statSync7(target).isDirectory();
9910
+ isDir = statSync6(target).isDirectory();
9045
9911
  } catch {
9046
9912
  continue;
9047
9913
  }
@@ -9059,7 +9925,7 @@ var REAL_PATH_ENV_PINS = [
9059
9925
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
9060
9926
  ];
9061
9927
  function realPathEnvPins(realHome) {
9062
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join11(realHome, ...segments)]));
9928
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join10(realHome, ...segments)]));
9063
9929
  }
9064
9930
  function dronePrivatePaths(realHome, swarmHome) {
9065
9931
  return [
@@ -9157,7 +10023,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
9157
10023
  const env = { ...base3 };
9158
10024
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
9159
10025
  if (isolate && !base3.TURBO_CACHE_DIR) {
9160
- env.TURBO_CACHE_DIR = join12(worktree, ".turbo", "cache");
10026
+ env.TURBO_CACHE_DIR = join11(worktree, ".turbo", "cache");
9161
10027
  }
9162
10028
  Object.assign(env, droneTestEnv(droneCap, base3));
9163
10029
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -9185,20 +10051,20 @@ var DAEMON_STATE_ENV = [
9185
10051
  "TASKLESS_TOKEN"
9186
10052
  ];
9187
10053
  function bundledRoleDir(kind) {
9188
- return join12(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
10054
+ return join11(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
9189
10055
  }
9190
10056
  function roleDir(worktree, kind) {
9191
- return join12(worktree, ".claude", "roles", kind);
10057
+ return join11(worktree, ".claude", "roles", kind);
9192
10058
  }
9193
10059
  function roleDirs(worktree, kind) {
9194
10060
  return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
9195
10061
  }
9196
10062
  function nextRunLogPath(runLogsDir, runId) {
9197
- const dir = join12(runLogsDir, String(runId));
10063
+ const dir = join11(runLogsDir, String(runId));
9198
10064
  mkdirSync7(dir, { recursive: true });
9199
10065
  let files = [];
9200
10066
  try {
9201
- files = readdirSync7(dir);
10067
+ files = readdirSync6(dir);
9202
10068
  } catch {
9203
10069
  }
9204
10070
  let next = 0;
@@ -9206,7 +10072,7 @@ function nextRunLogPath(runLogsDir, runId) {
9206
10072
  const m = /^(\d+)\.json/.exec(f);
9207
10073
  if (m) next = Math.max(next, Number(m[1]) + 1);
9208
10074
  }
9209
- return join12(dir, `${next}.json`);
10075
+ return join11(dir, `${next}.json`);
9210
10076
  }
9211
10077
  function openNextRunLog(runLogsDir, runId) {
9212
10078
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -9274,6 +10140,9 @@ function makeLauncher(cfg) {
9274
10140
  SWARM_RUN_NAME: run.name,
9275
10141
  ...run.workspace_id ? { SWARM_WORKSPACE_ID: run.workspace_id } : {},
9276
10142
  ...run.reviews_run_id != null ? { SWARM_REVIEWS_RUN_ID: String(run.reviews_run_id) } : {},
10143
+ // TASWM-344: та же роль, что у SWARM_REVIEWS_RUN_ID выше, для верификатора — без неё
10144
+ // его done() в client-режиме пошёл бы веткой обычной сдачи PR, и вердикт пропал бы.
10145
+ ...run.verifies_run_id != null ? { SWARM_VERIFIES_RUN_ID: String(run.verifies_run_id) } : {},
9277
10146
  ...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
9278
10147
  }
9279
10148
  };
@@ -9284,11 +10153,11 @@ function makeLauncher(cfg) {
9284
10153
  } catch (e) {
9285
10154
  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)}`);
9286
10155
  }
9287
- const realHome = cfg.droneIsolation?.realHome ?? homedir4();
10156
+ const realHome = cfg.droneIsolation?.realHome ?? homedir3();
9288
10157
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
9289
- if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join12(cfg.droneIsolation.swarmHome, "token"))) {
10158
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join11(cfg.droneIsolation.swarmHome, "token"))) {
9290
10159
  console.error(
9291
- `[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 ${join12(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.`
10160
+ `[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 ${join11(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.`
9292
10161
  );
9293
10162
  }
9294
10163
  const executor = cfg.executor ?? resolveExecutor();
@@ -9343,14 +10212,14 @@ function makeLauncher(cfg) {
9343
10212
  init_droneChannel();
9344
10213
 
9345
10214
  // src/git.ts
9346
- import { existsSync as existsSync8, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
10215
+ import { existsSync as existsSync8, readdirSync as readdirSync7, statSync as statSync7 } from "fs";
9347
10216
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
9348
- import { isAbsolute as isAbsolute2, join as join13, sep as sep2 } from "path";
10217
+ import { isAbsolute, join as join12, sep as sep2 } from "path";
9349
10218
  import { $ as $5 } from "execa";
9350
10219
  init_swarmFiles();
9351
10220
  function isLinkedWorktree(dir) {
9352
10221
  try {
9353
- return statSync8(join13(dir, ".git")).isFile();
10222
+ return statSync7(join12(dir, ".git")).isFile();
9354
10223
  } catch {
9355
10224
  return false;
9356
10225
  }
@@ -9405,7 +10274,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
9405
10274
 
9406
10275
  // src/attachments.ts
9407
10276
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
9408
- import { join as join15 } from "path";
10277
+ import { join as join13 } from "path";
9409
10278
  init_swarmFiles();
9410
10279
  var ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
9411
10280
  var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
@@ -9417,28 +10286,29 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
9417
10286
 
9418
10287
  // src/shotUpload.ts
9419
10288
  import { readFile as fsReadFile } from "fs/promises";
9420
- import { basename as basename3, isAbsolute as isAbsolute3, join as join16 } from "path";
10289
+ import { basename as basename3, isAbsolute as isAbsolute2, join as join14 } from "path";
9421
10290
  var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
9422
10291
 
9423
10292
  // src/taskless.ts
9424
10293
  init_config();
9425
10294
  init_agentToken();
10295
+ init_linkFailure();
9426
10296
 
9427
10297
  // src/workspace.ts
9428
10298
  init_config();
9429
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
9430
- import { join as join17 } from "path";
9431
- var WORKSPACE_PATH = join17(SWARM_HOME, "workspace.json");
10299
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
10300
+ import { join as join15 } from "path";
10301
+ var WORKSPACE_PATH = join15(SWARM_HOME, "workspace.json");
9432
10302
  function loadWorkspace() {
9433
10303
  try {
9434
- const w = JSON.parse(readFileSync11(WORKSPACE_PATH, "utf8"));
10304
+ const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
9435
10305
  return w && typeof w.id === "string" && w.id ? w : null;
9436
10306
  } catch {
9437
10307
  return null;
9438
10308
  }
9439
10309
  }
9440
10310
  function saveWorkspace(w) {
9441
- writeFileSync9(WORKSPACE_PATH, JSON.stringify(w, null, 2), { mode: 384 });
10311
+ writeFileSync8(WORKSPACE_PATH, JSON.stringify(w, null, 2), { mode: 384 });
9442
10312
  }
9443
10313
  function clearWorkspace() {
9444
10314
  try {
@@ -9476,6 +10346,7 @@ function parseJsonContent(res) {
9476
10346
  return null;
9477
10347
  }
9478
10348
  }
10349
+ var TASKLESS_CONNECT_TIMEOUT_MS = 1e4;
9479
10350
  async function resolveApiToken(api, explicit) {
9480
10351
  if (explicit) return explicit;
9481
10352
  const agent = loadAgentToken();
@@ -9503,22 +10374,37 @@ async function connectSwarmMcp(cfg) {
9503
10374
  const api = cfg?.api ?? TASKLESS_API;
9504
10375
  const workspace = cfg?.workspace ?? resolvedWorkspaceId();
9505
10376
  const token = await resolveApiToken(api, cfg?.token);
9506
- if (!token) return null;
10377
+ if (!token) {
10378
+ swarmConnectFailure = "\u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u0430 \u0440\u043E\u044F \u2014 \u043D\u0443\u0436\u0435\u043D \u0432\u0445\u043E\u0434: swarm login";
10379
+ return null;
10380
+ }
9507
10381
  const url = new URL(mcpEndpoint(api));
9508
10382
  const transport = new StreamableHTTPClientTransport(url, {
9509
10383
  requestInit: { headers: { Authorization: `Bearer ${token}` } }
9510
10384
  });
9511
10385
  const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
9512
10386
  try {
9513
- await client.connect(transport);
10387
+ await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
9514
10388
  } catch (e) {
9515
- console.warn(
9516
- isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox)` : `Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`
9517
- );
10389
+ if (isRevokedTokenError(e)) {
10390
+ swarmConnectFailure = revokedTokenLine();
10391
+ const shout = noteTokenRejected();
10392
+ if (shout) console.error(`${shout}
10393
+ \u0421\u043E\u0431\u044B\u0442\u0438\u044F \u043F\u043E\u043A\u0430 \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox \u0438 \u0443\u0435\u0434\u0443\u0442, \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u0441\u0442\u0443\u043F \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.`);
10394
+ } else {
10395
+ swarmConnectFailure = describeError(e);
10396
+ console.warn(`Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${swarmConnectFailure}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`);
10397
+ }
9518
10398
  return null;
9519
10399
  }
10400
+ swarmConnectFailure = null;
10401
+ noteTokenAccepted();
9520
10402
  return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
9521
10403
  }
10404
+ var swarmConnectFailure = null;
10405
+ function lastSwarmConnectFailure() {
10406
+ return swarmConnectFailure;
10407
+ }
9522
10408
  function swarmToolArgs(name, args, workspace) {
9523
10409
  if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
9524
10410
  return { workspaceId: workspace, ...args };
@@ -9526,8 +10412,10 @@ function swarmToolArgs(name, args, workspace) {
9526
10412
  var WS_SCOPED_SWARM_TOOLS = /* @__PURE__ */ new Set(["swarm_run_upsert", "get_task"]);
9527
10413
 
9528
10414
  // src/mcpResilient.ts
10415
+ init_linkFailure();
9529
10416
  var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
9530
10417
  var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
10418
+ var DEFAULT_RETRY_DELAYS_MS = [200, 800];
9531
10419
  function errorResult(reason) {
9532
10420
  return { isError: true, content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }] };
9533
10421
  }
@@ -9535,11 +10423,16 @@ function resilientSwarmMcp(opts = {}) {
9535
10423
  const connect = opts.connect ?? (() => connectSwarmMcp());
9536
10424
  const backoff = opts.backoffMs?.length ? opts.backoffMs : DEFAULT_BACKOFF_MS;
9537
10425
  const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
10426
+ const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
10427
+ const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
9538
10428
  let caller = null;
9539
10429
  let lastTryAt = 0;
9540
10430
  let failures = 0;
9541
10431
  let downSince = null;
9542
10432
  let downReported = false;
10433
+ let lastConnectReason = null;
10434
+ let lastOkAt = null;
10435
+ let lastCallReason = null;
9543
10436
  function maybeReportDown() {
9544
10437
  if (downReported || downSince === null) return;
9545
10438
  const downMs = Date.now() - downSince;
@@ -9547,45 +10440,65 @@ function resilientSwarmMcp(opts = {}) {
9547
10440
  downReported = true;
9548
10441
  opts.onDown?.(downMs);
9549
10442
  }
9550
- async function ensureCaller() {
10443
+ async function ensureCaller(force = false) {
9551
10444
  if (caller) return caller;
9552
10445
  const now = Date.now();
9553
10446
  const wait = backoff[Math.max(0, Math.min(failures - 1, backoff.length - 1))];
9554
- if (lastTryAt !== 0 && now - lastTryAt < wait) return null;
9555
- lastTryAt = now;
10447
+ if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
10448
+ if (!force) lastTryAt = now;
9556
10449
  let fresh = null;
9557
10450
  try {
9558
10451
  fresh = await connect();
9559
- } catch {
10452
+ if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
10453
+ } catch (e) {
9560
10454
  fresh = null;
10455
+ lastConnectReason = describeError(e);
9561
10456
  }
9562
10457
  if (fresh) {
9563
10458
  caller = fresh;
9564
10459
  failures = 0;
10460
+ lastConnectReason = null;
9565
10461
  if (downReported) opts.onUp?.();
9566
10462
  downSince = null;
9567
10463
  downReported = false;
9568
10464
  return caller;
9569
10465
  }
9570
- failures += 1;
10466
+ if (!force) failures += 1;
9571
10467
  if (downSince === null) downSince = now;
9572
10468
  return null;
9573
10469
  }
9574
- return async (name, args) => {
9575
- const active = await ensureCaller();
9576
- if (!active) {
9577
- maybeReportDown();
9578
- return errorResult("\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E");
9579
- }
9580
- try {
9581
- return await active(name, args);
9582
- } catch (e) {
9583
- caller = null;
9584
- if (downSince === null) downSince = Date.now();
9585
- maybeReportDown();
9586
- return errorResult(e instanceof Error ? e.message : String(e));
10470
+ const call = async (name, args) => {
10471
+ for (let attempt = 0; ; attempt++) {
10472
+ const active = await ensureCaller(attempt > 0);
10473
+ if (!active) {
10474
+ maybeReportDown();
10475
+ return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
10476
+ }
10477
+ try {
10478
+ const res = await active(name, args);
10479
+ lastOkAt = Date.now();
10480
+ lastCallReason = null;
10481
+ return res;
10482
+ } catch (e) {
10483
+ caller = null;
10484
+ if (downSince === null) downSince = Date.now();
10485
+ const { kind, detail: reason } = explainLinkFailure(e);
10486
+ lastCallReason = reason;
10487
+ const delay = retryDelays[attempt];
10488
+ if (delay === void 0 || kind !== "offline") {
10489
+ maybeReportDown();
10490
+ return errorResult(reason);
10491
+ }
10492
+ opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
10493
+ await sleep3(delay);
10494
+ }
9587
10495
  }
9588
10496
  };
10497
+ return Object.assign(call, {
10498
+ // Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
10499
+ // подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
10500
+ link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
10501
+ });
9589
10502
  }
9590
10503
 
9591
10504
  // src/repoScout.ts
@@ -9972,81 +10885,8 @@ async function runShotCli(argv, deps = {}) {
9972
10885
 
9973
10886
  // src/cli.ts
9974
10887
  init_daemonLink();
9975
-
9976
- // src/pauseState.ts
9977
- import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
9978
- import { join as join18 } from "path";
9979
- function pauseStatePath(home) {
9980
- return join18(home, "pause.json");
9981
- }
9982
- var ACTIVE = { paused: false, at: "", source: "cli" };
9983
- function loadPauseState(home, io = {}) {
9984
- const exists = io.exists ?? existsSync9;
9985
- const readFile2 = io.readFile ?? ((p) => readFileSync12(p, "utf8"));
9986
- const path = pauseStatePath(home);
9987
- if (!exists(path)) return ACTIVE;
9988
- try {
9989
- const parsed = JSON.parse(readFile2(path));
9990
- if (parsed.paused !== true) return ACTIVE;
9991
- return {
9992
- paused: true,
9993
- at: typeof parsed.at === "string" ? parsed.at : "",
9994
- source: parsed.source === "web" || parsed.source === "remote" ? parsed.source : "cli",
9995
- ...typeof parsed.reason === "string" && parsed.reason.trim() ? { reason: parsed.reason.trim() } : {}
9996
- };
9997
- } catch {
9998
- return ACTIVE;
9999
- }
10000
- }
10001
- function savePauseState(home, state, io = {}) {
10002
- const path = pauseStatePath(home);
10003
- if (!state.paused) {
10004
- const remove = io.remove ?? ((p) => existsSync9(p) && unlinkSync5(p));
10005
- remove(path);
10006
- return;
10007
- }
10008
- const writeFile3 = io.writeFile ?? ((p, data) => {
10009
- writeFileSync10(`${p}.tmp`, data);
10010
- renameSync5(`${p}.tmp`, p);
10011
- });
10012
- writeFile3(path, JSON.stringify(state, null, 2));
10013
- }
10014
- function makePauseState(source, reason, now = Date.now()) {
10015
- return {
10016
- paused: true,
10017
- at: new Date(now).toISOString(),
10018
- source,
10019
- ...reason && reason.trim() ? { reason: reason.trim() } : {}
10020
- };
10021
- }
10022
- function pauseText(state, now = Date.now()) {
10023
- if (!state?.paused) return "\u0440\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u0435\u043D \u2014 \u0431\u0435\u0440\u0451\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443";
10024
- const since = state.at ? sinceText(state.at, now) : "";
10025
- const who = { cli: "\u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435", web: "\u0441 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0439 \u043C\u043E\u0440\u0434\u044B", remote: "\u0438\u0437 Taskless" }[state.source];
10026
- const why = state.reason ? ` \u2014 ${state.reason}` : "";
10027
- return `\u0440\u043E\u0439 \u041D\u0410 \u041F\u0410\u0423\u0417\u0415${since} (${who})${why}`;
10028
- }
10029
- function sinceText(at, now) {
10030
- const ms = Date.parse(at);
10031
- if (Number.isNaN(ms)) return "";
10032
- const min = Math.max(0, Math.round((now - ms) / 6e4));
10033
- if (min < 1) return " (\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E)";
10034
- if (min < 60) return ` (${min} \u043C\u0438\u043D)`;
10035
- return ` (${Math.round(min / 60)} \u0447)`;
10036
- }
10037
-
10038
- // src/heartbeat.ts
10039
- import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
10040
- function readHeartbeat(path) {
10041
- try {
10042
- const ms = Number(readFileSync13(path, "utf8").trim());
10043
- return Number.isFinite(ms) ? ms : null;
10044
- } catch {
10045
- return null;
10046
- }
10047
- }
10048
-
10049
- // src/cli.ts
10888
+ init_pauseState();
10889
+ init_heartbeat();
10050
10890
  init_src();
10051
10891
  init_version();
10052
10892
  function takeRepo(argv) {
@@ -10159,14 +10999,24 @@ async function runLoginFlow(db2, opts) {
10159
10999
  const web = startWeb({ db: db2 });
10160
11000
  try {
10161
11001
  let agent = loadAgentToken();
11002
+ if (agent && opts.force) {
11003
+ console.log("--force: \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E, \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u0441\u0442\u0430\u0440\u044B\u0439 \u0442\u043E\u043A\u0435\u043D.");
11004
+ agent = null;
11005
+ }
10162
11006
  if (agent) {
10163
11007
  const probe = await probeAgentToken({ api: TASKLESS_API, token: agent.token });
10164
- if (probe === "revoked") {
10165
- console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C (\u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E.");
10166
- clearAgentToken();
10167
- agent = null;
10168
- } else {
11008
+ if (probe === "ok") {
10169
11009
  console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u044E \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430.");
11010
+ } else {
11011
+ if (probe === "revoked") {
11012
+ console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (\u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E.");
11013
+ clearAgentToken();
11014
+ } else {
11015
+ console.log(
11016
+ "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0434/\u043E\u0442\u043A\u0430\u0437) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E: \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043D\u0435\u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0437\u0430 \xAB\u0432\u0441\u0451 \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435\xBB \u043D\u0435\u043B\u044C\u0437\u044F."
11017
+ );
11018
+ }
11019
+ agent = null;
10170
11020
  }
10171
11021
  }
10172
11022
  if (!agent) {
@@ -10201,13 +11051,16 @@ async function runLoginFlow(db2, opts) {
10201
11051
  const minted = await mintAgentToken({
10202
11052
  api: TASKLESS_API,
10203
11053
  bearer: fresh.accessToken,
10204
- label: thisHost.hostName
11054
+ label: thisHost.hostName,
11055
+ hostId: thisHost.hostId,
11056
+ hostName: thisHost.hostName
10205
11057
  });
10206
11058
  saveAgentToken({
10207
11059
  token: minted.token,
10208
11060
  ...minted.tokenId ? { tokenId: minted.tokenId } : {},
10209
11061
  issuedAt: Date.now(),
10210
- api: TASKLESS_API
11062
+ api: TASKLESS_API,
11063
+ hostId: thisHost.hostId
10211
11064
  });
10212
11065
  } catch (e) {
10213
11066
  console.error(
@@ -10220,7 +11073,9 @@ async function runLoginFlow(db2, opts) {
10220
11073
  console.log(
10221
11074
  `\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D \u2192 ${SWARM_HOME}/agent.json (\u043F\u0440\u0430\u0432\u0430 \u0443\u0436\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u0438\u0445). \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0441\u043A\u0438\u0439 OAuth \u0441\u0442\u0451\u0440\u0442 \u2014 \u0440\u043E\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0445\u043E\u0434\u0438\u0442 \u0442\u0432\u043E\u0438\u043C \u0442\u043E\u043A\u0435\u043D\u043E\u043C (TASWM-296).`
10222
11075
  );
10223
- console.log("\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u043C\u043E\u0436\u043D\u043E \u043A\u043D\u043E\u043F\u043A\u043E\u0439 \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435: \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439 \u2192 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F\xBB.");
11076
+ console.log(
11077
+ `\u0442\u043E\u043A\u0435\u043D \u043F\u0440\u0438\u0432\u044F\u0437\u0430\u043D \u043A \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435 (${thisHost.hostName}) \u2014 \u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 \u043C\u0430\u0448\u0438\u043D\u0430\u0445 \u0440\u043E\u0439 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C. \u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043E\u0434\u043D\u0443 \u043C\u0430\u0448\u0438\u043D\u0443 \u2014 \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D (/swarm \u2192 \u0424\u043B\u043E\u0442), \u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u2014 \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439.`
11078
+ );
10224
11079
  }
10225
11080
  const call = await connectSwarmMcp();
10226
11081
  if (!call) {
@@ -10334,7 +11189,7 @@ async function runDaemonLogs(argv) {
10334
11189
  console.error(`\u043B\u043E\u0433 \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u044D\u0442\u043E \xABswarm log ${stray}\xBB (\u0431\u0435\u0437 s). \xABswarm logs\xBB \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430; \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A \u2014 \xABswarm logs -n 200\xBB.`);
10335
11190
  return 1;
10336
11191
  }
10337
- if (!existsSync14(DAEMON_LOG_PATH)) {
11192
+ if (!existsSync15(DAEMON_LOG_PATH)) {
10338
11193
  console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
10339
11194
  console.error("\u043E\u043D \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0444\u043E\u043D\u043E\u0432\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 (swarm start). \u0414\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 (--foreground) \u2014 \u0432\u044B\u0432\u043E\u0434 \u0438\u0434\u0451\u0442 \u0442\u0443\u0434\u0430.");
10340
11195
  return 1;
@@ -10518,16 +11373,22 @@ function usage() {
10518
11373
  swarm doctor \u0442\u0430 \u0436\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0411\u0415\u0417 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438: \u0447\u0442\u043E \u043D\u0435 \u0442\u0430\u043A \u0438 \u043A\u0430\u043A \u0447\u0438\u043D\u0438\u0442\u044C
10519
11374
  swarm update \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0440\u043E\u0439 \u0434\u043E \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 (\u0442\u0435\u043C \u0436\u0435 \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u043E\u043C,
10520
11375
  \u043A\u0430\u043A\u0438\u043C \u043E\u043D \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D)
10521
- swarm login [--workspace <slug|id>] [--no-open]
11376
+ swarm login [--workspace <slug|id>] [--no-open] [--force]
10522
11377
  \u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441. \u0414\u0435\u043C\u043E\u043D\u0430 \u0433\u0430\u0441\u0438\u0442 \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442
10523
11378
  \u0441\u0430\u043C\u0430 (\u0436\u0438\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043E\u0438\u0433\u0440\u044B\u0432\u0430\u044E\u0442) \u2014 \u043F\u0435\u0440\u0435\u043B\u043E\u0433\u0438\u043D \u044D\u0442\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430,
10524
- \u0431\u0435\u0437 stop/start \u0440\u0443\u043A\u0430\u043C\u0438
11379
+ \u0431\u0435\u0437 stop/start \u0440\u0443\u043A\u0430\u043C\u0438. \u0422\u043E\u043A\u0435\u043D \u0432\u044B\u0434\u0430\u0451\u0442\u0441\u044F \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435: \u0432\u0445\u043E\u0434 \u0437\u0434\u0435\u0441\u044C
11380
+ \u043D\u0435 \u0432\u044B\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0440\u043E\u0439 \u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 (TASWM-324). --force \u0432\u0445\u043E\u0434\u0438\u0442 \u0437\u0430\u043D\u043E\u0432\u043E,
11381
+ \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u0441\u0442\u0430\u0440\u044B\u0439 \u0442\u043E\u043A\u0435\u043D
10525
11382
  swarm start [--repo <path>] [--foreground]
10526
11383
  \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C, \u0441\u043F\u0430\u0432\u043D\u0438\u0442 \u0438 \u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u043E\u0432.
10527
11384
  \u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0430\u0445\u043E\u0434\u0438\u0442 \u0441\u0430\u043C \u2014 \u043A\u043B\u043E\u043D\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 Taskless \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435;
10528
11385
  --repo \u043D\u0443\u0436\u0435\u043D, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043B\u0435\u0436\u0430\u0442 \u0432 \u043D\u0435\u043E\u0431\u044B\u0447\u043D\u043E\u043C \u043C\u0435\u0441\u0442\u0435
10529
- swarm status \u0436\u0438\u0432 \u043B\u0438 \u0434\u0435\u043C\u043E\u043D, \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u043B\u0438 \u0440\u043E\u0439, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435,
10530
- \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F \u0438 \u0433\u0434\u0435 \u043D\u0430\u0448\u043B\u0438\u0441\u044C \u0438\u0445 \u043A\u043B\u043E\u043D\u044B
11386
+ swarm status \u0420\u0410\u0411\u041E\u0422\u0410\u0415\u0422 \u041B\u0418 \u0420\u041E\u0419 \u2014 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0438 \u043F\u0435\u0440\u0432\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439. \u0414\u0430\u043B\u044C\u0448\u0435
11387
+ \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438 \u0441\u043D\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445: \u0434\u0435\u043C\u043E\u043D, \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C (\u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439:
11388
+ \u0442\u043E\u043A\u0435\u043D/\u0441\u0435\u0442\u044C/\u043E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u043E\u0434\u0430) \u0438 \u0431\u0443\u0444\u0435\u0440, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u043E\u0434 \u0441\u043B\u044B\u0448\u0430\u043B \u044D\u0442\u0443
11389
+ \u043C\u0430\u0448\u0438\u043D\u0443, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043D\u044F\u0442\u043E \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0441\u0442\u043E\u0438\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C.
11390
+ \u041F\u0440\u0438 \u043D\u0435\u043F\u043E\u043B\u0430\u0434\u043A\u0435 \u0433\u043E\u0432\u043E\u0440\u0438\u0442, \u0427\u0422\u041E \u0414\u0415\u041B\u0410\u0422\u042C. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0438 \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u043E\u0434
11391
+ \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u0442\u043E\u0433\u0434\u0430 \u0447\u0435\u0441\u0442\u043D\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0432\u0438\u0434\u0438\u0442 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E
10531
11392
 
10532
11393
  \u0414\u0410\u0422\u042C \u0420\u041E\u042E \u0420\u0410\u0411\u041E\u0422\u0423
10533
11394
  swarm spawn <slug> [area] [\u0444\u043B\u0430\u0433\u0438 \u043D\u0438\u0436\u0435]
@@ -10549,6 +11410,8 @@ function usage() {
10549
11410
  swarm merge-gate <pr> \u043C\u043E\u0436\u043D\u043E \u043B\u0438 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u044D\u0442\u043E\u0442 PR (\u0441\u0441\u044B\u043B\u043A\u0430 \u0438\u043B\u0438 \u043D\u043E\u043C\u0435\u0440): \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441
10550
11411
  \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430. \u0412\u044B\u0445\u043E\u0434 1 = \u043D\u0435\u043B\u044C\u0437\u044F.
10551
11412
  \u0422\u043E\u0442 \u0436\u0435 \u043E\u0442\u0432\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 \u0445\u0443\u043A, \u043F\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044B\u0432\u0430\u044E\u0449\u0438\u0439 gh pr merge
11413
+ swarm verdicts \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u044B \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u044B\u0445 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u0432: \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u043D\u0435
11414
+ \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u043B\u043E\u0441\u044C \u0438\u043B\u0438 \u0441\u043B\u043E\u043C\u0430\u043B\u043E\u0441\u044C \u0440\u044F\u0434\u043E\u043C \u2014 \u043F\u043E \u0440\u043E\u044E \u0438 \u043F\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F\u043C
10552
11415
  swarm questions \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0434\u0440\u043E\u043D\u043E\u0432
10553
11416
  swarm answer <qid> <\u0442\u0435\u043A\u0441\u0442\u2026>
10554
11417
  \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430
@@ -10598,7 +11461,7 @@ function effectiveWorktreesDir() {
10598
11461
  legacyDir: WORKTREES_LEGACY_DIR,
10599
11462
  targetDir: WORKTREES_DIR,
10600
11463
  envOverride: process.env.SWARM_WORKTREES || "",
10601
- exists: (d) => existsSync14(d),
11464
+ exists: (d) => existsSync15(d),
10602
11465
  isNonEmptyDir: (d) => {
10603
11466
  try {
10604
11467
  return readdirSync9(d).length > 0;
@@ -10658,7 +11521,7 @@ async function runScoutCommand(repo) {
10658
11521
  });
10659
11522
  child.on("exit", (c) => resolve4(c ?? 1));
10660
11523
  }),
10661
- readOut: () => existsSync14(outFile) ? readFileSync16(outFile, "utf8") : null,
11524
+ readOut: () => existsSync15(outFile) ? readFileSync17(outFile, "utf8") : null,
10662
11525
  clearOut: () => rmSync2(outFile, { force: true }),
10663
11526
  report: (report) => client.reportRepoScout(repoFullName, report, name),
10664
11527
  log: (line) => console.log(line)
@@ -10852,25 +11715,8 @@ if (cmd === "cancel") {
10852
11715
  console.warn(NO_DAEMON_NOTE);
10853
11716
  }
10854
11717
  if (cmd === "status") {
10855
- const st = await daemonStatus2();
10856
- if (!st) {
10857
- const local = loadPauseState(SWARM_HOME);
10858
- console.log(`\u0434\u0435\u043C\u043E\u043D \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT} \u043C\u043E\u043B\u0447\u0438\u0442). \u041F\u043E\u0434\u043D\u044F\u0442\u044C: swarm start`);
10859
- console.log(local.paused ? `${pauseText(local)} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435, \u0441\u043D\u044F\u0442\u044C: swarm resume` : "\u043F\u0430\u0443\u0437\u044B \u043D\u0435\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u0440\u0430\u0431\u043E\u0442\u0443");
10860
- process.exit(1);
10861
- }
10862
- console.log(`\u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432 (v${st.version})${st.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${st.hostName}\xBB` : ""}, \u0431\u044D\u043A\u0435\u043D\u0434 ${st.backend}`);
10863
- console.log(pauseText(st.pause ?? (st.paused ? { paused: true, at: "", source: "web" } : null)));
10864
- if (st.restart?.phase === "waiting") {
10865
- console.log(
10866
- `\u0434\u0435\u043C\u043E\u043D \u0416\u0414\u0401\u0422 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (${st.restart.sessions.length}), \u0447\u0442\u043E\u0431\u044B ${st.restart.intent === "stop" ? "\u043F\u043E\u0433\u0430\u0441\u043D\u0443\u0442\u044C" : "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\u0441\u044F"} \u2014 \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043D\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F`
10867
- );
10868
- }
10869
- const counts = Object.entries(st.counts).filter(([, n]) => n > 0);
10870
- console.log(counts.length ? `\u043F\u0440\u043E\u0433\u043E\u043D\u044B: ${counts.map(([s, n]) => `${s} ${n}`).join(", ")}` : "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u043D\u0435\u0442");
10871
- console.log(st.live.length ? `\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u0441\u0435\u0439\u0447\u0430\u0441: ${st.live.length} (#${st.live.join(", #")})` : "\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435\u0442");
10872
- for (const line of repoMapLines(st.repos)) console.log(line);
10873
- process.exit(0);
11718
+ const { runStatusCli: runStatusCli2 } = await Promise.resolve().then(() => (init_statusCli(), statusCli_exports));
11719
+ process.exit(await runStatusCli2(rawArgs));
10874
11720
  }
10875
11721
  if (cmd === "merge-gate") {
10876
11722
  const ref = rawArgs[0];
@@ -10889,6 +11735,23 @@ if (cmd === "merge-gate") {
10889
11735
  console.log(`${mergeGateLine(answer)}${who}`);
10890
11736
  process.exit(answer.allowed ? 0 : 1);
10891
11737
  }
11738
+ if (cmd === "verdicts") {
11739
+ const runs = (await makeCliStore(SWARM_REPO)).listRuns();
11740
+ const overall = verifyStats(runs);
11741
+ console.log(verifyStatsLine(overall));
11742
+ if (overall.total > 0) {
11743
+ console.log("");
11744
+ console.log("\u043F\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F\u043C (\u043A\u043E\u0433\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0438):");
11745
+ for (const row of verifyStatsByDrone(runs)) {
11746
+ const pct = Math.round(row.negativeShare * 100);
11747
+ console.log(` ${row.name.padEnd(12)} \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u0432 ${row.total}, \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0445 ${row.negative} (${pct}%)`);
11748
+ }
11749
+ }
11750
+ const waiting = runs.filter((r) => r.status === "review" && r.kind === "drone" && r.pr_url && r.verify_verdict == null).length;
11751
+ if (waiting > 0) console.log(`
11752
+ PR \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430: ${waiting} \u2014 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 \u043E\u043D\u0438 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0438`);
11753
+ process.exit(0);
11754
+ }
10892
11755
  if (cmd === "pause" || cmd === "resume") {
10893
11756
  const wantPause = cmd === "pause";
10894
11757
  const reason = wantPause ? rawArgs.join(" ").trim() || null : null;
@@ -11149,7 +12012,8 @@ switch (cmd) {
11149
12012
  case "login": {
11150
12013
  const { workspace, rest } = takeWorkspace(rawArgs);
11151
12014
  const noOpen = rest.includes("--no-open");
11152
- const code = await runLogin(db, { workspace, noOpen });
12015
+ const force = rest.includes("--force");
12016
+ const code = await runLogin(db, { workspace, noOpen, force });
11153
12017
  db.close();
11154
12018
  process.exit(code);
11155
12019
  }