@taskless-app/swarm 0.6.26 → 0.6.28

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 +271 -218
  2. package/dist/overmind.js +494 -241
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -340,26 +340,6 @@ var init_timeline = __esm({
340
340
  }
341
341
  });
342
342
 
343
- // ../swarm-core/src/reviewPhase.ts
344
- var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
345
- var init_reviewPhase = __esm({
346
- "../swarm-core/src/reviewPhase.ts"() {
347
- "use strict";
348
- REVIEW_PHASE_TEXT = {
349
- checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
350
- awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
351
- ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
352
- in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
353
- changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
354
- ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
355
- reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
356
- };
357
- PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
358
- (a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
359
- );
360
- }
361
- });
362
-
363
343
  // ../swarm-core/src/verifyVerdict.ts
364
344
  function isVerifyVerdict(v) {
365
345
  return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
@@ -428,6 +408,31 @@ var init_verifyVerdict = __esm({
428
408
  }
429
409
  });
430
410
 
411
+ // ../swarm-core/src/reviewPhase.ts
412
+ var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
413
+ var init_reviewPhase = __esm({
414
+ "../swarm-core/src/reviewPhase.ts"() {
415
+ "use strict";
416
+ init_verifyVerdict();
417
+ REVIEW_PHASE_TEXT = {
418
+ checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
419
+ awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
420
+ ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
421
+ in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
422
+ changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
423
+ verify_found: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0430\u0448\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
424
+ verify_not_reproduced: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0451\u043B \u2014 \u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438",
425
+ awaiting_verify: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u043C \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044E",
426
+ verify_dead: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0434\u0430\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
427
+ ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
428
+ reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
429
+ };
430
+ PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
431
+ (a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
432
+ );
433
+ }
434
+ });
435
+
431
436
  // ../swarm-core/src/mergeGate.ts
