@taskless-app/swarm 0.6.27 → 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.
- package/dist/cli.js +238 -198
- package/dist/overmind.js +195 -164
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2770,6 +2770,192 @@ var init_storm = __esm({
|
|
|
2770
2770
|
}
|
|
2771
2771
|
});
|
|
2772
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
|
+
|
|
2773
2959
|
// src/procList.ts
|
|
2774
2960
|
function parsePsRows(out) {
|
|
2775
2961
|
const rows = [];
|
|
@@ -3053,7 +3239,7 @@ var init_package = __esm({
|
|
|
3053
3239
|
"package.json"() {
|
|
3054
3240
|
package_default = {
|
|
3055
3241
|
name: "@taskless-app/swarm",
|
|
3056
|
-
version: "0.6.
|
|
3242
|
+
version: "0.6.28",
|
|
3057
3243
|
type: "module",
|
|
3058
3244
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
3059
3245
|
keywords: [
|
|
@@ -3104,7 +3290,7 @@ var init_package = __esm({
|
|
|
3104
3290
|
execa: "^10.0.0",
|
|
3105
3291
|
zod: "3.25.76"
|
|
3106
3292
|
},
|
|
3107
|
-
gitHead: "
|
|
3293
|
+
gitHead: "62910fc63850764f4562b7ec3bd65b44d3557050"
|
|
3108
3294
|
};
|
|
3109
3295
|
}
|
|
3110
3296
|
});
|
|
@@ -3236,18 +3422,18 @@ var init_netTimeout = __esm({
|
|
|
3236
3422
|
|
|
3237
3423
|
// src/droneChannel.ts
|
|
3238
3424
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3239
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as
|
|
3425
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
3240
3426
|
import { dirname as dirname3 } from "path";
|
|
3241
3427
|
import { serve } from "@hono/node-server";
|
|
3242
3428
|
function loadChannelSecret(path) {
|
|
3243
3429
|
try {
|
|
3244
|
-
const s =
|
|
3430
|
+
const s = readFileSync8(path, "utf8").trim();
|
|
3245
3431
|
if (s.length >= 32) return s;
|
|
3246
3432
|
} catch {
|
|
3247
3433
|
}
|
|
3248
3434
|
const secret = randomBytes(32).toString("hex");
|
|
3249
3435
|
mkdirSync7(dirname3(path), { recursive: true });
|
|
3250
|
-
|
|
3436
|
+
writeFileSync7(path, secret, { mode: 384 });
|
|
3251
3437
|
try {
|
|
3252
3438
|
chmodSync2(path, 384);
|
|
3253
3439
|
} catch {
|
|
@@ -3286,95 +3472,6 @@ var init_droneChannel = __esm({
|
|
|
3286
3472
|
}
|
|
3287
3473
|
});
|
|
3288
3474
|
|
|
3289
|
-
// src/agentToken.ts
|
|
3290
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
3291
|
-
import { join as join10 } from "path";
|
|
3292
|
-
function loadAgentToken() {
|
|
3293
|
-
try {
|
|
3294
|
-
const s = JSON.parse(readFileSync8(STORE_PATH, "utf8"));
|
|
3295
|
-
return s.token ? s : null;
|
|
3296
|
-
} catch {
|
|
3297
|
-
return null;
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
function saveAgentToken(s) {
|
|
3301
|
-
writeFileSync7(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
|
|
3302
|
-
}
|
|
3303
|
-
function clearAgentToken() {
|
|
3304
|
-
try {
|
|
3305
|
-
unlinkSync2(STORE_PATH);
|
|
3306
|
-
} catch {
|
|
3307
|
-
}
|
|
3308
|
-
}
|
|
3309
|
-
async function mintAgentToken(args, fetchFn = fetch) {
|
|
3310
|
-
const res = await fetchFn(`${base(args.api)}/api/swarm/agent/token`, {
|
|
3311
|
-
method: "POST",
|
|
3312
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
|
|
3313
|
-
body: JSON.stringify({
|
|
3314
|
-
...args.label ? { label: args.label } : {},
|
|
3315
|
-
...args.hostId ? { hostId: args.hostId } : {},
|
|
3316
|
-
...args.hostName ? { hostName: args.hostName } : {}
|
|
3317
|
-
})
|
|
3318
|
-
});
|
|
3319
|
-
const text = await res.text();
|
|
3320
|
-
if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
3321
|
-
const j = JSON.parse(text);
|
|
3322
|
-
if (!j.token) throw new Error("agent token: \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 token");
|
|
3323
|
-
return { token: j.token, ...j.tokenId ? { tokenId: j.tokenId } : {} };
|
|
3324
|
-
}
|
|
3325
|
-
async function probeAgentToken(args, fetchFn = fetch) {
|
|
3326
|
-
try {
|
|
3327
|
-
const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
|
|
3328
|
-
headers: { Authorization: `Bearer ${args.token}` },
|
|
3329
|
-
// Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
|
|
3330
|
-
// то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
|
|
3331
|
-
signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
|
|
3332
|
-
});
|
|
3333
|
-
if (res.status === 401 || res.status === 403) return "revoked";
|
|
3334
|
-
return res.ok ? "ok" : "unreachable";
|
|
3335
|
-
} catch {
|
|
3336
|
-
return "unreachable";
|
|
3337
|
-
}
|
|
3338
|
-
}
|
|
3339
|
-
function isRevokedTokenError(e) {
|
|
3340
|
-
if (e && typeof e === "object" && e.code === 401) return true;
|
|
3341
|
-
const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
|
|
3342
|
-
return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
|
|
3343
|
-
}
|
|
3344
|
-
function revokedTokenLine() {
|
|
3345
|
-
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";
|
|
3346
|
-
}
|
|
3347
|
-
function revokedTokenComplaint() {
|
|
3348
|
-
return [
|
|
3349
|
-
"",
|
|
3350
|
-
"\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",
|
|
3351
|
-
"\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",
|
|
3352
|
-
"\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",
|
|
3353
|
-
"\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:",
|
|
3354
|
-
" \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;",
|
|
3355
|
-
" \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;",
|
|
3356
|
-
" \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.",
|
|
3357
|
-
"",
|
|
3358
|
-
"\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",
|
|
3359
|
-
"\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.",
|
|
3360
|
-
"",
|
|
3361
|
-
"\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",
|
|
3362
|
-
"\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",
|
|
3363
|
-
"\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",
|
|
3364
|
-
""
|
|
3365
|
-
].join("\n");
|
|
3366
|
-
}
|
|
3367
|
-
var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
|
|
3368
|
-
var init_agentToken = __esm({
|
|
3369
|
-
"src/agentToken.ts"() {
|
|
3370
|
-
"use strict";
|
|
3371
|
-
init_config();
|
|
3372
|
-
STORE_PATH = join10(SWARM_HOME, "agent.json");
|
|
3373
|
-
base = (api) => api.replace(/\/$/, "");
|
|
3374
|
-
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
3375
|
-
}
|
|
3376
|
-
});
|
|
3377
|
-
|
|
3378
3475
|
// src/authState.ts
|
|
3379
3476
|
function noteTokenRejected(nowMs = Date.now()) {
|
|
3380
3477
|
if (!denial) {
|
|
@@ -3818,85 +3915,6 @@ var init_shotUpload = __esm({
|
|
|
3818
3915
|
}
|
|
3819
3916
|
});
|
|
3820
3917
|
|
|
3821
|
-
// src/linkFailure.ts
|
|
3822
|
-
function describeError(e, maxDepth = 4) {
|
|
3823
|
-
const parts = [];
|
|
3824
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3825
|
-
let cur = e;
|
|
3826
|
-
for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
|
|
3827
|
-
seen.add(cur);
|
|
3828
|
-
const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
|
|
3829
|
-
const code = cur.code;
|
|
3830
|
-
const codeText = code === void 0 || code === null ? "" : String(code);
|
|
3831
|
-
const label2 = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
|
|
3832
|
-
if (label2 && !parts.includes(label2)) parts.push(label2);
|
|
3833
|
-
cur = cur.cause;
|
|
3834
|
-
}
|
|
3835
|
-
return parts.join(" \u2190 ") || String(e);
|
|
3836
|
-
}
|
|
3837
|
-
function explainLinkFailure(e) {
|
|
3838
|
-
const detail = describeError(e);
|
|
3839
|
-
if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
|
|
3840
|
-
if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
|
|
3841
|
-
if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
|
|
3842
|
-
if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
|
|
3843
|
-
return { kind: "rejected", detail };
|
|
3844
|
-
}
|
|
3845
|
-
var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
|
|
3846
|
-
var init_linkFailure = __esm({
|
|
3847
|
-
"src/linkFailure.ts"() {
|
|
3848
|
-
"use strict";
|
|
3849
|
-
init_agentToken();
|
|
3850
|
-
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";
|
|
3851
|
-
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;
|
|
3852
|
-
MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
|
|
3853
|
-
}
|
|
3854
|
-
});
|
|
3855
|
-
|
|
3856
|
-
// src/toolFailure.ts
|
|
3857
|
-
var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE;
|
|
3858
|
-
var init_toolFailure = __esm({
|
|
3859
|
-
"src/toolFailure.ts"() {
|
|
3860
|
-
"use strict";
|
|
3861
|
-
init_linkFailure();
|
|
3862
|
-
TOOL_FAILURE = {
|
|
3863
|
-
/** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
|
|
3864
|
-
NOT_FOUND: "not_found",
|
|
3865
|
-
/** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
|
|
3866
|
-
FORBIDDEN: "forbidden",
|
|
3867
|
-
/** Токен роя не состоит в воркспейсе, которым искали. */
|
|
3868
|
-
WORKSPACE_MISMATCH: "workspace_mismatch",
|
|
3869
|
-
/** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
|
|
3870
|
-
UNREACHABLE: "unreachable",
|
|
3871
|
-
/** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
|
|
3872
|
-
UNAUTHORIZED: "unauthorized",
|
|
3873
|
-
/** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
|
|
3874
|
-
TOOL_ERROR: "tool_error",
|
|
3875
|
-
/** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
|
|
3876
|
-
EMPTY: "empty"
|
|
3877
|
-
};
|
|
3878
|
-
TOOL_FAILURE_TEXT = {
|
|
3879
|
-
[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",
|
|
3880
|
-
[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",
|
|
3881
|
-
[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",
|
|
3882
|
-
[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",
|
|
3883
|
-
[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",
|
|
3884
|
-
[TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
|
|
3885
|
-
[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"
|
|
3886
|
-
};
|
|
3887
|
-
TOOL_FAILURE_RETRIABLE = {
|
|
3888
|
-
[TOOL_FAILURE.NOT_FOUND]: false,
|
|
3889
|
-
[TOOL_FAILURE.FORBIDDEN]: false,
|
|
3890
|
-
[TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
|
|
3891
|
-
[TOOL_FAILURE.UNREACHABLE]: true,
|
|
3892
|
-
// Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
|
|
3893
|
-
[TOOL_FAILURE.UNAUTHORIZED]: false,
|
|
3894
|
-
[TOOL_FAILURE.TOOL_ERROR]: false,
|
|
3895
|
-
[TOOL_FAILURE.EMPTY]: false
|
|
3896
|
-
};
|
|
3897
|
-
}
|
|
3898
|
-
});
|
|
3899
|
-
|
|
3900
3918
|
// src/workspace.ts
|
|
3901
3919
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
|
|
3902
3920
|
import { join as join17 } from "path";
|
|
@@ -6879,7 +6897,7 @@ init_config();
|
|
|
6879
6897
|
init_resources();
|
|
6880
6898
|
init_names2();
|
|
6881
6899
|
init_storm();
|
|
6882
|
-
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as
|
|
6900
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
6883
6901
|
import { dirname as dirname2 } from "path";
|
|
6884
6902
|
import { randomUUID } from "crypto";
|
|
6885
6903
|
|
|
@@ -6906,20 +6924,23 @@ function parsePendingRepoRules(raw) {
|
|
|
6906
6924
|
return out;
|
|
6907
6925
|
}
|
|
6908
6926
|
|
|
6927
|
+
// src/swarmClient.ts
|
|
6928
|
+
init_toolFailure();
|
|
6929
|
+
|
|
6909
6930
|
// src/logTail.ts
|
|
6910
6931
|
import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
6911
|
-
import { join as
|
|
6932
|
+
import { join as join8 } from "path";
|
|
6912
6933
|
|
|
6913
6934
|
// src/runLogDir.ts
|
|
6914
6935
|
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, statSync as statSync3 } from "fs";
|
|
6915
|
-
import { join as
|
|
6936
|
+
import { join as join7 } from "path";
|
|
6916
6937
|
function runLogDirName(runLogKey) {
|
|
6917
6938
|
const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
6918
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)}`);
|
|
6919
6940
|
return /^\d+$/.test(safe) ? `k-${safe}` : safe;
|
|
6920
6941
|
}
|
|
6921
6942
|
function runLogDirPath(runLogsDir, runLogKey) {
|
|
6922
|
-
return
|
|
6943
|
+
return join7(runLogsDir, runLogDirName(runLogKey));
|
|
6923
6944
|
}
|
|
6924
6945
|
function findRunLogDir(runLogsDir, keys) {
|
|
6925
6946
|
for (const key of keys) {
|
|
@@ -6947,6 +6968,14 @@ function toolErrorText(res) {
|
|
|
6947
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";
|
|
6948
6969
|
return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
|
|
6949
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
|
+
}
|
|
6950
6979
|
function runRef(runKey) {
|
|
6951
6980
|
return { __runRef: runKey };
|
|
6952
6981
|
}
|
|
@@ -7043,6 +7072,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7043
7072
|
// дальше удвоением до потолка TTL: устойчиво отвергающий прод (старее демона, скоуп
|
|
7044
7073
|
// токена) не должен получать вопрос каждые четыре секунды до конца своей жизни.
|
|
7045
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;
|
|
7046
7082
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
7047
7083
|
// ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
|
|
7048
7084
|
// каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
|
|
@@ -7114,13 +7150,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7114
7150
|
recover() {
|
|
7115
7151
|
if (existsSync6(this.mapPath)) {
|
|
7116
7152
|
try {
|
|
7117
|
-
const obj = JSON.parse(
|
|
7153
|
+
const obj = JSON.parse(readFileSync5(this.mapPath, "utf8"));
|
|
7118
7154
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
7119
7155
|
} catch {
|
|
7120
7156
|
}
|
|
7121
7157
|
}
|
|
7122
7158
|
if (existsSync6(this.outboxPath)) {
|
|
7123
|
-
const txt =
|
|
7159
|
+
const txt = readFileSync5(this.outboxPath, "utf8");
|
|
7124
7160
|
for (const line of txt.split("\n")) {
|
|
7125
7161
|
const s = line.trim();
|
|
7126
7162
|
if (!s) continue;
|
|
@@ -7143,7 +7179,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7143
7179
|
if (!existsSync6(this.stormPath)) return;
|
|
7144
7180
|
let txt;
|
|
7145
7181
|
try {
|
|
7146
|
-
txt =
|
|
7182
|
+
txt = readFileSync5(this.stormPath, "utf8");
|
|
7147
7183
|
} catch {
|
|
7148
7184
|
return;
|
|
7149
7185
|
}
|
|
@@ -7648,11 +7684,12 @@ var SwarmClient = class _SwarmClient {
|
|
|
7648
7684
|
res = await this.call("swarm_deps_status", { refs: ask });
|
|
7649
7685
|
} catch (e) {
|
|
7650
7686
|
for (const ref of ask) this.depsAskedAt.delete(ref);
|
|
7651
|
-
this.noteDepsUnanswered(ask, nowMs, String(e));
|
|
7687
|
+
this.noteDepsUnanswered(ask, nowMs, String(e), depsFailureRetriable(classifyThrown(e).code));
|
|
7652
7688
|
return;
|
|
7653
7689
|
}
|
|
7654
7690
|
if (res?.isError) {
|
|
7655
|
-
|
|
7691
|
+
const text = toolErrorText(res);
|
|
7692
|
+
this.noteDepsUnanswered(ask, nowMs, text, depsFailureRetriable(classifyToolFailure(text)));
|
|
7656
7693
|
return;
|
|
7657
7694
|
}
|
|
7658
7695
|
const deps = res.structuredContent?.deps ?? [];
|
|
@@ -7692,6 +7729,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7692
7729
|
depsRetryDelay(ref) {
|
|
7693
7730
|
const fail = this.depsUnanswered.get(ref);
|
|
7694
7731
|
if (fail) {
|
|
7732
|
+
if (!fail.retriable && fail.attempts >= _SwarmClient.DEPS_UNRETRIABLE_ATTEMPTS) return _SwarmClient.DEPS_TTL_MS;
|
|
7695
7733
|
const backoff = _SwarmClient.DEPS_RETRY_BASE_MS * 2 ** Math.min(fail.attempts - 1, 10);
|
|
7696
7734
|
return Math.min(backoff, _SwarmClient.DEPS_TTL_MS);
|
|
7697
7735
|
}
|
|
@@ -7700,17 +7738,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
7700
7738
|
return _SwarmClient.DEPS_TTL_MS;
|
|
7701
7739
|
}
|
|
7702
7740
|
/** Запомнить, что прод по этим ссылкам не ответил (см. depsUnanswered / TASWM-486). */
|
|
7703
|
-
noteDepsUnanswered(refs, nowMs, error) {
|
|
7741
|
+
noteDepsUnanswered(refs, nowMs, error, retriable = true) {
|
|
7704
7742
|
for (const ref of refs) {
|
|
7705
7743
|
const prev = this.depsUnanswered.get(ref);
|
|
7744
|
+
const sameKind = prev != null && prev.retriable === retriable;
|
|
7706
7745
|
this.depsUnanswered.set(ref, {
|
|
7707
7746
|
ref,
|
|
7708
7747
|
slug: this.depSlug.get(ref) ?? ref,
|
|
7709
7748
|
// `since` — начало ЭПИЗОДА слепоты, а не последняя попытка: именно возраст эпизода
|
|
7710
7749
|
// отвечает на вопрос «сколько прогон уже стоит вслепую».
|
|
7711
7750
|
since: prev?.since ?? nowMs,
|
|
7712
|
-
attempts:
|
|
7713
|
-
error
|
|
7751
|
+
attempts: sameKind ? prev.attempts + 1 : 1,
|
|
7752
|
+
error,
|
|
7753
|
+
retriable
|
|
7714
7754
|
});
|
|
7715
7755
|
}
|
|
7716
7756
|
}
|
|
@@ -9254,13 +9294,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
9254
9294
|
}
|
|
9255
9295
|
persistPending() {
|
|
9256
9296
|
try {
|
|
9257
|
-
|
|
9297
|
+
writeFileSync5(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
|
|
9258
9298
|
} catch {
|
|
9259
9299
|
}
|
|
9260
9300
|
}
|
|
9261
9301
|
persistMap() {
|
|
9262
9302
|
try {
|
|
9263
|
-
|
|
9303
|
+
writeFileSync5(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
|
|
9264
9304
|
} catch {
|
|
9265
9305
|
}
|
|
9266
9306
|
}
|
|
@@ -9393,7 +9433,7 @@ init_config();
|
|
|
9393
9433
|
import { $ as $6 } from "execa";
|
|
9394
9434
|
|
|
9395
9435
|
// src/daemonLaunch.ts
|
|
9396
|
-
import { openSync as openSync2, closeSync as closeSync2, readFileSync as
|
|
9436
|
+
import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync6 } from "fs";
|
|
9397
9437
|
import { spawn } from "child_process";
|
|
9398
9438
|
var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
|
|
9399
9439
|
function parseStartMode(argv) {
|
|
@@ -9453,14 +9493,14 @@ function launchDetached(opts) {
|
|
|
9453
9493
|
}
|
|
9454
9494
|
function tailFile(path, lines) {
|
|
9455
9495
|
try {
|
|
9456
|
-
return tailText(
|
|
9496
|
+
return tailText(readFileSync6(path, "utf8"), lines);
|
|
9457
9497
|
} catch {
|
|
9458
9498
|
return "";
|
|
9459
9499
|
}
|
|
9460
9500
|
}
|
|
9461
9501
|
|
|
9462
9502
|
// src/logRotate.ts
|
|
9463
|
-
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";
|
|
9464
9504
|
var realIo = {
|
|
9465
9505
|
size: (path) => {
|
|
9466
9506
|
try {
|
|
@@ -9472,7 +9512,7 @@ var realIo = {
|
|
|
9472
9512
|
copy: copyFileSync2,
|
|
9473
9513
|
rename: renameSync3,
|
|
9474
9514
|
truncate: (path) => truncateSync(path, 0),
|
|
9475
|
-
unlink:
|
|
9515
|
+
unlink: unlinkSync2,
|
|
9476
9516
|
exists: existsSync7
|
|
9477
9517
|
};
|
|
9478
9518
|
var STRAY_GENERATIONS = 8;
|
|
@@ -9501,17 +9541,17 @@ init_repoRegistry();
|
|
|
9501
9541
|
|
|
9502
9542
|
// src/hostIdentity.ts
|
|
9503
9543
|
import { hostname } from "os";
|
|
9504
|
-
import { existsSync as existsSync8, readFileSync as
|
|
9505
|
-
import { join as
|
|
9544
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
9545
|
+
import { join as join9 } from "path";
|
|
9506
9546
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9507
9547
|
function hostIdentityPath(home) {
|
|
9508
|
-
return
|
|
9548
|
+
return join9(home, "host.json");
|
|
9509
9549
|
}
|
|
9510
9550
|
function loadHostIdentity(deps) {
|
|
9511
9551
|
const exists = deps.exists ?? existsSync8;
|
|
9512
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
9552
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
|
|
9513
9553
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
9514
|
-
|
|
9554
|
+
writeFileSync6(`${p}.tmp`, data);
|
|
9515
9555
|
renameSync4(`${p}.tmp`, p);
|
|
9516
9556
|
});
|
|
9517
9557
|
const host = deps.hostname ?? hostname;
|
|
@@ -9627,7 +9667,7 @@ import { serve as serve2 } from "@hono/node-server";
|
|
|
9627
9667
|
|
|
9628
9668
|
// src/diff.ts
|
|
9629
9669
|
import { statSync as statSync6 } from "fs";
|
|
9630
|
-
import { join as
|
|
9670
|
+
import { join as join10 } from "path";
|
|
9631
9671
|
import { $ as $3 } from "execa";
|
|
9632
9672
|
var MAX_FILES = 200;
|
|
9633
9673
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -9795,7 +9835,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9795
9835
|
files.push(pf);
|
|
9796
9836
|
}
|
|
9797
9837
|
}
|
|
9798
|
-
files.sort((a, b) => mtime(
|
|
9838
|
+
files.sort((a, b) => mtime(join10(worktree, b.path)) - mtime(join10(worktree, a.path)));
|
|
9799
9839
|
const truncatedFiles = files.length > MAX_FILES;
|
|
9800
9840
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
9801
9841
|
const added = files.reduce((s, f) => s + f.added, 0);
|
package/dist/overmind.js
CHANGED
|
@@ -3092,76 +3092,12 @@ var init_storm = __esm({
|
|
|
3092
3092
|
}
|
|
3093
3093
|
});
|
|
3094
3094
|
|
|
3095
|
-
// src/heartbeat.ts
|
|
3096
|
-
import { readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3097
|
-
function writeHeartbeat(path, now) {
|
|
3098
|
-
const tmp = `${path}.tmp`;
|
|
3099
|
-
writeFileSync6(tmp, String(now));
|
|
3100
|
-
renameSync4(tmp, path);
|
|
3101
|
-
}
|
|
3102
|
-
function readHeartbeat(path) {
|
|
3103
|
-
try {
|
|
3104
|
-
const ms = Number(readFileSync6(path, "utf8").trim());
|
|
3105
|
-
return Number.isFinite(ms) ? ms : null;
|
|
3106
|
-
} catch {
|
|
3107
|
-
return null;
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
function overmindAlive(path, now, staleMs) {
|
|
3111
|
-
const ts = readHeartbeat(path);
|
|
3112
|
-
if (ts == null) return false;
|
|
3113
|
-
return now - ts < staleMs;
|
|
3114
|
-
}
|
|
3115
|
-
var init_heartbeat = __esm({
|
|
3116
|
-
"src/heartbeat.ts"() {
|
|
3117
|
-
"use strict";
|
|
3118
|
-
}
|
|
3119
|
-
});
|
|
3120
|
-
|
|
3121
|
-
// src/logNoise.ts
|
|
3122
|
-
function timesWord(n) {
|
|
3123
|
-
const tens = n % 100;
|
|
3124
|
-
if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
|
|
3125
|
-
return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
|
|
3126
|
-
}
|
|
3127
|
-
function makeRepeatFilter(opts = {}) {
|
|
3128
|
-
const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
|
|
3129
|
-
const streaks = /* @__PURE__ */ new Map();
|
|
3130
|
-
return {
|
|
3131
|
-
say(key, message, now) {
|
|
3132
|
-
const prev = streaks.get(key);
|
|
3133
|
-
if (!prev || prev.message !== message) {
|
|
3134
|
-
const tail2 = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
|
|
3135
|
-
streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
|
|
3136
|
-
return `${message}${tail2}`;
|
|
3137
|
-
}
|
|
3138
|
-
prev.hidden += 1;
|
|
3139
|
-
if (now - prev.saidAt < remindAfterMs) return null;
|
|
3140
|
-
const hidden = prev.hidden;
|
|
3141
|
-
const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
|
|
3142
|
-
prev.saidAt = now;
|
|
3143
|
-
prev.hidden = 0;
|
|
3144
|
-
return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
|
|
3145
|
-
},
|
|
3146
|
-
forget(key) {
|
|
3147
|
-
streaks.delete(key);
|
|
3148
|
-
}
|
|
3149
|
-
};
|
|
3150
|
-
}
|
|
3151
|
-
var DEFAULT_REMIND_MS;
|
|
3152
|
-
var init_logNoise = __esm({
|
|
3153
|
-
"src/logNoise.ts"() {
|
|
3154
|
-
"use strict";
|
|
3155
|
-
DEFAULT_REMIND_MS = 10 * 6e4;
|
|
3156
|
-
}
|
|
3157
|
-
});
|
|
3158
|
-
|
|
3159
3095
|
// src/agentToken.ts
|
|
3160
|
-
import { readFileSync as
|
|
3161
|
-
import { join as
|
|
3096
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
3097
|
+
import { join as join7 } from "path";
|
|
3162
3098
|
function loadAgentToken() {
|
|
3163
3099
|
try {
|
|
3164
|
-
const s = JSON.parse(
|
|
3100
|
+
const s = JSON.parse(readFileSync5(STORE_PATH, "utf8"));
|
|
3165
3101
|
return s.token ? s : null;
|
|
3166
3102
|
} catch {
|
|
3167
3103
|
return null;
|
|
@@ -3214,7 +3150,7 @@ var init_agentToken = __esm({
|
|
|
3214
3150
|
"src/agentToken.ts"() {
|
|
3215
3151
|
"use strict";
|
|
3216
3152
|
init_config();
|
|
3217
|
-
STORE_PATH =
|
|
3153
|
+
STORE_PATH = join7(SWARM_HOME, "agent.json");
|
|
3218
3154
|
base = (api) => api.replace(/\/$/, "");
|
|
3219
3155
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
3220
3156
|
}
|
|
@@ -3255,6 +3191,142 @@ var init_linkFailure = __esm({
|
|
|
3255
3191
|
}
|
|
3256
3192
|
});
|
|
3257
3193
|
|
|
3194
|
+
// src/toolFailure.ts
|
|
3195
|
+
function trimDetail(s) {
|
|
3196
|
+
const t = s.trim();
|
|
3197
|
+
return t.length <= FAILURE_DETAIL_MAX ? t : `${t.slice(0, FAILURE_DETAIL_MAX)}\u2026`;
|
|
3198
|
+
}
|
|
3199
|
+
function classifyToolFailure(raw) {
|
|
3200
|
+
if (MISMATCH_RE.test(raw)) return TOOL_FAILURE.WORKSPACE_MISMATCH;
|
|
3201
|
+
if (FORBIDDEN_RE.test(raw)) return TOOL_FAILURE.FORBIDDEN;
|
|
3202
|
+
if (NOT_FOUND_RE.test(raw)) return TOOL_FAILURE.NOT_FOUND;
|
|
3203
|
+
return TOOL_FAILURE.TOOL_ERROR;
|
|
3204
|
+
}
|
|
3205
|
+
function classifyThrown(e) {
|
|
3206
|
+
const { kind, detail } = explainLinkFailure(e);
|
|
3207
|
+
const code = kind === "offline" ? TOOL_FAILURE.UNREACHABLE : kind === "unauthorized" ? TOOL_FAILURE.UNAUTHORIZED : (
|
|
3208
|
+
// «ручки нет» и «сервер отказал по существу» разбираем доменно: там может быть
|
|
3209
|
+
// и task_not_found, и отказ по правам — то есть наши коды, а не транспортные.
|
|
3210
|
+
classifyToolFailure(detail)
|
|
3211
|
+
);
|
|
3212
|
+
return { code, detail };
|
|
3213
|
+
}
|
|
3214
|
+
function describeToolFailure(f) {
|
|
3215
|
+
const text = TOOL_FAILURE_TEXT[f.code];
|
|
3216
|
+
const detail = trimDetail(f.detail);
|
|
3217
|
+
return detail ? `${text} (${detail})` : text;
|
|
3218
|
+
}
|
|
3219
|
+
var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE, FAILURE_DETAIL_MAX, NOT_FOUND_RE, FORBIDDEN_RE, MISMATCH_RE;
|
|
3220
|
+
var init_toolFailure = __esm({
|
|
3221
|
+
"src/toolFailure.ts"() {
|
|
3222
|
+
"use strict";
|
|
3223
|
+
init_linkFailure();
|
|
3224
|
+
TOOL_FAILURE = {
|
|
3225
|
+
/** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
|
|
3226
|
+
NOT_FOUND: "not_found",
|
|
3227
|
+
/** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
|
|
3228
|
+
FORBIDDEN: "forbidden",
|
|
3229
|
+
/** Токен роя не состоит в воркспейсе, которым искали. */
|
|
3230
|
+
WORKSPACE_MISMATCH: "workspace_mismatch",
|
|
3231
|
+
/** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
|
|
3232
|
+
UNREACHABLE: "unreachable",
|
|
3233
|
+
/** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
|
|
3234
|
+
UNAUTHORIZED: "unauthorized",
|
|
3235
|
+
/** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
|
|
3236
|
+
TOOL_ERROR: "tool_error",
|
|
3237
|
+
/** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
|
|
3238
|
+
EMPTY: "empty"
|
|
3239
|
+
};
|
|
3240
|
+
TOOL_FAILURE_TEXT = {
|
|
3241
|
+
[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",
|
|
3242
|
+
[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",
|
|
3243
|
+
[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",
|
|
3244
|
+
[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",
|
|
3245
|
+
[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",
|
|
3246
|
+
[TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
|
|
3247
|
+
[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"
|
|
3248
|
+
};
|
|
3249
|
+
TOOL_FAILURE_RETRIABLE = {
|
|
3250
|
+
[TOOL_FAILURE.NOT_FOUND]: false,
|
|
3251
|
+
[TOOL_FAILURE.FORBIDDEN]: false,
|
|
3252
|
+
[TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
|
|
3253
|
+
[TOOL_FAILURE.UNREACHABLE]: true,
|
|
3254
|
+
// Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
|
|
3255
|
+
[TOOL_FAILURE.UNAUTHORIZED]: false,
|
|
3256
|
+
[TOOL_FAILURE.TOOL_ERROR]: false,
|
|
3257
|
+
[TOOL_FAILURE.EMPTY]: false
|
|
3258
|
+
};
|
|
3259
|
+
FAILURE_DETAIL_MAX = 300;
|
|
3260
|
+
NOT_FOUND_RE = /task_not_found|not_found|не найдена|не найден/i;
|
|
3261
|
+
FORBIDDEN_RE = /forbidden|not visible|no access|нет доступа|(?:http|status|code)[ =:]*403\b|\(403\)|403 forbidden/i;
|
|
3262
|
+
MISMATCH_RE = /workspace_mismatch|not a member of that workspace/i;
|
|
3263
|
+
}
|
|
3264
|
+
});
|
|
3265
|
+
|
|
3266
|
+
// src/heartbeat.ts
|
|
3267
|
+
import { readFileSync as readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
3268
|
+
function writeHeartbeat(path, now) {
|
|
3269
|
+
const tmp = `${path}.tmp`;
|
|
3270
|
+
writeFileSync7(tmp, String(now));
|
|
3271
|
+
renameSync4(tmp, path);
|
|
3272
|
+
}
|
|
3273
|
+
function readHeartbeat(path) {
|
|
3274
|
+
try {
|
|
3275
|
+
const ms = Number(readFileSync7(path, "utf8").trim());
|
|
3276
|
+
return Number.isFinite(ms) ? ms : null;
|
|
3277
|
+
} catch {
|
|
3278
|
+
return null;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
function overmindAlive(path, now, staleMs) {
|
|
3282
|
+
const ts = readHeartbeat(path);
|
|
3283
|
+
if (ts == null) return false;
|
|
3284
|
+
return now - ts < staleMs;
|
|
3285
|
+
}
|
|
3286
|
+
var init_heartbeat = __esm({
|
|
3287
|
+
"src/heartbeat.ts"() {
|
|
3288
|
+
"use strict";
|
|
3289
|
+
}
|
|
3290
|
+
});
|
|
3291
|
+
|
|
3292
|
+
// src/logNoise.ts
|
|
3293
|
+
function timesWord(n) {
|
|
3294
|
+
const tens = n % 100;
|
|
3295
|
+
if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
|
|
3296
|
+
return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
|
|
3297
|
+
}
|
|
3298
|
+
function makeRepeatFilter(opts = {}) {
|
|
3299
|
+
const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
|
|
3300
|
+
const streaks = /* @__PURE__ */ new Map();
|
|
3301
|
+
return {
|
|
3302
|
+
say(key, message, now) {
|
|
3303
|
+
const prev = streaks.get(key);
|
|
3304
|
+
if (!prev || prev.message !== message) {
|
|
3305
|
+
const tail2 = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
|
|
3306
|
+
streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
|
|
3307
|
+
return `${message}${tail2}`;
|
|
3308
|
+
}
|
|
3309
|
+
prev.hidden += 1;
|
|
3310
|
+
if (now - prev.saidAt < remindAfterMs) return null;
|
|
3311
|
+
const hidden = prev.hidden;
|
|
3312
|
+
const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
|
|
3313
|
+
prev.saidAt = now;
|
|
3314
|
+
prev.hidden = 0;
|
|
3315
|
+
return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
|
|
3316
|
+
},
|
|
3317
|
+
forget(key) {
|
|
3318
|
+
streaks.delete(key);
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
var DEFAULT_REMIND_MS;
|
|
3323
|
+
var init_logNoise = __esm({
|
|
3324
|
+
"src/logNoise.ts"() {
|
|
3325
|
+
"use strict";
|
|
3326
|
+
DEFAULT_REMIND_MS = 10 * 6e4;
|
|
3327
|
+
}
|
|
3328
|
+
});
|
|
3329
|
+
|
|
3258
3330
|
// src/procList.ts
|
|
3259
3331
|
function parsePsRows(out) {
|
|
3260
3332
|
const rows = [];
|
|
@@ -4904,78 +4976,6 @@ var init_oauth = __esm({
|
|
|
4904
4976
|
}
|
|
4905
4977
|
});
|
|
4906
4978
|
|
|
4907
|
-
// src/toolFailure.ts
|
|
4908
|
-
function trimDetail(s) {
|
|
4909
|
-
const t = s.trim();
|
|
4910
|
-
return t.length <= FAILURE_DETAIL_MAX ? t : `${t.slice(0, FAILURE_DETAIL_MAX)}\u2026`;
|
|
4911
|
-
}
|
|
4912
|
-
function classifyToolFailure(raw) {
|
|
4913
|
-
if (MISMATCH_RE.test(raw)) return TOOL_FAILURE.WORKSPACE_MISMATCH;
|
|
4914
|
-
if (FORBIDDEN_RE.test(raw)) return TOOL_FAILURE.FORBIDDEN;
|
|
4915
|
-
if (NOT_FOUND_RE.test(raw)) return TOOL_FAILURE.NOT_FOUND;
|
|
4916
|
-
return TOOL_FAILURE.TOOL_ERROR;
|
|
4917
|
-
}
|
|
4918
|
-
function classifyThrown(e) {
|
|
4919
|
-
const { kind, detail } = explainLinkFailure(e);
|
|
4920
|
-
const code = kind === "offline" ? TOOL_FAILURE.UNREACHABLE : kind === "unauthorized" ? TOOL_FAILURE.UNAUTHORIZED : (
|
|
4921
|
-
// «ручки нет» и «сервер отказал по существу» разбираем доменно: там может быть
|
|
4922
|
-
// и task_not_found, и отказ по правам — то есть наши коды, а не транспортные.
|
|
4923
|
-
classifyToolFailure(detail)
|
|
4924
|
-
);
|
|
4925
|
-
return { code, detail };
|
|
4926
|
-
}
|
|
4927
|
-
function describeToolFailure(f) {
|
|
4928
|
-
const text = TOOL_FAILURE_TEXT[f.code];
|
|
4929
|
-
const detail = trimDetail(f.detail);
|
|
4930
|
-
return detail ? `${text} (${detail})` : text;
|
|
4931
|
-
}
|
|
4932
|
-
var TOOL_FAILURE, TOOL_FAILURE_TEXT, TOOL_FAILURE_RETRIABLE, FAILURE_DETAIL_MAX, NOT_FOUND_RE, FORBIDDEN_RE, MISMATCH_RE;
|
|
4933
|
-
var init_toolFailure = __esm({
|
|
4934
|
-
"src/toolFailure.ts"() {
|
|
4935
|
-
"use strict";
|
|
4936
|
-
init_linkFailure();
|
|
4937
|
-
TOOL_FAILURE = {
|
|
4938
|
-
/** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
|
|
4939
|
-
NOT_FOUND: "not_found",
|
|
4940
|
-
/** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
|
|
4941
|
-
FORBIDDEN: "forbidden",
|
|
4942
|
-
/** Токен роя не состоит в воркспейсе, которым искали. */
|
|
4943
|
-
WORKSPACE_MISMATCH: "workspace_mismatch",
|
|
4944
|
-
/** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
|
|
4945
|
-
UNREACHABLE: "unreachable",
|
|
4946
|
-
/** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
|
|
4947
|
-
UNAUTHORIZED: "unauthorized",
|
|
4948
|
-
/** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
|
|
4949
|
-
TOOL_ERROR: "tool_error",
|
|
4950
|
-
/** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
|
|
4951
|
-
EMPTY: "empty"
|
|
4952
|
-
};
|
|
4953
|
-
TOOL_FAILURE_TEXT = {
|
|
4954
|
-
[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",
|
|
4955
|
-
[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",
|
|
4956
|
-
[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",
|
|
4957
|
-
[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",
|
|
4958
|
-
[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",
|
|
4959
|
-
[TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
|
|
4960
|
-
[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"
|
|
4961
|
-
};
|
|
4962
|
-
TOOL_FAILURE_RETRIABLE = {
|
|
4963
|
-
[TOOL_FAILURE.NOT_FOUND]: false,
|
|
4964
|
-
[TOOL_FAILURE.FORBIDDEN]: false,
|
|
4965
|
-
[TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
|
|
4966
|
-
[TOOL_FAILURE.UNREACHABLE]: true,
|
|
4967
|
-
// Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
|
|
4968
|
-
[TOOL_FAILURE.UNAUTHORIZED]: false,
|
|
4969
|
-
[TOOL_FAILURE.TOOL_ERROR]: false,
|
|
4970
|
-
[TOOL_FAILURE.EMPTY]: false
|
|
4971
|
-
};
|
|
4972
|
-
FAILURE_DETAIL_MAX = 300;
|
|
4973
|
-
NOT_FOUND_RE = /task_not_found|not_found|не найдена|не найден/i;
|
|
4974
|
-
FORBIDDEN_RE = /forbidden|not visible|no access|нет доступа|(?:http|status|code)[ =:]*403\b|\(403\)|403 forbidden/i;
|
|
4975
|
-
MISMATCH_RE = /workspace_mismatch|not a member of that workspace/i;
|
|
4976
|
-
}
|
|
4977
|
-
});
|
|
4978
|
-
|
|
4979
4979
|
// src/authState.ts
|
|
4980
4980
|
function noteTokenRejected(nowMs = Date.now()) {
|
|
4981
4981
|
if (!denial) {
|
|
@@ -6689,7 +6689,7 @@ init_config();
|
|
|
6689
6689
|
init_resources();
|
|
6690
6690
|
init_names2();
|
|
6691
6691
|
init_storm();
|
|
6692
|
-
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as
|
|
6692
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
6693
6693
|
import { dirname as dirname2 } from "path";
|
|
6694
6694
|
import { randomUUID } from "crypto";
|
|
6695
6695
|
|
|
@@ -6741,13 +6741,16 @@ function renderPendingRulesBlock(rules) {
|
|
|
6741
6741
|
].join("\n");
|
|
6742
6742
|
}
|
|
6743
6743
|
|
|
6744
|
+
// src/swarmClient.ts
|
|
6745
|
+
init_toolFailure();
|
|
6746
|
+
|
|
6744
6747
|
// src/logTail.ts
|
|
6745
6748
|
import { closeSync, existsSync as existsSync6, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
6746
|
-
import { join as
|
|
6749
|
+
import { join as join9 } from "path";
|
|
6747
6750
|
|
|
6748
6751
|
// src/runLogDir.ts
|
|
6749
6752
|
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
6750
|
-
import { join as
|
|
6753
|
+
import { join as join8 } from "path";
|
|
6751
6754
|
var LEGACY_RUN_LOGS_SUBDIR = "legacy";
|
|
6752
6755
|
function runLogDirName(runLogKey) {
|
|
6753
6756
|
const safe2 = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
@@ -6755,7 +6758,7 @@ function runLogDirName(runLogKey) {
|
|
|
6755
6758
|
return /^\d+$/.test(safe2) ? `k-${safe2}` : safe2;
|
|
6756
6759
|
}
|
|
6757
6760
|
function runLogDirPath(runLogsDir, runLogKey) {
|
|
6758
|
-
return
|
|
6761
|
+
return join8(runLogsDir, runLogDirName(runLogKey));
|
|
6759
6762
|
}
|
|
6760
6763
|
function findRunLogDir(runLogsDir, keys) {
|
|
6761
6764
|
for (const key of keys) {
|
|
@@ -6770,7 +6773,7 @@ function runLogDirForWrite(runLogsDir, keys) {
|
|
|
6770
6773
|
return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
|
|
6771
6774
|
}
|
|
6772
6775
|
function sweepLegacyRunLogs(runLogsDir) {
|
|
6773
|
-
const legacyDir =
|
|
6776
|
+
const legacyDir = join8(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
|
|
6774
6777
|
let entries;
|
|
6775
6778
|
try {
|
|
6776
6779
|
entries = readdirSync4(runLogsDir);
|
|
@@ -6781,12 +6784,12 @@ function sweepLegacyRunLogs(runLogsDir) {
|
|
|
6781
6784
|
let failed = 0;
|
|
6782
6785
|
for (const name of entries) {
|
|
6783
6786
|
if (!/^\d+$/.test(name)) continue;
|
|
6784
|
-
const from =
|
|
6787
|
+
const from = join8(runLogsDir, name);
|
|
6785
6788
|
try {
|
|
6786
6789
|
if (!statSync3(from).isDirectory()) continue;
|
|
6787
6790
|
mkdirSync5(legacyDir, { recursive: true });
|
|
6788
|
-
let to =
|
|
6789
|
-
for (let n = 2; existsSync5(to) && n < 100; n++) to =
|
|
6791
|
+
let to = join8(legacyDir, name);
|
|
6792
|
+
for (let n = 2; existsSync5(to) && n < 100; n++) to = join8(legacyDir, `${name}-${n}`);
|
|
6790
6793
|
renameSync3(from, to);
|
|
6791
6794
|
moved++;
|
|
6792
6795
|
} catch {
|
|
@@ -6852,8 +6855,8 @@ function latestRunLaunch(runLogsDir, runLogKeys) {
|
|
|
6852
6855
|
}
|
|
6853
6856
|
if (indexes.size === 0) return null;
|
|
6854
6857
|
const index = Math.max(...indexes);
|
|
6855
|
-
const out = [
|
|
6856
|
-
const err = existsSync6(
|
|
6858
|
+
const out = [join9(dir, `${index}.json`), join9(dir, `${index}.json.done`)].find((p) => existsSync6(p)) ?? null;
|
|
6859
|
+
const err = existsSync6(join9(dir, `${index}.err.log`)) ? join9(dir, `${index}.err.log`) : null;
|
|
6857
6860
|
return { index, total: indexes.size, out, err };
|
|
6858
6861
|
}
|
|
6859
6862
|
function runLogTail(runLogsDir, runLogKeys, opts) {
|
|
@@ -6940,6 +6943,14 @@ function toolErrorText(res) {
|
|
|
6940
6943
|
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";
|
|
6941
6944
|
return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
|
|
6942
6945
|
}
|
|
6946
|
+
var DEPS_UNRETRIABLE_CODES = /* @__PURE__ */ new Set([
|
|
6947
|
+
TOOL_FAILURE.FORBIDDEN,
|
|
6948
|
+
TOOL_FAILURE.UNAUTHORIZED,
|
|
6949
|
+
TOOL_FAILURE.WORKSPACE_MISMATCH
|
|
6950
|
+
]);
|
|
6951
|
+
function depsFailureRetriable(code) {
|
|
6952
|
+
return !DEPS_UNRETRIABLE_CODES.has(code);
|
|
6953
|
+
}
|
|
6943
6954
|
function runRef(runKey) {
|
|
6944
6955
|
return { __runRef: runKey };
|
|
6945
6956
|
}
|
|
@@ -7036,6 +7047,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7036
7047
|
// дальше удвоением до потолка TTL: устойчиво отвергающий прод (старее демона, скоуп
|
|
7037
7048
|
// токена) не должен получать вопрос каждые четыре секунды до конца своей жизни.
|
|
7038
7049
|
static DEPS_RETRY_BASE_MS = 5e3;
|
|
7050
|
+
// TASWM-517: сколько раз спрашиваем НЕСАМОЛЕЧАЩИЙСЯ отказ, прежде чем перестать вовсе.
|
|
7051
|
+
// `agent_forbidden` («тул недоступен аккаунту-агенту роя») повтором не чинится: 26.08
|
|
7052
|
+
// два прогона получили его по 53 раза каждый и остались в очереди навсегда. Три захода
|
|
7053
|
+
// при backoff 5с/10с — это ~15 секунд: запас на случай, если классификатор ошибся и
|
|
7054
|
+
// отказ на деле транзиентный, но не час молчаливого простоя. По исчерпании прогон
|
|
7055
|
+
// роняет гейт спавна (DEPS_UNRETRIABLE_ATTEMPTS читается и там).
|
|
7056
|
+
static DEPS_UNRETRIABLE_ATTEMPTS = 3;
|
|
7039
7057
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
7040
7058
|
// ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
|
|
7041
7059
|
// каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
|
|
@@ -7107,13 +7125,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7107
7125
|
recover() {
|
|
7108
7126
|
if (existsSync7(this.mapPath)) {
|
|
7109
7127
|
try {
|
|
7110
|
-
const obj = JSON.parse(
|
|
7128
|
+
const obj = JSON.parse(readFileSync6(this.mapPath, "utf8"));
|
|
7111
7129
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
7112
7130
|
} catch {
|
|
7113
7131
|
}
|
|
7114
7132
|
}
|
|
7115
7133
|
if (existsSync7(this.outboxPath)) {
|
|
7116
|
-
const txt =
|
|
7134
|
+
const txt = readFileSync6(this.outboxPath, "utf8");
|
|
7117
7135
|
for (const line of txt.split("\n")) {
|
|
7118
7136
|
const s = line.trim();
|
|
7119
7137
|
if (!s) continue;
|
|
@@ -7136,7 +7154,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7136
7154
|
if (!existsSync7(this.stormPath)) return;
|
|
7137
7155
|
let txt;
|
|
7138
7156
|
try {
|
|
7139
|
-
txt =
|
|
7157
|
+
txt = readFileSync6(this.stormPath, "utf8");
|
|
7140
7158
|
} catch {
|
|
7141
7159
|
return;
|
|
7142
7160
|
}
|
|
@@ -7641,11 +7659,12 @@ var SwarmClient = class _SwarmClient {
|
|
|
7641
7659
|
res = await this.call("swarm_deps_status", { refs: ask });
|
|
7642
7660
|
} catch (e) {
|
|
7643
7661
|
for (const ref of ask) this.depsAskedAt.delete(ref);
|
|
7644
|
-
this.noteDepsUnanswered(ask, nowMs, String(e));
|
|
7662
|
+
this.noteDepsUnanswered(ask, nowMs, String(e), depsFailureRetriable(classifyThrown(e).code));
|
|
7645
7663
|
return;
|
|
7646
7664
|
}
|
|
7647
7665
|
if (res?.isError) {
|
|
7648
|
-
|
|
7666
|
+
const text = toolErrorText(res);
|
|
7667
|
+
this.noteDepsUnanswered(ask, nowMs, text, depsFailureRetriable(classifyToolFailure(text)));
|
|
7649
7668
|
return;
|
|
7650
7669
|
}
|
|
7651
7670
|
const deps = res.structuredContent?.deps ?? [];
|
|
@@ -7685,6 +7704,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7685
7704
|
depsRetryDelay(ref) {
|
|
7686
7705
|
const fail = this.depsUnanswered.get(ref);
|
|
7687
7706
|
if (fail) {
|
|
7707
|
+
if (!fail.retriable && fail.attempts >= _SwarmClient.DEPS_UNRETRIABLE_ATTEMPTS) return _SwarmClient.DEPS_TTL_MS;
|
|
7688
7708
|
const backoff = _SwarmClient.DEPS_RETRY_BASE_MS * 2 ** Math.min(fail.attempts - 1, 10);
|
|
7689
7709
|
return Math.min(backoff, _SwarmClient.DEPS_TTL_MS);
|
|
7690
7710
|
}
|
|
@@ -7693,17 +7713,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
7693
7713
|
return _SwarmClient.DEPS_TTL_MS;
|
|
7694
7714
|
}
|
|
7695
7715
|
/** Запомнить, что прод по этим ссылкам не ответил (см. depsUnanswered / TASWM-486). */
|
|
7696
|
-
noteDepsUnanswered(refs, nowMs, error) {
|
|
7716
|
+
noteDepsUnanswered(refs, nowMs, error, retriable = true) {
|
|
7697
7717
|
for (const ref of refs) {
|
|
7698
7718
|
const prev = this.depsUnanswered.get(ref);
|
|
7719
|
+
const sameKind = prev != null && prev.retriable === retriable;
|
|
7699
7720
|
this.depsUnanswered.set(ref, {
|
|
7700
7721
|
ref,
|
|
7701
7722
|
slug: this.depSlug.get(ref) ?? ref,
|
|
7702
7723
|
// `since` — начало ЭПИЗОДА слепоты, а не последняя попытка: именно возраст эпизода
|
|
7703
7724
|
// отвечает на вопрос «сколько прогон уже стоит вслепую».
|
|
7704
7725
|
since: prev?.since ?? nowMs,
|
|
7705
|
-
attempts:
|
|
7706
|
-
error
|
|
7726
|
+
attempts: sameKind ? prev.attempts + 1 : 1,
|
|
7727
|
+
error,
|
|
7728
|
+
retriable
|
|
7707
7729
|
});
|
|
7708
7730
|
}
|
|
7709
7731
|
}
|
|
@@ -9247,13 +9269,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
9247
9269
|
}
|
|
9248
9270
|
persistPending() {
|
|
9249
9271
|
try {
|
|
9250
|
-
|
|
9272
|
+
writeFileSync6(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
|
|
9251
9273
|
} catch {
|
|
9252
9274
|
}
|
|
9253
9275
|
}
|
|
9254
9276
|
persistMap() {
|
|
9255
9277
|
try {
|
|
9256
|
-
|
|
9278
|
+
writeFileSync6(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
|
|
9257
9279
|
} catch {
|
|
9258
9280
|
}
|
|
9259
9281
|
}
|
|
@@ -9323,7 +9345,7 @@ function tickPulse(health, now, intervalMs) {
|
|
|
9323
9345
|
}
|
|
9324
9346
|
|
|
9325
9347
|
// src/logRotate.ts
|
|
9326
|
-
import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as
|
|
9348
|
+
import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
|
|
9327
9349
|
var realIo = {
|
|
9328
9350
|
size: (path) => {
|
|
9329
9351
|
try {
|
|
@@ -9335,7 +9357,7 @@ var realIo = {
|
|
|
9335
9357
|
copy: copyFileSync2,
|
|
9336
9358
|
rename: renameSync5,
|
|
9337
9359
|
truncate: (path) => truncateSync(path, 0),
|
|
9338
|
-
unlink:
|
|
9360
|
+
unlink: unlinkSync3,
|
|
9339
9361
|
exists: existsSync8
|
|
9340
9362
|
};
|
|
9341
9363
|
var realOwnerIo = {
|
|
@@ -14204,7 +14226,7 @@ import { serve as serve2 } from "@hono/node-server";
|
|
|
14204
14226
|
// package.json
|
|
14205
14227
|
var package_default = {
|
|
14206
14228
|
name: "@taskless-app/swarm",
|
|
14207
|
-
version: "0.6.
|
|
14229
|
+
version: "0.6.28",
|
|
14208
14230
|
type: "module",
|
|
14209
14231
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
14210
14232
|
keywords: [
|
|
@@ -14255,7 +14277,7 @@ var package_default = {
|
|
|
14255
14277
|
execa: "^10.0.0",
|
|
14256
14278
|
zod: "3.25.76"
|
|
14257
14279
|
},
|
|
14258
|
-
gitHead: "
|
|
14280
|
+
gitHead: "62910fc63850764f4562b7ec3bd65b44d3557050"
|
|
14259
14281
|
};
|
|
14260
14282
|
|
|
14261
14283
|
// src/version.ts
|
|
@@ -18411,6 +18433,15 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
18411
18433
|
if (unmet.length) {
|
|
18412
18434
|
const lost = unmet.filter((d) => d.outcome === "unknown");
|
|
18413
18435
|
const blind = deps.db.depsUnconfirmed?.(run.depends_on) ?? [];
|
|
18436
|
+
const stuck = blind.find((d) => !d.retriable && d.attempts >= SwarmClient.DEPS_UNRETRIABLE_ATTEMPTS);
|
|
18437
|
+
if (stuck) {
|
|
18438
|
+
return await failBeforeStart(
|
|
18439
|
+
deps,
|
|
18440
|
+
run,
|
|
18441
|
+
`\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C ${stuck.slug} \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C: \u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 swarm_deps_status \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u044D\u0442\u043E \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F (${stuck.attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A), \u043E\u0442\u0432\u0435\u0442 \u2014 ${stuck.error}). \u0412\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0435 \u0436\u0434\u0443 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 (TASWM-517)`,
|
|
18442
|
+
{ retriable: false }
|
|
18443
|
+
);
|
|
18444
|
+
}
|
|
18414
18445
|
const why = lost.length ? `\u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0435\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u041D\u0415\u0422: ${lost.map((d) => d.slug).join(", ")} \u2014 \u043F\u043E \u044D\u0442\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u0435 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430; \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442, \u0441\u043D\u0438\u043C\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C (swarm_run_upsert{dependsOn:[]}) \u0438\u043B\u0438 \u0437\u0430\u0432\u0435\u0434\u0438 \u0431\u043B\u043E\u043A\u0435\u0440 \u0437\u0430\u043D\u043E\u0432\u043E (TASWM-399)` : blind.length ? `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 ${blind.map((d) => d.slug).join(", ")}, \u043D\u043E \u041F\u0420\u041E\u0414 \u0415\u0401 \u041D\u0415 \u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u0415\u0422: ${blind[0].attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A) swarm_deps_status, \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u2014 ${blind[0].error}. \u041F\u043E\u043A\u0430 \u043F\u0440\u043E\u0434 \u043C\u043E\u043B\u0447\u0438\u0442, \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438 \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442 (TASWM-486)` : `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438: ${unmet.map((d) => d.slug).join(", ")}`;
|
|
18415
18446
|
return deferSpawn(deps, run, why);
|
|
18416
18447
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskless-app/swarm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"execa": "^10.0.0",
|
|
40
40
|
"zod": "3.25.76"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "62910fc63850764f4562b7ec3bd65b44d3557050",
|
|
43
43
|
"scripts": {
|
|
44
44
|
"overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
|
|
45
45
|
"swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",
|