432
437
  function computeMergeGate(input) {
433
438
  if (!input.hasRun) {
@@ -1619,6 +1624,13 @@ var init_zoneMemory = __esm({
1619
1624
  }
1620
1625
  });
1621
1626
 
1627
+ // ../swarm-core/src/taskPrs.ts
1628
+ var init_taskPrs = __esm({
1629
+ "../swarm-core/src/taskPrs.ts"() {
1630
+ "use strict";
1631
+ }
1632
+ });
1633
+
1622
1634
  // ../swarm-core/src/index.ts
1623
1635
  var init_src = __esm({
1624
1636
  "../swarm-core/src/index.ts"() {
@@ -1653,6 +1665,7 @@ var init_src = __esm({
1653
1665
  init_effort();
1654
1666
  init_queuePlan();
1655
1667
  init_zoneMemory();
1668
+ init_taskPrs();
1656
1669
  }
1657
1670
  });
1658
1671
 
@@ -2757,6 +2770,192 @@ var init_storm = __esm({
2757
2770
  }
2758
2771
  });
2759
2772
 
2773
+ // src/agentToken.ts
2774
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync } from "fs";
2775
+ import { join as join6 } from "path";
2776
+ function loadAgentToken() {
2777
+ try {
2778
+ const s = JSON.parse(readFileSync4(STORE_PATH, "utf8"));
2779
+ return s.token ? s : null;
2780
+ } catch {
2781
+ return null;
2782
+ }
2783
+ }
2784
+ function saveAgentToken(s) {
2785
+ writeFileSync4(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
2786
+ }
2787
+ function clearAgentToken() {
2788
+ try {
2789
+ unlinkSync(STORE_PATH);
2790
+ } catch {
2791
+ }
2792
+ }
2793
+ async function mintAgentToken(args, fetchFn = fetch) {
2794
+ const res = await fetchFn(`${base(args.api)}/api/swarm/agent/token`, {
2795
+ method: "POST",
2796
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
2797
+ body: JSON.stringify({
2798
+ ...args.label ? { label: args.label } : {},
2799
+ ...args.hostId ? { hostId: args.hostId } : {},
2800
+ ...args.hostName ? { hostName: args.hostName } : {}
2801
+ })
2802
+ });
2803
+ const text = await res.text();
2804
+ if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
2805
+ const j = JSON.parse(text);
2806
+ if (!j.token) throw new Error("agent token: \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 token");
2807
+ return { token: j.token, ...j.tokenId ? { tokenId: j.tokenId } : {} };
2808
+ }
2809
+ async function probeAgentToken(args, fetchFn = fetch) {
2810
+ try {
2811
+ const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
2812
+ headers: { Authorization: `Bearer ${args.token}` },
2813
+ // Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
2814
+ // то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
2815
+ signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
2816
+ });
2817
+ if (res.status === 401 || res.status === 403) return "revoked";
2818
+ return res.ok ? "ok" : "unreachable";
2819
+ } catch {
2820
+ return "unreachable";
2821
+ }
2822
+ }
2823
+ function isRevokedTokenError(e) {
2824
+ if (e && typeof e === "object" && e.code === 401) return true;
2825
+ const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
2826
+ return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
2827
+ }
2828
+ function revokedTokenLine() {
2829
+ 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";
2830
+ }
2831
+ function revokedTokenComplaint() {
2832
+ return [
2833
+ "",
2834
+ "\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",
2835
+ "\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",
2836
+ "\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",
2837
+ "\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:",
2838
+ " \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;",
2839
+ " \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;",
2840
+ " \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.",
2841
+ "",
2842
+ "\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",
2843
+ "\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.",
2844
+ "",
2845
+ "\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",
2846
+ "\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",
2847
+ "\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",
2848
+ ""
2849
+ ].join("\n");
2850
+ }
2851
+ var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
2852
+ var init_agentToken = __esm({
2853
+ "src/agentToken.ts"() {
2854
+ "use strict";
2855
+ init_config();
2856
+ STORE_PATH = join6(SWARM_HOME, "agent.json");
2857
+ base = (api) => api.replace(/\/$/, "");
2858
+ TOKEN_PROBE_TIMEOUT_MS = 8e3;
2859
+ }
2860
+ });
2861
+
2862
+ // src/linkFailure.ts
2863
+ function describeError(e, maxDepth = 4) {
2864
+ const parts = [];
2865
+ const seen = /* @__PURE__ */ new Set();
2866
+ let cur = e;
2867
+ for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
2868
+ seen.add(cur);
2869
+ const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
2870
+ const code = cur.code;
2871
+ const codeText = code === void 0 || code === null ? "" : String(code);
2872
+ const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
2873
+ if (label2 && !parts.includes(label2)) parts.push(label2);
2874
+ cur = cur.cause;
2875
+ }
2876
+ return parts.join(" \u2190 ") || String(e);
2877
+ }
2878
+ function explainLinkFailure(e) {
2879
+ const detail = describeError(e);
2880
+ if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
2881
+ if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
2882
+ if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
2883
+ if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
2884
+ return { kind: "rejected", detail };
2885
+ }
2886
+ var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
2887
+ var init_linkFailure = __esm({
2888
+ "src/linkFailure.ts"() {
2889
+ "use strict";
2890
+ init_agentToken();
2891
+ 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";
2892
+ 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;
2893
+ MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
2894
+ }
2895
+ });
2896
+
2897
+ // src/toolFailure.ts
2898
+ function classifyToolFailure(raw) {
2899
+ if (MISMATCH_RE.test(raw)) return TOOL_FAILURE.WORKSPACE_MISMATCH;
2900
+ if (FORBIDDEN_RE.test(raw)) return TOOL_FAILURE.FORBIDDEN;
2901
+ if (NOT_FOUND_RE.test(raw)) return TOOL_FAILURE.NOT_FOUND;
2902
+ return TOOL_FAILURE.TOOL_ERROR;
2903
+ }
2904
+ function classifyThrown(e) {
2905
+ const { kind, detail } = explainLinkFailure(e);
2906
+ const code = kind === "offline" ? TOOL_FAILURE.UNREACHABLE : kind === "unauthorized" ? TOOL_FAILURE.UNAUTHORIZED : (
2907
+ // «ручки нет» и «сервер отказал по существу» разбираем доменно: там может быть
2908
+ // и task_not_found, и отказ по правам — то есть наши коды, а не транспортные.
2909
+ classifyToolFailure(detail)
2910
+ );
2911
+ return { code, detail };
2912
+ }
2913
+ var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE, NOT_FOUND_RE, FORBIDDEN_RE, MISMATCH_RE;
2914
+ var init_toolFailure = __esm({
2915
+ "src/toolFailure.ts"() {
2916
+ "use strict";
2917
+ init_linkFailure();
2918
+ TOOL_FAILURE = {
2919
+ /** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
2920
+ NOT_FOUND: "not_found",
2921
+ /** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
2922
+ FORBIDDEN: "forbidden",
2923
+ /** Токен роя не состоит в воркспейсе, которым искали. */
2924
+ WORKSPACE_MISMATCH: "workspace_mismatch",
2925
+ /** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
2926
+ UNREACHABLE: "unreachable",
2927
+ /** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
2928
+ UNAUTHORIZED: "unauthorized",
2929
+ /** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
2930
+ TOOL_ERROR: "tool_error",
2931
+ /** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
2932
+ EMPTY: "empty"
2933
+ };
2934
+ TOOL_FAILURE_TEXT = {
2935
+ [TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
2936
+ [TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
2937
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
2938
+ [TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
2939
+ [TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
2940
+ [TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
2941
+ [TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
2942
+ };
2943
+ TOOL_FAILURE_RETRIABLE = {
2944
+ [TOOL_FAILURE.NOT_FOUND]: false,
2945
+ [TOOL_FAILURE.FORBIDDEN]: false,
2946
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
2947
+ [TOOL_FAILURE.UNREACHABLE]: true,
2948
+ // Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
2949
+ [TOOL_FAILURE.UNAUTHORIZED]: false,
2950
+ [TOOL_FAILURE.TOOL_ERROR]: false,
2951
+ [TOOL_FAILURE.EMPTY]: false
2952
+ };
2953
+ NOT_FOUND_RE = /task_not_found|not_found|не найдена|не найден/i;
2954
+ FORBIDDEN_RE = /forbidden|not visible|no access|нет доступа|(?:http|status|code)[ =:]*403\b|\(403\)|403 forbidden/i;
2955
+ MISMATCH_RE = /workspace_mismatch|not a member of that workspace/i;
2956
+ }
2957
+ });
2958
+
2760
2959
  // src/procList.ts
2761
2960
  function parsePsRows(out) {
2762
2961
  const rows = [];
@@ -3040,7 +3239,7 @@ var init_package = __esm({
3040
3239
  "package.json"() {
3041
3240
  package_default = {
3042
3241
  name: "@taskless-app/swarm",
3043
- version: "0.6.26",
3242
+ version: "0.6.28",
3044
3243
  type: "module",
3045
3244
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3046
3245
  keywords: [
@@ -3091,7 +3290,7 @@ var init_package = __esm({
3091
3290
  execa: "^10.0.0",
3092
3291
  zod: "3.25.76"
3093
3292
  },
3094
- gitHead: "52416995d46b0a843374d14bfb00dd310f71c4bf"
3293
+ gitHead: "62910fc63850764f4562b7ec3bd65b44d3557050"
3095
3294
  };
3096
3295
  }
3097
3296
  });
@@ -3223,18 +3422,18 @@ var init_netTimeout = __esm({
3223
3422
 
3224
3423
  // src/droneChannel.ts
3225
3424
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3226
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3425
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
3227
3426
  import { dirname as dirname3 } from "path";
3228
3427
  import { serve } from "@hono/node-server";
3229
3428
  function loadChannelSecret(path) {
3230
3429
  try {
3231
- const s = readFileSync7(path, "utf8").trim();
3430
+ const s = readFileSync8(path, "utf8").trim();
3232
3431
  if (s.length >= 32) return s;
3233
3432
  } catch {
3234
3433
  }
3235
3434
  const secret = randomBytes(32).toString("hex");
3236
3435
  mkdirSync7(dirname3(path), { recursive: true });
3237
- writeFileSync6(path, secret, { mode: 384 });
3436
+ writeFileSync7(path, secret, { mode: 384 });
3238
3437
  try {
3239
3438
  chmodSync2(path, 384);
3240
3439
  } catch {
@@ -3273,95 +3472,6 @@ var init_droneChannel = __esm({
3273
3472
  }
3274
3473
  });
3275
3474
 
3276
- // src/agentToken.ts
3277
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
3278
- import { join as join10 } from "path";
3279
- function loadAgentToken() {
3280
- try {
3281
- const s = JSON.parse(readFileSync8(STORE_PATH, "utf8"));
3282
- return s.token ? s : null;
3283
- } catch {
3284
- return null;
3285
- }
3286
- }
3287
- function saveAgentToken(s) {
3288
- writeFileSync7(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
3289
- }
3290
- function clearAgentToken() {
3291
- try {
3292
- unlinkSync2(STORE_PATH);
3293
- } catch {
3294
- }
3295
- }
3296
- async function mintAgentToken(args, fetchFn = fetch) {
3297
- const res = await fetchFn(`${base(args.api)}/api/swarm/agent/token`, {
3298
- method: "POST",
3299
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
3300
- body: JSON.stringify({
3301
- ...args.label ? { label: args.label } : {},
3302
- ...args.hostId ? { hostId: args.hostId } : {},
3303
- ...args.hostName ? { hostName: args.hostName } : {}
3304
- })
3305
- });
3306
- const text = await res.text();
3307
- if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
3308
- const j = JSON.parse(text);
3309
- if (!j.token) throw new Error("agent token: \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 token");
3310
- return { token: j.token, ...j.tokenId ? { tokenId: j.tokenId } : {} };
3311
- }
3312
- async function probeAgentToken(args, fetchFn = fetch) {
3313
- try {
3314
- const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
3315
- headers: { Authorization: `Bearer ${args.token}` },
3316
- // Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
3317
- // то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
3318
- signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
3319
- });
3320
- if (res.status === 401 || res.status === 403) return "revoked";
3321
- return res.ok ? "ok" : "unreachable";
3322
- } catch {
3323
- return "unreachable";
3324
- }
3325
- }
3326
- function isRevokedTokenError(e) {
3327
- if (e && typeof e === "object" && e.code === 401) return true;
3328
- const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
3329
- return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
3330
- }
3331
- function revokedTokenLine() {
3332
- 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";
3333
- }
3334
- function revokedTokenComplaint() {
3335
- return [
3336
- "",
3337
- "\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",
3338
- "\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",
3339
- "\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",
3340
- "\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:",
3341
- " \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;",
3342
- " \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;",
3343
- " \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.",
3344
- "",
3345
- "\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",
3346
- "\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.",
3347
- "",
3348
- "\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",
3349
- "\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",
3350
- "\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",
3351
- ""
3352
- ].join("\n");
3353
- }
3354
- var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
3355
- var init_agentToken = __esm({
3356
- "src/agentToken.ts"() {
3357
- "use strict";
3358
- init_config();
3359
- STORE_PATH = join10(SWARM_HOME, "agent.json");
3360
- base = (api) => api.replace(/\/$/, "");
3361
- TOKEN_PROBE_TIMEOUT_MS = 8e3;
3362
- }
3363
- });
3364
-
3365
3475
  // src/authState.ts
3366
3476
  function noteTokenRejected(nowMs = Date.now()) {
3367
3477
  if (!denial) {
@@ -3805,85 +3915,6 @@ var init_shotUpload = __esm({
3805
3915
  }
3806
3916
  });
3807
3917
 
3808
- // src/linkFailure.ts
3809
- function describeError(e, maxDepth = 4) {
3810
- const parts = [];
3811
- const seen = /* @__PURE__ */ new Set();
3812
- let cur = e;
3813
- for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
3814
- seen.add(cur);
3815
- const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
3816
- const code = cur.code;
3817
- const codeText = code === void 0 || code === null ? "" : String(code);
3818
- const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
3819
- if (label2 && !parts.includes(label2)) parts.push(label2);
3820
- cur = cur.cause;
3821
- }
3822
- return parts.join(" \u2190 ") || String(e);
3823
- }
3824
- function explainLinkFailure(e) {
3825
- const detail = describeError(e);
3826
- if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
3827
- if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
3828
- if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
3829
- if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
3830
- return { kind: "rejected", detail };
3831
- }
3832
- var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
3833
- var init_linkFailure = __esm({
3834
- "src/linkFailure.ts"() {
3835
- "use strict";
3836
- init_agentToken();
3837
- 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";
3838
- 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;
3839
- MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
3840
- }
3841
- });
3842
-
3843
- // src/toolFailure.ts
3844
- var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE;
3845
- var init_toolFailure = __esm({
3846
- "src/toolFailure.ts"() {
3847
- "use strict";
3848
- init_linkFailure();
3849
- TOOL_FAILURE = {
3850
- /** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
3851
- NOT_FOUND: "not_found",
3852
- /** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
3853
- FORBIDDEN: "forbidden",
3854
- /** Токен роя не состоит в воркспейсе, которым искали. */
3855
- WORKSPACE_MISMATCH: "workspace_mismatch",
3856
- /** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
3857
- UNREACHABLE: "unreachable",
3858
- /** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
3859
- UNAUTHORIZED: "unauthorized",
3860
- /** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
3861
- TOOL_ERROR: "tool_error",
3862
- /** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
3863
- EMPTY: "empty"
3864
- };
3865
- TOOL_FAILURE_TEXT = {
3866
- [TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
3867
- [TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
3868
- [TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
3869
- [TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
3870
- [TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
3871
- [TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
3872
- [TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
3873
- };
3874
- TOOL_FAILURE_RETRIABLE = {
3875
- [TOOL_FAILURE.NOT_FOUND]: false,
3876
- [TOOL_FAILURE.FORBIDDEN]: false,
3877
- [TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
3878
- [TOOL_FAILURE.UNREACHABLE]: true,
3879
- // Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
3880
- [TOOL_FAILURE.UNAUTHORIZED]: false,
3881
- [TOOL_FAILURE.TOOL_ERROR]: false,
3882
- [TOOL_FAILURE.EMPTY]: false
3883
- };
3884
- }
3885
- });
3886
-
3887
3918
  // src/workspace.ts
3888
3919
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
3889
3920
  import { join as join17 } from "path";
@@ -6866,7 +6897,7 @@ init_config();
6866
6897
  init_resources();
6867
6898
  init_names2();
6868
6899
  init_storm();
6869
- import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
6900
+ import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
6870
6901
  import { dirname as dirname2 } from "path";
6871
6902
  import { randomUUID } from "crypto";
6872
6903
 
@@ -6893,20 +6924,23 @@ function parsePendingRepoRules(raw) {
6893
6924
  return out;
6894
6925
  }
6895
6926
 
6927
+ // src/swarmClient.ts
6928
+ init_toolFailure();
6929
+
6896
6930
  // src/logTail.ts
6897
6931
  import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
6898
- import { join as join7 } from "path";
6932
+ import { join as join8 } from "path";
6899
6933
 
6900
6934
  // src/runLogDir.ts
6901
6935
  import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, statSync as statSync3 } from "fs";
6902
- import { join as join6 } from "path";
6936
+ import { join as join7 } from "path";
6903
6937
  function runLogDirName(runLogKey) {
6904
6938
  const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
6905
6939
  if (!safe) throw new Error(`\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435 \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0432 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 run-log'\u043E\u0432: ${JSON.stringify(runLogKey)}`);
6906
6940
  return /^\d+$/.test(safe) ? `k-${safe}` : safe;
6907
6941
  }
6908
6942
  function runLogDirPath(runLogsDir, runLogKey) {
6909
- return join6(runLogsDir, runLogDirName(runLogKey));
6943
+ return join7(runLogsDir, runLogDirName(runLogKey));
6910
6944
  }
6911
6945
  function findRunLogDir(runLogsDir, keys) {
6912
6946
  for (const key of keys) {
@@ -6934,6 +6968,14 @@ function toolErrorText(res) {
6934
6968
  if (!text) return "\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433 \u0432\u044B\u0437\u043E\u0432 \u0431\u0435\u0437 \u043E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u044F";
6935
6969
  return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
6936
6970
  }
6971
+ var DEPS_UNRETRIABLE_CODES = /* @__PURE__ */ new Set([
6972
+ TOOL_FAILURE.FORBIDDEN,
6973
+ TOOL_FAILURE.UNAUTHORIZED,
6974
+ TOOL_FAILURE.WORKSPACE_MISMATCH
6975
+ ]);
6976
+ function depsFailureRetriable(code) {
6977
+ return !DEPS_UNRETRIABLE_CODES.has(code);
6978
+ }
6937
6979
  function runRef(runKey) {
6938
6980
  return { __runRef: runKey };
6939
6981
  }
@@ -7030,6 +7072,13 @@ var SwarmClient = class _SwarmClient {
7030
7072
  // дальше удвоением до потолка TTL: устойчиво отвергающий прод (старее демона, скоуп
7031
7073
  // токена) не должен получать вопрос каждые четыре секунды до конца своей жизни.
7032
7074
  static DEPS_RETRY_BASE_MS = 5e3;
7075
+ // TASWM-517: сколько раз спрашиваем НЕСАМОЛЕЧАЩИЙСЯ отказ, прежде чем перестать вовсе.
7076
+ // `agent_forbidden` («тул недоступен аккаунту-агенту роя») повтором не чинится: 26.08
7077
+ // два прогона получили его по 53 раза каждый и остались в очереди навсегда. Три захода
7078
+ // при backoff 5с/10с — это ~15 секунд: запас на случай, если классификатор ошибся и
7079
+ // отказ на деле транзиентный, но не час молчаливого простоя. По исчерпании прогон
7080
+ // роняет гейт спавна (DEPS_UNRETRIABLE_ATTEMPTS читается и там).
7081
+ static DEPS_UNRETRIABLE_ATTEMPTS = 3;
7033
7082
  // TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
7034
7083
  // ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
7035
7084
  // каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
@@ -7101,13 +7150,13 @@ var SwarmClient = class _SwarmClient {
7101
7150
  recover() {
7102
7151
  if (existsSync6(this.mapPath)) {
7103
7152
  try {
7104
- const obj = JSON.parse(readFileSync4(this.mapPath, "utf8"));
7153
+ const obj = JSON.parse(readFileSync5(this.mapPath, "utf8"));
7105
7154
  for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
7106
7155
  } catch {
7107
7156
  }
7108
7157
  }
7109
7158
  if (existsSync6(this.outboxPath)) {
7110
- const txt = readFileSync4(this.outboxPath, "utf8");
7159
+ const txt = readFileSync5(this.outboxPath, "utf8");
7111
7160
  for (const line of txt.split("\n")) {
7112
7161
  const s = line.trim();
7113
7162
  if (!s) continue;
@@ -7130,7 +7179,7 @@ var SwarmClient = class _SwarmClient {
7130
7179
  if (!existsSync6(this.stormPath)) return;
7131
7180
  let txt;
7132
7181
  try {
7133
- txt = readFileSync4(this.stormPath, "utf8");
7182
+ txt = readFileSync5(this.stormPath, "utf8");
7134
7183
  } catch {
7135
7184
  return;
7136
7185
  }
@@ -7635,11 +7684,12 @@ var SwarmClient = class _SwarmClient {
7635
7684
  res = await this.call("swarm_deps_status", { refs: ask });
7636
7685
  } catch (e) {
7637
7686
  for (const ref of ask) this.depsAskedAt.delete(ref);
7638
- this.noteDepsUnanswered(ask, nowMs, String(e));
7687
+ this.noteDepsUnanswered(ask, nowMs, String(e), depsFailureRetriable(classifyThrown(e).code));
7639
7688
  return;
7640
7689
  }
7641
7690
  if (res?.isError) {
7642
- this.noteDepsUnanswered(ask, nowMs, toolErrorText(res));
7691
+ const text = toolErrorText(res);
7692
+ this.noteDepsUnanswered(ask, nowMs, text, depsFailureRetriable(classifyToolFailure(text)));
7643
7693
  return;
7644
7694
  }
7645
7695
  const deps = res.structuredContent?.deps ?? [];
@@ -7679,6 +7729,7 @@ var SwarmClient = class _SwarmClient {
7679
7729
  depsRetryDelay(ref) {
7680
7730
  const fail = this.depsUnanswered.get(ref);
7681
7731
  if (fail) {
7732
+ if (!fail.retriable && fail.attempts >= _SwarmClient.DEPS_UNRETRIABLE_ATTEMPTS) return _SwarmClient.DEPS_TTL_MS;
7682
7733
  const backoff = _SwarmClient.DEPS_RETRY_BASE_MS * 2 ** Math.min(fail.attempts - 1, 10);
7683
7734
  return Math.min(backoff, _SwarmClient.DEPS_TTL_MS);
7684
7735
  }
@@ -7687,17 +7738,19 @@ var SwarmClient = class _SwarmClient {
7687
7738
  return _SwarmClient.DEPS_TTL_MS;
7688
7739
  }
7689
7740
  /** Запомнить, что прод по этим ссылкам не ответил (см. depsUnanswered / TASWM-486). */
7690
- noteDepsUnanswered(refs, nowMs, error) {
7741
+ noteDepsUnanswered(refs, nowMs, error, retriable = true) {
7691
7742
  for (const ref of refs) {
7692
7743
  const prev = this.depsUnanswered.get(ref);
7744
+ const sameKind = prev != null && prev.retriable === retriable;
7693
7745
  this.depsUnanswered.set(ref, {
7694
7746
  ref,
7695
7747
  slug: this.depSlug.get(ref) ?? ref,
7696
7748
  // `since` — начало ЭПИЗОДА слепоты, а не последняя попытка: именно возраст эпизода
7697
7749
  // отвечает на вопрос «сколько прогон уже стоит вслепую».
7698
7750
  since: prev?.since ?? nowMs,
7699
- attempts: (prev?.attempts ?? 0) + 1,
7700
- error
7751
+ attempts: sameKind ? prev.attempts + 1 : 1,
7752
+ error,
7753
+ retriable
7701
7754
  });
7702
7755
  }
7703
7756
  }
@@ -9241,13 +9294,13 @@ var SwarmClient = class _SwarmClient {
9241
9294
  }
9242
9295
  persistPending() {
9243
9296
  try {
9244
- writeFileSync4(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
9297
+ writeFileSync5(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
9245
9298
  } catch {
9246
9299
  }
9247
9300
  }
9248
9301
  persistMap() {
9249
9302
  try {
9250
- writeFileSync4(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
9303
+ writeFileSync5(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
9251
9304
  } catch {
9252
9305
  }
9253
9306
  }
@@ -9380,7 +9433,7 @@ init_config();
9380
9433
  import { $ as $6 } from "execa";
9381
9434
 
9382
9435
  // src/daemonLaunch.ts
9383
- import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync5 } from "fs";
9436
+ import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync6 } from "fs";
9384
9437
  import { spawn } from "child_process";
9385
9438
  var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
9386
9439
  function parseStartMode(argv) {
@@ -9440,14 +9493,14 @@ function launchDetached(opts) {
9440
9493
  }
9441
9494
  function tailFile(path, lines) {
9442
9495
  try {
9443
- return tailText(readFileSync5(path, "utf8"), lines);
9496
+ return tailText(readFileSync6(path, "utf8"), lines);
9444
9497
  } catch {
9445
9498
  return "";
9446
9499
  }
9447
9500
  }
9448
9501
 
9449
9502
  // src/logRotate.ts
9450
- import { copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync5, fstatSync, truncateSync, unlinkSync, existsSync as existsSync7 } from "fs";
9503
+ import { copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
9451
9504
  var realIo = {
9452
9505
  size: (path) => {
9453
9506
  try {
@@ -9459,7 +9512,7 @@ var realIo = {
9459
9512
  copy: copyFileSync2,
9460
9513
  rename: renameSync3,
9461
9514
  truncate: (path) => truncateSync(path, 0),
9462
- unlink: unlinkSync,
9515
+ unlink: unlinkSync2,
9463
9516
  exists: existsSync7
9464
9517
  };
9465
9518
  var STRAY_GENERATIONS = 8;
@@ -9488,17 +9541,17 @@ init_repoRegistry();
9488
9541
 
9489
9542
  // src/hostIdentity.ts
9490
9543
  import { hostname } from "os";
9491
- import { existsSync as existsSync8, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
9492
- import { join as join8 } from "path";
9544
+ import { existsSync as existsSync8, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
9545
+ import { join as join9 } from "path";
9493
9546
  import { randomUUID as randomUUID2 } from "crypto";
9494
9547
  function hostIdentityPath(home) {
9495
- return join8(home, "host.json");
9548
+ return join9(home, "host.json");
9496
9549
  }
9497
9550
  function loadHostIdentity(deps) {
9498
9551
  const exists = deps.exists ?? existsSync8;
9499
- const readFile2 = deps.readFile ?? ((p) => readFileSync6(p, "utf8"));
9552
+ const readFile2 = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
9500
9553
  const writeFile3 = deps.writeFile ?? ((p, data) => {
9501
- writeFileSync5(`${p}.tmp`, data);
9554
+ writeFileSync6(`${p}.tmp`, data);
9502
9555
  renameSync4(`${p}.tmp`, p);
9503
9556
  });
9504
9557
  const host = deps.hostname ?? hostname;
@@ -9614,7 +9667,7 @@ import { serve as serve2 } from "@hono/node-server";
9614
9667
 
9615
9668
  // src/diff.ts
9616
9669
  import { statSync as statSync6 } from "fs";
9617
- import { join as join9 } from "path";
9670
+ import { join as join10 } from "path";
9618
9671
  import { $ as $3 } from "execa";
9619
9672
  var MAX_FILES = 200;
9620
9673
  var MAX_LINES_PER_FILE = 1500;
@@ -9782,7 +9835,7 @@ async function collectRunDiff(worktree, opts = {}) {
9782
9835
  files.push(pf);
9783
9836
  }
9784
9837
  }
9785
- files.sort((a, b) => mtime(join9(worktree, b.path)) - mtime(join9(worktree, a.path)));
9838
+ files.sort((a, b) => mtime(join10(worktree, b.path)) - mtime(join10(worktree, a.path)));
9786
9839
  const truncatedFiles = files.length > MAX_FILES;
9787
9840
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
9788
9841
  const added = files.reduce((s, f) => s + f.added, 0);