@taskless-app/swarm 0.6.24 → 0.6.25
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 +3945 -3319
- package/dist/overmind.js +653 -349
- package/hooks/pre-merge-gate.sh +355 -0
- package/package.json +3 -2
package/dist/overmind.js
CHANGED
|
@@ -525,6 +525,56 @@ var init_mergeQueue = __esm({
|
|
|
525
525
|
}
|
|
526
526
|
});
|
|
527
527
|
|
|
528
|
+
// ../swarm-core/src/prRef.ts
|
|
529
|
+
function prNumberOf(ref) {
|
|
530
|
+
const s = (ref || "").trim();
|
|
531
|
+
if (!s) return null;
|
|
532
|
+
const url = s.match(/\/pull\/(\d+)/);
|
|
533
|
+
if (url) return Number(url[1]);
|
|
534
|
+
const bare = s.match(/^#?(\d+)$/);
|
|
535
|
+
return bare ? Number(bare[1]) : null;
|
|
536
|
+
}
|
|
537
|
+
function repoKeyOf(raw) {
|
|
538
|
+
const s = (raw ?? "").trim().replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
539
|
+
if (!s) return null;
|
|
540
|
+
const url = s.match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\/(?:pull|tree|compare|commit|issues)\/.*)?$/i);
|
|
541
|
+
if (url) return `${url[1]}/${url[2]}`.toLowerCase();
|
|
542
|
+
const pair = s.match(/^([\w.-]+)\/([\w.-]+)$/);
|
|
543
|
+
return pair ? `${pair[1]}/${pair[2]}`.toLowerCase() : null;
|
|
544
|
+
}
|
|
545
|
+
function newest(rows) {
|
|
546
|
+
let best = null;
|
|
547
|
+
for (const r of rows) if (!best || r.order > best.order) best = r;
|
|
548
|
+
return best;
|
|
549
|
+
}
|
|
550
|
+
function pick(rows) {
|
|
551
|
+
return newest(rows.filter((r) => r.author)) ?? newest(rows);
|
|
552
|
+
}
|
|
553
|
+
function pickRunForPr(rows, ref, ctxRepo) {
|
|
554
|
+
const num = prNumberOf(ref);
|
|
555
|
+
if (num == null) return null;
|
|
556
|
+
const target = repoKeyOf(ref) ?? repoKeyOf(ctxRepo);
|
|
557
|
+
const hits = rows.filter((r) => r.prUrl && prNumberOf(r.prUrl) === num);
|
|
558
|
+
if (hits.length === 0) return null;
|
|
559
|
+
if (target) {
|
|
560
|
+
const mine = hits.filter((r) => keyOf(r) === target);
|
|
561
|
+
const blind = hits.filter((r) => keyOf(r) == null);
|
|
562
|
+
return pick(mine.length ? mine : blind);
|
|
563
|
+
}
|
|
564
|
+
const keys = new Set(hits.map((r) => keyOf(r) ?? ""));
|
|
565
|
+
if (keys.size === 1) return pick(hits);
|
|
566
|
+
const risky = hits.filter((r) => r.reviewVerdict !== "approve");
|
|
567
|
+
return pick(risky.length ? risky : hits);
|
|
568
|
+
}
|
|
569
|
+
function keyOf(r) {
|
|
570
|
+
return repoKeyOf(r.prUrl) ?? repoKeyOf(r.repoKey);
|
|
571
|
+
}
|
|
572
|
+
var init_prRef = __esm({
|
|
573
|
+
"../swarm-core/src/prRef.ts"() {
|
|
574
|
+
"use strict";
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
528
578
|
// ../swarm-core/src/modelPolicy.ts
|
|
529
579
|
function findProvider(id) {
|
|
530
580
|
const key = typeof id === "string" ? id.trim().toLowerCase() : "";
|
|
@@ -1632,6 +1682,7 @@ var init_src = __esm({
|
|
|
1632
1682
|
init_reviewPhase();
|
|
1633
1683
|
init_mergeGate();
|
|
1634
1684
|
init_mergeQueue();
|
|
1685
|
+
init_prRef();
|
|
1635
1686
|
init_verifyVerdict();
|
|
1636
1687
|
init_modelPolicy();
|
|
1637
1688
|
init_status();
|
|
@@ -1784,6 +1835,43 @@ var init_swarmFiles = __esm({
|
|
|
1784
1835
|
}
|
|
1785
1836
|
});
|
|
1786
1837
|
|
|
1838
|
+
// src/mergeGateHook.ts
|
|
1839
|
+
import { chmodSync, copyFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1840
|
+
import { homedir } from "os";
|
|
1841
|
+
import { dirname, join as join2 } from "path";
|
|
1842
|
+
import { fileURLToPath } from "url";
|
|
1843
|
+
function packagedHookPath() {
|
|
1844
|
+
return join2(dirname(fileURLToPath(import.meta.url)), "..", "hooks", HOOK_FILE);
|
|
1845
|
+
}
|
|
1846
|
+
function wiredCommands(settings) {
|
|
1847
|
+
const blocks = settings?.hooks?.PreToolUse;
|
|
1848
|
+
if (!Array.isArray(blocks)) return [];
|
|
1849
|
+
return blocks.flatMap((b) => Array.isArray(b?.hooks) ? b.hooks : []).flatMap((h) => isOurHook(h?.command) ? [h.command] : []);
|
|
1850
|
+
}
|
|
1851
|
+
function readSettings(path) {
|
|
1852
|
+
if (!existsSync3(path)) return void 0;
|
|
1853
|
+
try {
|
|
1854
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
1855
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1856
|
+
} catch {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
function droneMergeGateHookPath(worktree, packaged = packagedHookPath()) {
|
|
1861
|
+
if (!existsSync3(packaged)) return null;
|
|
1862
|
+
const projectSettings = readSettings(join2(worktree, ".claude", "settings.json"));
|
|
1863
|
+
if (projectSettings && wiredCommands(projectSettings).length > 0) return null;
|
|
1864
|
+
return packaged;
|
|
1865
|
+
}
|
|
1866
|
+
var HOOK_FILE, isOurHook;
|
|
1867
|
+
var init_mergeGateHook = __esm({
|
|
1868
|
+
"src/mergeGateHook.ts"() {
|
|
1869
|
+
"use strict";
|
|
1870
|
+
HOOK_FILE = "pre-merge-gate.sh";
|
|
1871
|
+
isOurHook = (command) => typeof command === "string" && command.trim().endsWith(HOOK_FILE);
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1787
1875
|
// src/pricing.ts
|
|
1788
1876
|
function costFromUsage(usage, model, table = MODEL_PRICING) {
|
|
1789
1877
|
const entry = findModelPricing(model, table);
|
|
@@ -1872,9 +1960,9 @@ var init_claudeResult = __esm({
|
|
|
1872
1960
|
});
|
|
1873
1961
|
|
|
1874
1962
|
// src/executors/claudeCode.ts
|
|
1875
|
-
import { readdirSync as readdirSync2, statSync, writeFileSync as
|
|
1876
|
-
import { homedir } from "os";
|
|
1877
|
-
import { join as
|
|
1963
|
+
import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1964
|
+
import { homedir as homedir2 } from "os";
|
|
1965
|
+
import { join as join3 } from "path";
|
|
1878
1966
|
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
1879
1967
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
1880
1968
|
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
@@ -1900,20 +1988,23 @@ function droneDenyRules(paths) {
|
|
|
1900
1988
|
const abs = (p) => `//${p.replace(/^\/+/, "")}`;
|
|
1901
1989
|
return paths.flatMap((p) => [`Read(${abs(p)})`, `Edit(${abs(p)})`]);
|
|
1902
1990
|
}
|
|
1903
|
-
function droneSettingsJson(paths) {
|
|
1904
|
-
|
|
1991
|
+
function droneSettingsJson(paths, mergeGateHook = null) {
|
|
1992
|
+
const settings = {};
|
|
1993
|
+
if (paths.length > 0) settings.permissions = { deny: droneDenyRules(paths) };
|
|
1994
|
+
if (mergeGateHook) settings.hooks = { PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: mergeGateHook }] }] };
|
|
1995
|
+
return JSON.stringify(settings);
|
|
1905
1996
|
}
|
|
1906
1997
|
function transcriptDir(worktree, projectsDir) {
|
|
1907
|
-
return
|
|
1998
|
+
return join3(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
1908
1999
|
}
|
|
1909
|
-
function sessionTranscriptExists(worktree, projectsDir =
|
|
2000
|
+
function sessionTranscriptExists(worktree, projectsDir = join3(homedir2(), ".claude", "projects")) {
|
|
1910
2001
|
try {
|
|
1911
2002
|
return readdirSync2(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
|
|
1912
2003
|
} catch {
|
|
1913
2004
|
return false;
|
|
1914
2005
|
}
|
|
1915
2006
|
}
|
|
1916
|
-
function transcriptMtimeMs(worktree, projectsDir =
|
|
2007
|
+
function transcriptMtimeMs(worktree, projectsDir = join3(homedir2(), ".claude", "projects")) {
|
|
1917
2008
|
const dir = transcriptDir(worktree, projectsDir);
|
|
1918
2009
|
let files;
|
|
1919
2010
|
try {
|
|
@@ -1925,7 +2016,7 @@ function transcriptMtimeMs(worktree, projectsDir = join2(homedir(), ".claude", "
|
|
|
1925
2016
|
for (const f of files) {
|
|
1926
2017
|
if (!f.endsWith(".jsonl")) continue;
|
|
1927
2018
|
try {
|
|
1928
|
-
const mtime2 = statSync(
|
|
2019
|
+
const mtime2 = statSync(join3(dir, f)).mtimeMs;
|
|
1929
2020
|
if (latest === null || mtime2 > latest) latest = mtime2;
|
|
1930
2021
|
} catch {
|
|
1931
2022
|
}
|
|
@@ -1938,8 +2029,8 @@ function writeMcpConfig(spec) {
|
|
|
1938
2029
|
[spec.mcp.name]: { command: spec.mcp.command, args: spec.mcp.args, env: spec.mcp.env }
|
|
1939
2030
|
}
|
|
1940
2031
|
};
|
|
1941
|
-
const path =
|
|
1942
|
-
|
|
2032
|
+
const path = join3(spec.worktree, MCP_CONFIG_FILE);
|
|
2033
|
+
writeFileSync3(path, JSON.stringify(cfg, null, 2));
|
|
1943
2034
|
return path;
|
|
1944
2035
|
}
|
|
1945
2036
|
function abortField(abort) {
|
|
@@ -1951,6 +2042,7 @@ var init_claudeCode = __esm({
|
|
|
1951
2042
|
"use strict";
|
|
1952
2043
|
init_claudeBin();
|
|
1953
2044
|
init_swarmFiles();
|
|
2045
|
+
init_mergeGateHook();
|
|
1954
2046
|
init_claudeResult();
|
|
1955
2047
|
ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
|
|
1956
2048
|
MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
|
|
@@ -1964,7 +2056,8 @@ var init_claudeCode = __esm({
|
|
|
1964
2056
|
// проброс колбэком), и `this` тогда уедет в undefined.
|
|
1965
2057
|
plan: (spec) => {
|
|
1966
2058
|
const cfgPath = writeMcpConfig(spec);
|
|
1967
|
-
const
|
|
2059
|
+
const hook = droneMergeGateHookPath(spec.worktree);
|
|
2060
|
+
const settings = spec.denyPaths.length > 0 || hook ? droneSettingsJson(spec.denyPaths, hook) : void 0;
|
|
1968
2061
|
return {
|
|
1969
2062
|
bin: resolveClaudeBin(),
|
|
1970
2063
|
args: claudeArgs(cfgPath, spec.prompt, spec.continueSession, spec.model, spec.skillDirs, settings, spec.maxBudgetUsd, spec.effort)
|
|
@@ -2006,35 +2099,35 @@ var init_claudeCode = __esm({
|
|
|
2006
2099
|
});
|
|
2007
2100
|
|
|
2008
2101
|
// src/swarmHome.ts
|
|
2009
|
-
import { homedir as
|
|
2010
|
-
import { join as
|
|
2011
|
-
import { mkdirSync } from "fs";
|
|
2102
|
+
import { homedir as homedir3 } from "os";
|
|
2103
|
+
import { join as join4 } from "path";
|
|
2104
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
2012
2105
|
var SWARM_HOME;
|
|
2013
2106
|
var init_swarmHome = __esm({
|
|
2014
2107
|
"src/swarmHome.ts"() {
|
|
2015
2108
|
"use strict";
|
|
2016
|
-
SWARM_HOME = process.env.SWARM_HOME ||
|
|
2017
|
-
|
|
2109
|
+
SWARM_HOME = process.env.SWARM_HOME || join4(homedir3(), ".taskless-swarm");
|
|
2110
|
+
mkdirSync2(SWARM_HOME, { recursive: true });
|
|
2018
2111
|
}
|
|
2019
2112
|
});
|
|
2020
2113
|
|
|
2021
2114
|
// src/executors/codex.ts
|
|
2022
|
-
import { existsSync as
|
|
2023
|
-
import { join as
|
|
2115
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, statSync as statSync2, symlinkSync } from "fs";
|
|
2116
|
+
import { join as join5 } from "path";
|
|
2024
2117
|
function resolveCodexBin(env = process.env) {
|
|
2025
2118
|
return (env.SWARM_CODEX_BIN ?? "").trim() || "codex";
|
|
2026
2119
|
}
|
|
2027
2120
|
function codexHomePath(worktree, swarmHome = SWARM_HOME) {
|
|
2028
|
-
return
|
|
2121
|
+
return join5(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
|
|
2029
2122
|
}
|
|
2030
2123
|
function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWARM_HOME) {
|
|
2031
2124
|
const home = codexHomePath(worktree, swarmHome);
|
|
2032
|
-
|
|
2033
|
-
const real = (env.CODEX_HOME ?? "").trim() ||
|
|
2125
|
+
mkdirSync3(home, { recursive: true });
|
|
2126
|
+
const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
|
|
2034
2127
|
for (const file of ["auth.json", "config.toml"]) {
|
|
2035
|
-
const target =
|
|
2036
|
-
const link =
|
|
2037
|
-
if (!
|
|
2128
|
+
const target = join5(real, file);
|
|
2129
|
+
const link = join5(home, file);
|
|
2130
|
+
if (!existsSync4(target) || existsSync4(link)) continue;
|
|
2038
2131
|
try {
|
|
2039
2132
|
symlinkSync(target, link);
|
|
2040
2133
|
} catch {
|
|
@@ -2098,7 +2191,7 @@ function newestRolloutMtime(dir, depth = 4) {
|
|
|
2098
2191
|
}
|
|
2099
2192
|
let latest = null;
|
|
2100
2193
|
for (const name of entries) {
|
|
2101
|
-
const full =
|
|
2194
|
+
const full = join5(dir, name);
|
|
2102
2195
|
let st;
|
|
2103
2196
|
try {
|
|
2104
2197
|
st = statSync2(full);
|
|
@@ -2172,8 +2265,8 @@ ${notice}` : spec.prompt;
|
|
|
2172
2265
|
return est.warning ? { costUsd: est.costUsd, warning: est.warning } : { costUsd: est.costUsd };
|
|
2173
2266
|
},
|
|
2174
2267
|
session: {
|
|
2175
|
-
exists: (worktree) => newestRolloutMtime(
|
|
2176
|
-
mtimeMs: (worktree) => newestRolloutMtime(
|
|
2268
|
+
exists: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions")) !== null,
|
|
2269
|
+
mtimeMs: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions"))
|
|
2177
2270
|
},
|
|
2178
2271
|
probe: {
|
|
2179
2272
|
// Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
|
|
@@ -2298,6 +2391,7 @@ __export(config_exports, {
|
|
|
2298
2391
|
SWARM_WORKTREE_REAP_INTERVAL_MS: () => SWARM_WORKTREE_REAP_INTERVAL_MS,
|
|
2299
2392
|
TASKLESS_API: () => TASKLESS_API,
|
|
2300
2393
|
TASKLESS_MCP_PATH: () => TASKLESS_MCP_PATH,
|
|
2394
|
+
TASKLESS_REST_PATH: () => TASKLESS_REST_PATH,
|
|
2301
2395
|
TASKLESS_TASK_URL_BASE: () => TASKLESS_TASK_URL_BASE,
|
|
2302
2396
|
TASKLESS_TOKEN: () => TASKLESS_TOKEN,
|
|
2303
2397
|
TASKLESS_WORKSPACE: () => TASKLESS_WORKSPACE,
|
|
@@ -2309,14 +2403,16 @@ __export(config_exports, {
|
|
|
2309
2403
|
WORKTREES_NOINDEX_DIR: () => WORKTREES_NOINDEX_DIR,
|
|
2310
2404
|
droneWorktreePath: () => droneWorktreePath,
|
|
2311
2405
|
droneWorktreeRoot: () => droneWorktreeRoot,
|
|
2406
|
+
effectiveRepoOf: () => effectiveRepoOf,
|
|
2312
2407
|
mcpEndpoint: () => mcpEndpoint,
|
|
2313
2408
|
migrateWorktreesToNoindex: () => migrateWorktreesToNoindex,
|
|
2314
2409
|
normalizeRepo: () => normalizeRepo,
|
|
2315
2410
|
resolveEffectiveWorktreesDir: () => resolveEffectiveWorktreesDir,
|
|
2411
|
+
restEndpoint: () => restEndpoint,
|
|
2316
2412
|
reviewCutoffAt: () => reviewCutoffAt
|
|
2317
2413
|
});
|
|
2318
|
-
import { basename, join as
|
|
2319
|
-
import { readFileSync as
|
|
2414
|
+
import { basename, join as join6, resolve } from "path";
|
|
2415
|
+
import { readFileSync as readFileSync3, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2320
2416
|
function normalizeRepo(repo) {
|
|
2321
2417
|
const r = (repo ?? "").trim();
|
|
2322
2418
|
if (!r) return "";
|
|
@@ -2326,11 +2422,14 @@ function normalizeRepo(repo) {
|
|
|
2326
2422
|
return resolve(r);
|
|
2327
2423
|
}
|
|
2328
2424
|
}
|
|
2425
|
+
function effectiveRepoOf(repo, fallback) {
|
|
2426
|
+
return normalizeRepo(repo || fallback || "");
|
|
2427
|
+
}
|
|
2329
2428
|
function droneWorktreePath(worktreesDir, repo, slug) {
|
|
2330
|
-
return
|
|
2429
|
+
return join6(droneWorktreeRoot(worktreesDir, repo), slug);
|
|
2331
2430
|
}
|
|
2332
2431
|
function droneWorktreeRoot(worktreesDir, repo) {
|
|
2333
|
-
return
|
|
2432
|
+
return join6(worktreesDir, basename(normalizeRepo(repo)));
|
|
2334
2433
|
}
|
|
2335
2434
|
function resolveEffectiveWorktreesDir(deps) {
|
|
2336
2435
|
if (deps.envOverride) return deps.envOverride;
|
|
@@ -2365,6 +2464,12 @@ function envNum(name, def) {
|
|
|
2365
2464
|
const n = Number(raw);
|
|
2366
2465
|
return Number.isFinite(n) && n >= 0 ? n : def;
|
|
2367
2466
|
}
|
|
2467
|
+
function restEndpoint(api, route, prefix = TASKLESS_REST_PATH) {
|
|
2468
|
+
const base3 = api.replace(/\/$/, "");
|
|
2469
|
+
const p = prefix === "/" ? "" : prefix.startsWith("/") ? prefix.replace(/\/$/, "") : `/${prefix.replace(/\/$/, "")}`;
|
|
2470
|
+
const r = route.startsWith("/") ? route : `/${route}`;
|
|
2471
|
+
return `${base3}${p}${r}`;
|
|
2472
|
+
}
|
|
2368
2473
|
function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
|
|
2369
2474
|
const base3 = api.replace(/\/$/, "");
|
|
2370
2475
|
const p = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -2372,27 +2477,27 @@ function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
|
|
|
2372
2477
|
}
|
|
2373
2478
|
function tokenFromFile() {
|
|
2374
2479
|
try {
|
|
2375
|
-
return
|
|
2480
|
+
return readFileSync3(join6(SWARM_HOME, "token"), "utf8").trim();
|
|
2376
2481
|
} catch {
|
|
2377
2482
|
return "";
|
|
2378
2483
|
}
|
|
2379
2484
|
}
|
|
2380
2485
|
function reviewCutoffAt() {
|
|
2381
2486
|
try {
|
|
2382
|
-
const raw = JSON.parse(
|
|
2487
|
+
const raw = JSON.parse(readFileSync3(REVIEW_CUTOFF_PATH, "utf8"));
|
|
2383
2488
|
if (raw.at) return raw.at;
|
|
2384
2489
|
} catch {
|
|
2385
2490
|
}
|
|
2386
2491
|
const at = stamp();
|
|
2387
2492
|
try {
|
|
2388
2493
|
const tmp = `${REVIEW_CUTOFF_PATH}.tmp`;
|
|
2389
|
-
|
|
2494
|
+
writeFileSync4(tmp, JSON.stringify({ at }));
|
|
2390
2495
|
renameSync2(tmp, REVIEW_CUTOFF_PATH);
|
|
2391
2496
|
} catch {
|
|
2392
2497
|
}
|
|
2393
2498
|
return at;
|
|
2394
2499
|
}
|
|
2395
|
-
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2500
|
+
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_REST_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2396
2501
|
var init_config = __esm({
|
|
2397
2502
|
"src/config.ts"() {
|
|
2398
2503
|
"use strict";
|
|
@@ -2400,22 +2505,22 @@ var init_config = __esm({
|
|
|
2400
2505
|
init_executors();
|
|
2401
2506
|
init_swarmHome();
|
|
2402
2507
|
init_swarmHome();
|
|
2403
|
-
DB_PATH = process.env.SWARM_DB ||
|
|
2508
|
+
DB_PATH = process.env.SWARM_DB || join6(SWARM_HOME, "swarm.db");
|
|
2404
2509
|
EXECUTOR = resolveExecutor(process.env.SWARM_EXECUTOR);
|
|
2405
|
-
DRONE_HOME = process.env.SWARM_DRONE_HOME ||
|
|
2510
|
+
DRONE_HOME = process.env.SWARM_DRONE_HOME || join6(SWARM_HOME, "drone-home");
|
|
2406
2511
|
DRONE_HOME_ISOLATION = process.env.SWARM_DRONE_ISOLATE_HOME !== "0" && process.platform !== "win32";
|
|
2407
2512
|
DRONE_ISOLATION = DRONE_HOME_ISOLATION ? { home: DRONE_HOME, swarmHome: SWARM_HOME } : void 0;
|
|
2408
|
-
WORKTREES_NOINDEX_DIR =
|
|
2409
|
-
WORKTREES_LEGACY_DIR =
|
|
2513
|
+
WORKTREES_NOINDEX_DIR = join6(SWARM_HOME, "worktrees.noindex");
|
|
2514
|
+
WORKTREES_LEGACY_DIR = join6(SWARM_HOME, "worktrees");
|
|
2410
2515
|
WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
|
|
2411
|
-
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS ||
|
|
2412
|
-
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG ||
|
|
2516
|
+
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join6(SWARM_HOME, "run-logs");
|
|
2517
|
+
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join6(SWARM_HOME, "overmind.log");
|
|
2413
2518
|
DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
|
|
2414
2519
|
DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
|
|
2415
2520
|
DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
|
|
2416
2521
|
DIST_BUILD = true;
|
|
2417
2522
|
SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
|
|
2418
|
-
SWARM_OUTBOX = process.env.SWARM_OUTBOX ||
|
|
2523
|
+
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join6(SWARM_HOME, "outbox.jsonl");
|
|
2419
2524
|
SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
|
|
2420
2525
|
SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
|
|
2421
2526
|
SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
|
|
@@ -2445,20 +2550,21 @@ var init_config = __esm({
|
|
|
2445
2550
|
DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
|
|
2446
2551
|
DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
|
|
2447
2552
|
DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
|
|
2448
|
-
DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET ||
|
|
2553
|
+
DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET || join6(SWARM_HOME, "drone-channel.key");
|
|
2449
2554
|
DRONE_CHANNEL_GRACE_MS = envNum("SWARM_DRONE_CHANNEL_GRACE_MS", 12e4);
|
|
2450
|
-
HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT ||
|
|
2555
|
+
HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join6(SWARM_HOME, "overmind.heartbeat");
|
|
2451
2556
|
HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
|
|
2452
2557
|
SWARM_REPO = process.env.SWARM_REPO || "";
|
|
2453
2558
|
SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
|
|
2454
2559
|
SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2455
2560
|
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
2456
|
-
REPO_MAP_PATH = process.env.SWARM_REPO_MAP ||
|
|
2561
|
+
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join6(SWARM_HOME, "repo-map.json");
|
|
2457
2562
|
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
2458
2563
|
SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
|
|
2459
2564
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
2460
2565
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
2461
2566
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
2567
|
+
TASKLESS_REST_PATH = process.env.TASKLESS_REST_PATH || "/api";
|
|
2462
2568
|
TASKLESS_TOKEN = process.env.TASKLESS_TOKEN || tokenFromFile();
|
|
2463
2569
|
TASKLESS_WORKSPACE = process.env.TASKLESS_WORKSPACE || "";
|
|
2464
2570
|
TASKLESS_WORKSPACE_SLUG = process.env.TASKLESS_WORKSPACE_SLUG || "";
|
|
@@ -2467,7 +2573,7 @@ var init_config = __esm({
|
|
|
2467
2573
|
SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
|
|
2468
2574
|
SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
|
|
2469
2575
|
SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
|
|
2470
|
-
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF ||
|
|
2576
|
+
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join6(SWARM_HOME, "review-cutoff.json");
|
|
2471
2577
|
SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
|
|
2472
2578
|
SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
|
|
2473
2579
|
WEB_PORT = Number(process.env.SWARM_WEB_PORT) || 7420;
|
|
@@ -2507,7 +2613,7 @@ var init_quietExec = __esm({
|
|
|
2507
2613
|
|
|
2508
2614
|
// src/resources.ts
|
|
2509
2615
|
import os from "os";
|
|
2510
|
-
import { readFileSync as
|
|
2616
|
+
import { readFileSync as readFileSync4, statfsSync } from "fs";
|
|
2511
2617
|
function isTransientResourceError(e) {
|
|
2512
2618
|
const code = e?.code;
|
|
2513
2619
|
return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
|
|
@@ -2565,7 +2671,7 @@ function fileDescriptorLoadRatio() {
|
|
|
2565
2671
|
}
|
|
2566
2672
|
if (process.platform === "linux") {
|
|
2567
2673
|
try {
|
|
2568
|
-
const [allocated, , max] =
|
|
2674
|
+
const [allocated, , max] = readFileSync4("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
|
|
2569
2675
|
if (Number.isFinite(allocated ?? NaN) && Number.isFinite(max ?? NaN) && max) return allocated / max;
|
|
2570
2676
|
} catch {
|
|
2571
2677
|
}
|
|
@@ -2882,15 +2988,15 @@ var init_storm = __esm({
|
|
|
2882
2988
|
});
|
|
2883
2989
|
|
|
2884
2990
|
// src/heartbeat.ts
|
|
2885
|
-
import { readFileSync as
|
|
2991
|
+
import { readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
2886
2992
|
function writeHeartbeat(path, now) {
|
|
2887
2993
|
const tmp = `${path}.tmp`;
|
|
2888
|
-
|
|
2994
|
+
writeFileSync6(tmp, String(now));
|
|
2889
2995
|
renameSync4(tmp, path);
|
|
2890
2996
|
}
|
|
2891
2997
|
function readHeartbeat(path) {
|
|
2892
2998
|
try {
|
|
2893
|
-
const ms = Number(
|
|
2999
|
+
const ms = Number(readFileSync6(path, "utf8").trim());
|
|
2894
3000
|
return Number.isFinite(ms) ? ms : null;
|
|
2895
3001
|
} catch {
|
|
2896
3002
|
return null;
|
|
@@ -2946,11 +3052,11 @@ var init_logNoise = __esm({
|
|
|
2946
3052
|
});
|
|
2947
3053
|
|
|
2948
3054
|
// src/agentToken.ts
|
|
2949
|
-
import { readFileSync as
|
|
2950
|
-
import { join as
|
|
3055
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
3056
|
+
import { join as join9 } from "path";
|
|
2951
3057
|
function loadAgentToken() {
|
|
2952
3058
|
try {
|
|
2953
|
-
const s = JSON.parse(
|
|
3059
|
+
const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
|
|
2954
3060
|
return s.token ? s : null;
|
|
2955
3061
|
} catch {
|
|
2956
3062
|
return null;
|
|
@@ -3003,7 +3109,7 @@ var init_agentToken = __esm({
|
|
|
3003
3109
|
"src/agentToken.ts"() {
|
|
3004
3110
|
"use strict";
|
|
3005
3111
|
init_config();
|
|
3006
|
-
STORE_PATH =
|
|
3112
|
+
STORE_PATH = join9(SWARM_HOME, "agent.json");
|
|
3007
3113
|
base = (api) => api.replace(/\/$/, "");
|
|
3008
3114
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
3009
3115
|
}
|
|
@@ -3441,9 +3547,9 @@ var init_netTimeout = __esm({
|
|
|
3441
3547
|
});
|
|
3442
3548
|
|
|
3443
3549
|
// src/git.ts
|
|
3444
|
-
import { existsSync as
|
|
3550
|
+
import { existsSync as existsSync11, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
3445
3551
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3446
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3552
|
+
import { isAbsolute as isAbsolute2, join as join18, sep as sep2 } from "path";
|
|
3447
3553
|
import { $ as $2 } from "execa";
|
|
3448
3554
|
function gitNet(repo, args, timeoutMs) {
|
|
3449
3555
|
return withHardTimeout(
|
|
@@ -3466,7 +3572,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
3466
3572
|
}
|
|
3467
3573
|
function isLinkedWorktree(dir) {
|
|
3468
3574
|
try {
|
|
3469
|
-
return statSync8(
|
|
3575
|
+
return statSync8(join18(dir, ".git")).isFile();
|
|
3470
3576
|
} catch {
|
|
3471
3577
|
return false;
|
|
3472
3578
|
}
|
|
@@ -3478,7 +3584,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
3478
3584
|
}
|
|
3479
3585
|
}
|
|
3480
3586
|
async function ensureWorktreeDirGone(worktree) {
|
|
3481
|
-
if (!
|
|
3587
|
+
if (!existsSync11(worktree)) return false;
|
|
3482
3588
|
if (!isLinkedWorktree(worktree)) {
|
|
3483
3589
|
console.error(
|
|
3484
3590
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -3493,7 +3599,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
3493
3599
|
);
|
|
3494
3600
|
return true;
|
|
3495
3601
|
}
|
|
3496
|
-
if (
|
|
3602
|
+
if (existsSync11(worktree)) {
|
|
3497
3603
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
3498
3604
|
}
|
|
3499
3605
|
return true;
|
|
@@ -3541,7 +3647,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3541
3647
|
}
|
|
3542
3648
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
3543
3649
|
if (busyAt) {
|
|
3544
|
-
const exists =
|
|
3650
|
+
const exists = existsSync11(busyAt);
|
|
3545
3651
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
3546
3652
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
3547
3653
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -3576,8 +3682,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3576
3682
|
try {
|
|
3577
3683
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3578
3684
|
if (!commonDir) return false;
|
|
3579
|
-
const infoDir =
|
|
3580
|
-
const excludeFile =
|
|
3685
|
+
const infoDir = join18(commonDir, "info");
|
|
3686
|
+
const excludeFile = join18(infoDir, "exclude");
|
|
3581
3687
|
let current = "";
|
|
3582
3688
|
try {
|
|
3583
3689
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3597,7 +3703,7 @@ async function gitPath(worktree, flag) {
|
|
|
3597
3703
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3598
3704
|
const raw = out.stdout.trim();
|
|
3599
3705
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3600
|
-
return isAbsolute2(raw) ? raw :
|
|
3706
|
+
return isAbsolute2(raw) ? raw : join18(worktree, raw);
|
|
3601
3707
|
}
|
|
3602
3708
|
async function ensureWorktreeHygiene(worktree) {
|
|
3603
3709
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3607,12 +3713,12 @@ async function installGuardHooks(worktree) {
|
|
|
3607
3713
|
try {
|
|
3608
3714
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3609
3715
|
if (!gitDir) return false;
|
|
3610
|
-
const hooksDir =
|
|
3716
|
+
const hooksDir = join18(gitDir, "swarm-hooks");
|
|
3611
3717
|
const prev = await previousHooksPath(worktree);
|
|
3612
3718
|
await mkdir(hooksDir, { recursive: true });
|
|
3613
3719
|
for (const hook of GUARD_HOOKS) {
|
|
3614
|
-
await writeFile(
|
|
3615
|
-
await chmod(
|
|
3720
|
+
await writeFile(join18(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
3721
|
+
await chmod(join18(hooksDir, hook), 493);
|
|
3616
3722
|
}
|
|
3617
3723
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3618
3724
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3631,7 +3737,7 @@ async function previousHooksPath(worktree) {
|
|
|
3631
3737
|
if (out.exitCode === 0 && value) return value;
|
|
3632
3738
|
}
|
|
3633
3739
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3634
|
-
return commonDir ?
|
|
3740
|
+
return commonDir ? join18(commonDir, "hooks") : null;
|
|
3635
3741
|
}
|
|
3636
3742
|
async function removeWorktree(repo, path) {
|
|
3637
3743
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3656,7 +3762,7 @@ async function worktreeDirty(path) {
|
|
|
3656
3762
|
async function repairMovedWorktrees(targetDir) {
|
|
3657
3763
|
const subdirs = (dir) => {
|
|
3658
3764
|
try {
|
|
3659
|
-
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) =>
|
|
3765
|
+
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join18(dir, d.name));
|
|
3660
3766
|
} catch {
|
|
3661
3767
|
return [];
|
|
3662
3768
|
}
|
|
@@ -3717,7 +3823,7 @@ async function mergeRef(worktree, ref) {
|
|
|
3717
3823
|
return { clean: false, conflicts: u ? u.split("\n") : [], changed: false };
|
|
3718
3824
|
}
|
|
3719
3825
|
async function pushHeadTo(worktree, branch) {
|
|
3720
|
-
await gitNetStrict(worktree, ["push", "origin", `HEAD
|
|
3826
|
+
await gitNetStrict(worktree, ["push", "origin", `HEAD:refs/heads/${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
|
|
3721
3827
|
}
|
|
3722
3828
|
async function snapshotWorktree(worktree, message) {
|
|
3723
3829
|
try {
|
|
@@ -3785,20 +3891,20 @@ __export(droneChannel_exports, {
|
|
|
3785
3891
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
3786
3892
|
});
|
|
3787
3893
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3788
|
-
import { chmodSync, mkdirSync as
|
|
3789
|
-
import { dirname as
|
|
3894
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
|
|
3895
|
+
import { dirname as dirname4 } from "path";
|
|
3790
3896
|
import { serve } from "@hono/node-server";
|
|
3791
3897
|
function loadChannelSecret(path) {
|
|
3792
3898
|
try {
|
|
3793
|
-
const s =
|
|
3899
|
+
const s = readFileSync15(path, "utf8").trim();
|
|
3794
3900
|
if (s.length >= 32) return s;
|
|
3795
3901
|
} catch {
|
|
3796
3902
|
}
|
|
3797
3903
|
const secret = randomBytes(32).toString("hex");
|
|
3798
|
-
|
|
3799
|
-
|
|
3904
|
+
mkdirSync9(dirname4(path), { recursive: true });
|
|
3905
|
+
writeFileSync11(path, secret, { mode: 384 });
|
|
3800
3906
|
try {
|
|
3801
|
-
|
|
3907
|
+
chmodSync2(path, 384);
|
|
3802
3908
|
} catch {
|
|
3803
3909
|
}
|
|
3804
3910
|
return secret;
|
|
@@ -4140,7 +4246,7 @@ var init_droneChannel = __esm({
|
|
|
4140
4246
|
|
|
4141
4247
|
// src/attachments.ts
|
|
4142
4248
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
4143
|
-
import { join as
|
|
4249
|
+
import { join as join21 } from "path";
|
|
4144
4250
|
function attachmentsFromDoc(doc, origin) {
|
|
4145
4251
|
const out = [];
|
|
4146
4252
|
const walk2 = (node) => {
|
|
@@ -4231,7 +4337,7 @@ async function materializeAttachments(opts) {
|
|
|
4231
4337
|
};
|
|
4232
4338
|
const result = { files: [], skipped: [] };
|
|
4233
4339
|
if (opts.refs.length === 0) return result;
|
|
4234
|
-
const dir =
|
|
4340
|
+
const dir = join21(opts.worktree, ATTACHMENTS_DIR);
|
|
4235
4341
|
await io.removeDir(dir);
|
|
4236
4342
|
let dirReady = false;
|
|
4237
4343
|
let total = 0;
|
|
@@ -4277,7 +4383,7 @@ async function materializeAttachments(opts) {
|
|
|
4277
4383
|
await io.mkdir(dir);
|
|
4278
4384
|
dirReady = true;
|
|
4279
4385
|
}
|
|
4280
|
-
await io.writeFile(
|
|
4386
|
+
await io.writeFile(join21(dir, name), file.bytes);
|
|
4281
4387
|
} catch (e) {
|
|
4282
4388
|
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
4283
4389
|
continue;
|
|
@@ -4430,7 +4536,7 @@ var init_attachments = __esm({
|
|
|
4430
4536
|
|
|
4431
4537
|
// src/shotUpload.ts
|
|
4432
4538
|
import { readFile as fsReadFile } from "fs/promises";
|
|
4433
|
-
import { basename as basename4, isAbsolute as isAbsolute3, join as
|
|
4539
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join22 } from "path";
|
|
4434
4540
|
function shotMimetype(path) {
|
|
4435
4541
|
const dot = path.lastIndexOf(".");
|
|
4436
4542
|
if (dot < 0) return null;
|
|
@@ -4456,7 +4562,7 @@ async function readShots(paths, opts) {
|
|
|
4456
4562
|
}
|
|
4457
4563
|
let bytes;
|
|
4458
4564
|
try {
|
|
4459
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
4565
|
+
bytes = await read(isAbsolute3(path) ? path : join22(opts.worktree, path));
|
|
4460
4566
|
} catch (e) {
|
|
4461
4567
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
4462
4568
|
continue;
|
|
@@ -4466,7 +4572,10 @@ async function readShots(paths, opts) {
|
|
|
4466
4572
|
continue;
|
|
4467
4573
|
}
|
|
4468
4574
|
if (bytes.byteLength > maxBytes) {
|
|
4469
|
-
skipped.push({
|
|
4575
|
+
skipped.push({
|
|
4576
|
+
path,
|
|
4577
|
+
reason: `${(bytes.byteLength / 1024 / 1024).toFixed(1)} \u041C\u0411 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043B\u0438\u043C\u0438\u0442\u0430 ${maxBytes / 1024 / 1024} \u041C\u0411; \u043F\u0435\u0440\u0435\u0441\u043D\u0438\u043C\u0438 \u043F\u043E\u043B\u0435\u0433\u0447\u0435: swarm shot \u2026 --scale 1 (\u0438\u043B\u0438 --scroll-to <\u0441\u0435\u043B\u0435\u043A\u0442\u043E\u0440> \u0432\u043C\u0435\u0441\u0442\u043E \u0434\u043B\u0438\u043D\u043D\u043E\u0433\u043E \u043A\u0430\u0434\u0440\u0430)`
|
|
4578
|
+
});
|
|
4470
4579
|
continue;
|
|
4471
4580
|
}
|
|
4472
4581
|
files.push({ path, fileName: basename4(path), mimetype, bytes });
|
|
@@ -4521,7 +4630,7 @@ var init_shotUpload = __esm({
|
|
|
4521
4630
|
"src/shotUpload.ts"() {
|
|
4522
4631
|
"use strict";
|
|
4523
4632
|
SHOT_MAX_FILES = 6;
|
|
4524
|
-
SHOT_MAX_FILE_BYTES =
|
|
4633
|
+
SHOT_MAX_FILE_BYTES = 20 * 1024 * 1024;
|
|
4525
4634
|
MIME_BY_EXT = {
|
|
4526
4635
|
".png": "image/png",
|
|
4527
4636
|
".jpg": "image/jpeg",
|
|
@@ -4534,8 +4643,8 @@ var init_shotUpload = __esm({
|
|
|
4534
4643
|
|
|
4535
4644
|
// src/oauth.ts
|
|
4536
4645
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
4537
|
-
import { readFileSync as
|
|
4538
|
-
import { join as
|
|
4646
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
|
|
4647
|
+
import { join as join23 } from "path";
|
|
4539
4648
|
function makePkce() {
|
|
4540
4649
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4541
4650
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
@@ -4619,13 +4728,13 @@ function toStore(clientId, t, now) {
|
|
|
4619
4728
|
}
|
|
4620
4729
|
function loadStore() {
|
|
4621
4730
|
try {
|
|
4622
|
-
return JSON.parse(
|
|
4731
|
+
return JSON.parse(readFileSync16(STORE_PATH2, "utf8"));
|
|
4623
4732
|
} catch {
|
|
4624
4733
|
return null;
|
|
4625
4734
|
}
|
|
4626
4735
|
}
|
|
4627
4736
|
function saveStore(s) {
|
|
4628
|
-
|
|
4737
|
+
writeFileSync12(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
4629
4738
|
}
|
|
4630
4739
|
function clearStore() {
|
|
4631
4740
|
try {
|
|
@@ -4635,7 +4744,7 @@ function clearStore() {
|
|
|
4635
4744
|
}
|
|
4636
4745
|
function loadClientId() {
|
|
4637
4746
|
try {
|
|
4638
|
-
return JSON.parse(
|
|
4747
|
+
return JSON.parse(readFileSync16(CLIENT_PATH, "utf8")).clientId;
|
|
4639
4748
|
} catch {
|
|
4640
4749
|
return null;
|
|
4641
4750
|
}
|
|
@@ -4644,7 +4753,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
4644
4753
|
const existing = loadClientId();
|
|
4645
4754
|
if (existing) return existing;
|
|
4646
4755
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
4647
|
-
|
|
4756
|
+
writeFileSync12(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
4648
4757
|
return clientId;
|
|
4649
4758
|
}
|
|
4650
4759
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -4670,8 +4779,8 @@ var init_oauth = __esm({
|
|
|
4670
4779
|
AUTHORIZE = "/api/oauth/authorize";
|
|
4671
4780
|
TOKEN = "/api/oauth/token";
|
|
4672
4781
|
SCOPE = "openid profile email taskless:full";
|
|
4673
|
-
STORE_PATH2 =
|
|
4674
|
-
CLIENT_PATH =
|
|
4782
|
+
STORE_PATH2 = join23(SWARM_HOME, "oauth.json");
|
|
4783
|
+
CLIENT_PATH = join23(SWARM_HOME, "oauth-client.json");
|
|
4675
4784
|
base2 = (api) => api.replace(/\/$/, "");
|
|
4676
4785
|
}
|
|
4677
4786
|
});
|
|
@@ -4789,11 +4898,11 @@ var init_authState = __esm({
|
|
|
4789
4898
|
});
|
|
4790
4899
|
|
|
4791
4900
|
// src/workspace.ts
|
|
4792
|
-
import { readFileSync as
|
|
4793
|
-
import { join as
|
|
4901
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
|
|
4902
|
+
import { join as join24 } from "path";
|
|
4794
4903
|
function loadWorkspace() {
|
|
4795
4904
|
try {
|
|
4796
|
-
const w = JSON.parse(
|
|
4905
|
+
const w = JSON.parse(readFileSync17(WORKSPACE_PATH, "utf8"));
|
|
4797
4906
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
4798
4907
|
} catch {
|
|
4799
4908
|
return null;
|
|
@@ -4807,7 +4916,7 @@ var init_workspace = __esm({
|
|
|
4807
4916
|
"src/workspace.ts"() {
|
|
4808
4917
|
"use strict";
|
|
4809
4918
|
init_config();
|
|
4810
|
-
WORKSPACE_PATH =
|
|
4919
|
+
WORKSPACE_PATH = join24(SWARM_HOME, "workspace.json");
|
|
4811
4920
|
}
|
|
4812
4921
|
});
|
|
4813
4922
|
|
|
@@ -4844,7 +4953,7 @@ function isClosingTaskCategory(category) {
|
|
|
4844
4953
|
function taskAuthor(u) {
|
|
4845
4954
|
return u?.id ? { id: u.id, name: u.displayName || "\u0430\u0432\u0442\u043E\u0440 \u0437\u0430\u0434\u0430\u0447\u0438" } : null;
|
|
4846
4955
|
}
|
|
4847
|
-
function
|
|
4956
|
+
function toolErrorText2(r) {
|
|
4848
4957
|
const fromContent = (r.content ?? []).map((c) => c.text ?? "").filter(Boolean).join(" ").trim();
|
|
4849
4958
|
return fromContent || JSON.stringify(r);
|
|
4850
4959
|
}
|
|
@@ -4856,7 +4965,7 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4856
4965
|
try {
|
|
4857
4966
|
const r = await call(name, WS_FREE_TOOLS.has(name) ? args : { ...args, ...ws });
|
|
4858
4967
|
if (r?.isError) {
|
|
4859
|
-
const raw =
|
|
4968
|
+
const raw = toolErrorText2(r);
|
|
4860
4969
|
const detail = trimDetail(raw);
|
|
4861
4970
|
console.error(`[taskless] \u0442\u0443\u043B ${name} \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443:`, detail);
|
|
4862
4971
|
return { ok: false, failure: { code: classifyToolFailure(raw), detail } };
|
|
@@ -5388,7 +5497,7 @@ __export(db_exports, {
|
|
|
5388
5497
|
});
|
|
5389
5498
|
import { createRequire as createRequire2 } from "module";
|
|
5390
5499
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5391
|
-
import { existsSync as
|
|
5500
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5392
5501
|
import { resolve as resolve3 } from "path";
|
|
5393
5502
|
function rowToQuestion(row) {
|
|
5394
5503
|
return {
|
|
@@ -5996,7 +6105,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5996
6105
|
const repoN = normalizeRepo(repo);
|
|
5997
6106
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
5998
6107
|
if (already) return { imported: false, runs: already.run_count };
|
|
5999
|
-
if (!
|
|
6108
|
+
if (!existsSync13(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
6000
6109
|
const src = new this.databaseCtor(abs);
|
|
6001
6110
|
try {
|
|
6002
6111
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -6364,10 +6473,10 @@ var init_droneStore = __esm({
|
|
|
6364
6473
|
});
|
|
6365
6474
|
|
|
6366
6475
|
// src/overmind.ts
|
|
6367
|
-
import { homedir as
|
|
6368
|
-
import { existsSync as
|
|
6369
|
-
import { basename as basename6, dirname as
|
|
6370
|
-
import { fileURLToPath as
|
|
6476
|
+
import { homedir as homedir6 } from "os";
|
|
6477
|
+
import { existsSync as existsSync18, readFileSync as readFileSync21, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
|
|
6478
|
+
import { basename as basename6, dirname as dirname5, join as join31 } from "path";
|
|
6479
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
6371
6480
|
import { spawn as spawn3 } from "child_process";
|
|
6372
6481
|
|
|
6373
6482
|
// src/restart.ts
|
|
@@ -6446,8 +6555,8 @@ init_config();
|
|
|
6446
6555
|
init_resources();
|
|
6447
6556
|
init_names2();
|
|
6448
6557
|
init_storm();
|
|
6449
|
-
import { appendFileSync, existsSync as
|
|
6450
|
-
import { dirname } from "path";
|
|
6558
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
6559
|
+
import { dirname as dirname2 } from "path";
|
|
6451
6560
|
import { randomUUID } from "crypto";
|
|
6452
6561
|
|
|
6453
6562
|
// src/repoRules.ts
|
|
@@ -6499,12 +6608,12 @@ function renderPendingRulesBlock(rules) {
|
|
|
6499
6608
|
}
|
|
6500
6609
|
|
|
6501
6610
|
// src/logTail.ts
|
|
6502
|
-
import { closeSync, existsSync as
|
|
6503
|
-
import { join as
|
|
6611
|
+
import { closeSync, existsSync as existsSync6, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
6612
|
+
import { join as join8 } from "path";
|
|
6504
6613
|
|
|
6505
6614
|
// src/runLogDir.ts
|
|
6506
|
-
import { existsSync as
|
|
6507
|
-
import { join as
|
|
6615
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
6616
|
+
import { join as join7 } from "path";
|
|
6508
6617
|
var LEGACY_RUN_LOGS_SUBDIR = "legacy";
|
|
6509
6618
|
function runLogDirName(runLogKey) {
|
|
6510
6619
|
const safe2 = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
@@ -6512,12 +6621,12 @@ function runLogDirName(runLogKey) {
|
|
|
6512
6621
|
return /^\d+$/.test(safe2) ? `k-${safe2}` : safe2;
|
|
6513
6622
|
}
|
|
6514
6623
|
function runLogDirPath(runLogsDir, runLogKey) {
|
|
6515
|
-
return
|
|
6624
|
+
return join7(runLogsDir, runLogDirName(runLogKey));
|
|
6516
6625
|
}
|
|
6517
6626
|
function findRunLogDir(runLogsDir, keys) {
|
|
6518
6627
|
for (const key of keys) {
|
|
6519
6628
|
const dir = runLogDirPath(runLogsDir, key);
|
|
6520
|
-
if (
|
|
6629
|
+
if (existsSync5(dir)) return dir;
|
|
6521
6630
|
}
|
|
6522
6631
|
return null;
|
|
6523
6632
|
}
|
|
@@ -6527,7 +6636,7 @@ function runLogDirForWrite(runLogsDir, keys) {
|
|
|
6527
6636
|
return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
|
|
6528
6637
|
}
|
|
6529
6638
|
function sweepLegacyRunLogs(runLogsDir) {
|
|
6530
|
-
const legacyDir =
|
|
6639
|
+
const legacyDir = join7(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
|
|
6531
6640
|
let entries;
|
|
6532
6641
|
try {
|
|
6533
6642
|
entries = readdirSync4(runLogsDir);
|
|
@@ -6538,12 +6647,12 @@ function sweepLegacyRunLogs(runLogsDir) {
|
|
|
6538
6647
|
let failed = 0;
|
|
6539
6648
|
for (const name of entries) {
|
|
6540
6649
|
if (!/^\d+$/.test(name)) continue;
|
|
6541
|
-
const from =
|
|
6650
|
+
const from = join7(runLogsDir, name);
|
|
6542
6651
|
try {
|
|
6543
6652
|
if (!statSync3(from).isDirectory()) continue;
|
|
6544
|
-
|
|
6545
|
-
let to =
|
|
6546
|
-
for (let n = 2;
|
|
6653
|
+
mkdirSync5(legacyDir, { recursive: true });
|
|
6654
|
+
let to = join7(legacyDir, name);
|
|
6655
|
+
for (let n = 2; existsSync5(to) && n < 100; n++) to = join7(legacyDir, `${name}-${n}`);
|
|
6547
6656
|
renameSync3(from, to);
|
|
6548
6657
|
moved++;
|
|
6549
6658
|
} catch {
|
|
@@ -6609,8 +6718,8 @@ function latestRunLaunch(runLogsDir, runLogKeys) {
|
|
|
6609
6718
|
}
|
|
6610
6719
|
if (indexes.size === 0) return null;
|
|
6611
6720
|
const index = Math.max(...indexes);
|
|
6612
|
-
const out = [
|
|
6613
|
-
const err =
|
|
6721
|
+
const out = [join8(dir, `${index}.json`), join8(dir, `${index}.json.done`)].find((p) => existsSync6(p)) ?? null;
|
|
6722
|
+
const err = existsSync6(join8(dir, `${index}.err.log`)) ? join8(dir, `${index}.err.log`) : null;
|
|
6614
6723
|
return { index, total: indexes.size, out, err };
|
|
6615
6724
|
}
|
|
6616
6725
|
function runLogTail(runLogsDir, runLogKeys, opts) {
|
|
@@ -6692,6 +6801,11 @@ async function serveLogRequests(deps, commands) {
|
|
|
6692
6801
|
init_src();
|
|
6693
6802
|
var RESOLVER_TERMINAL = /* @__PURE__ */ new Set([...TERMINAL_STATUSES, "stalled"]);
|
|
6694
6803
|
var SLUG_TERMINAL = new Set(TERMINAL_STATUSES);
|
|
6804
|
+
function toolErrorText(res) {
|
|
6805
|
+
const text = (res?.content ?? []).map((c) => c.text ?? "").join(" ").replace(/\s+/g, " ").trim();
|
|
6806
|
+
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";
|
|
6807
|
+
return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
|
|
6808
|
+
}
|
|
6695
6809
|
function runRef(runKey) {
|
|
6696
6810
|
return { __runRef: runKey };
|
|
6697
6811
|
}
|
|
@@ -6777,6 +6891,17 @@ var SwarmClient = class _SwarmClient {
|
|
|
6777
6891
|
// нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
|
|
6778
6892
|
depSlug = /* @__PURE__ */ new Map();
|
|
6779
6893
|
static DEPS_TTL_MS = 6e4;
|
|
6894
|
+
// TASWM-486: ссылки, по которым прод НЕ ОТВЕТИЛ (отверг вызов или не дозвонились) —
|
|
6895
|
+
// с какого времени, сколько попыток и чем именно отказал. Отказ ≠ ответ: вердикта в
|
|
6896
|
+
// `depsProd` не появляется, `depVerdicts` отдаёт `pending`, и ждущий прогон стоит,
|
|
6897
|
+
// пока человек не разбудит его руками (21.08 — дважды за час). Отсюда две вещи:
|
|
6898
|
+
// короткий backoff вместо полного TTL (переспросить, а не ждать минуту) и
|
|
6899
|
+
// `depsUnconfirmed` — чтобы «стою вслепую» было ВИДНО в причине отказа и в аномалии.
|
|
6900
|
+
depsUnanswered = /* @__PURE__ */ new Map();
|
|
6901
|
+
// Первый переспрос — через 5 с (тик ~4 с, то есть буквально «на следующем тике»),
|
|
6902
|
+
// дальше удвоением до потолка TTL: устойчиво отвергающий прод (старее демона, скоуп
|
|
6903
|
+
// токена) не должен получать вопрос каждые четыре секунды до конца своей жизни.
|
|
6904
|
+
static DEPS_RETRY_BASE_MS = 5e3;
|
|
6780
6905
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
6781
6906
|
// ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
|
|
6782
6907
|
// каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
|
|
@@ -6835,26 +6960,26 @@ var SwarmClient = class _SwarmClient {
|
|
|
6835
6960
|
this.repoFullNames = registry.map((e) => e.repoFullName);
|
|
6836
6961
|
this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
|
|
6837
6962
|
this.host = opts.host ?? null;
|
|
6838
|
-
this.repoExists = opts.repoExists ??
|
|
6963
|
+
this.repoExists = opts.repoExists ?? existsSync7;
|
|
6839
6964
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
6840
6965
|
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
6841
6966
|
this.outboxPath = opts.outboxPath;
|
|
6842
6967
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
6843
6968
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
6844
|
-
|
|
6969
|
+
mkdirSync6(dirname2(this.outboxPath), { recursive: true });
|
|
6845
6970
|
this.recover();
|
|
6846
6971
|
}
|
|
6847
6972
|
// ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
|
|
6848
6973
|
recover() {
|
|
6849
|
-
if (
|
|
6974
|
+
if (existsSync7(this.mapPath)) {
|
|
6850
6975
|
try {
|
|
6851
|
-
const obj = JSON.parse(
|
|
6976
|
+
const obj = JSON.parse(readFileSync5(this.mapPath, "utf8"));
|
|
6852
6977
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
6853
6978
|
} catch {
|
|
6854
6979
|
}
|
|
6855
6980
|
}
|
|
6856
|
-
if (
|
|
6857
|
-
const txt =
|
|
6981
|
+
if (existsSync7(this.outboxPath)) {
|
|
6982
|
+
const txt = readFileSync5(this.outboxPath, "utf8");
|
|
6858
6983
|
for (const line of txt.split("\n")) {
|
|
6859
6984
|
const s = line.trim();
|
|
6860
6985
|
if (!s) continue;
|
|
@@ -6874,10 +6999,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
6874
6999
|
// eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
|
|
6875
7000
|
// переприсваиваем локально (id событий — только порядок, не корреляция с продом).
|
|
6876
7001
|
replayGlobalEvents() {
|
|
6877
|
-
if (!
|
|
7002
|
+
if (!existsSync7(this.stormPath)) return;
|
|
6878
7003
|
let txt;
|
|
6879
7004
|
try {
|
|
6880
|
-
txt =
|
|
7005
|
+
txt = readFileSync5(this.stormPath, "utf8");
|
|
6881
7006
|
} catch {
|
|
6882
7007
|
return;
|
|
6883
7008
|
}
|
|
@@ -7354,27 +7479,41 @@ var SwarmClient = class _SwarmClient {
|
|
|
7354
7479
|
* Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
|
|
7355
7480
|
* бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
|
|
7356
7481
|
* дороже.
|
|
7482
|
+
*
|
|
7483
|
+
* TASWM-486: ОТКАЗ ПРОДА — НЕ ОТВЕТ. Раньше `isError` засчитывался как «спросили», и
|
|
7484
|
+
* следующая попытка шла только через TTL; при УСТОЙЧИВОМ отказе (прод старее демона,
|
|
7485
|
+
* просроченный скоуп токена демона, сбой на батче) вердикта не появлялось никогда —
|
|
7486
|
+
* зависимость навсегда оставалась `pending`, а ждущий прогон стоял при свободной машине
|
|
7487
|
+
* до ручного `wake_run`. Теперь отказ помнится отдельно (`depsUnanswered`): переспрос идёт
|
|
7488
|
+
* по короткому нарастающему backoff, а сам факт «прод молчит» становится видимым —
|
|
7489
|
+
* причина отказа спавна называет его вслух, и на затянувшуюся слепоту звенит аномалия.
|
|
7357
7490
|
*/
|
|
7358
7491
|
async refreshDeps(refs, nowMs = Date.now()) {
|
|
7359
7492
|
const ask = [...new Set(refs)].filter((ref) => {
|
|
7360
7493
|
if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
|
|
7361
7494
|
const asked = this.depsAskedAt.get(ref);
|
|
7362
|
-
|
|
7495
|
+
if (asked === void 0) return true;
|
|
7496
|
+
return nowMs - asked >= this.depsRetryDelay(ref);
|
|
7363
7497
|
});
|
|
7364
7498
|
if (!ask.length) return;
|
|
7365
7499
|
for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
|
|
7366
7500
|
let res;
|
|
7367
7501
|
try {
|
|
7368
7502
|
res = await this.call("swarm_deps_status", { refs: ask });
|
|
7369
|
-
} catch {
|
|
7503
|
+
} catch (e) {
|
|
7370
7504
|
for (const ref of ask) this.depsAskedAt.delete(ref);
|
|
7505
|
+
this.noteDepsUnanswered(ask, nowMs, String(e));
|
|
7506
|
+
return;
|
|
7507
|
+
}
|
|
7508
|
+
if (res?.isError) {
|
|
7509
|
+
this.noteDepsUnanswered(ask, nowMs, toolErrorText(res));
|
|
7371
7510
|
return;
|
|
7372
7511
|
}
|
|
7373
|
-
if (res?.isError) return;
|
|
7374
7512
|
const deps = res.structuredContent?.deps ?? [];
|
|
7375
7513
|
for (const d of deps) {
|
|
7376
7514
|
const ref = typeof d.ref === "string" ? d.ref : null;
|
|
7377
7515
|
if (!ref) continue;
|
|
7516
|
+
this.depsUnanswered.delete(ref);
|
|
7378
7517
|
const outcome = String(d.outcome ?? "pending");
|
|
7379
7518
|
this.depsProd.set(ref, {
|
|
7380
7519
|
outcome,
|
|
@@ -7386,6 +7525,75 @@ var SwarmClient = class _SwarmClient {
|
|
|
7386
7525
|
if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
|
|
7387
7526
|
}
|
|
7388
7527
|
}
|
|
7528
|
+
/**
|
|
7529
|
+
* Через сколько переспрашивать ссылку (TASWM-486). Пауза зависит от того, ЧЕГО мы ждём —
|
|
7530
|
+
* и сколько стоит опоздать с ответом:
|
|
7531
|
+
*
|
|
7532
|
+
* • ПРОД ОТКАЗАЛ (isError / не дозвонились) — короткий backoff с удвоением (5с →
|
|
7533
|
+
* потолок TTL). Ждём, что прод вообще начнёт отвечать; до тех пор вердикта нет вовсе.
|
|
7534
|
+
* • БЛОКЕР ЖИВ либо вердикта ещё не было (`pending`) — КАЖДЫЙ ТИК. Это единственное
|
|
7535
|
+
* состояние, в котором ждущий прогон ЖЖЁТ ВРЕМЯ: блокер может закрыться в любую
|
|
7536
|
+
* секунду, и до TASWM-486 минута TTL превращалась в минуту простоя при свободной
|
|
7537
|
+
* машине (21.08: «стартует сам в пределах одного-двух тиков» не выполнялось даже
|
|
7538
|
+
* когда прод отвечал исправно). Цена — ОДИН батч-вызов на тик (все ссылки очереди
|
|
7539
|
+
* идут одним запросом, см. refreshDeps), и только пока кто-то реально ждёт.
|
|
7540
|
+
* • ЗАВИСИМОСТЬ УЖЕ СНЯТА неуспехом (`exhausted`) или блокера не знает никто
|
|
7541
|
+
* (`unknown`) — штатный TTL: прогон этими вердиктами больше не задерживается
|
|
7542
|
+
* (`exhausted`) либо не поедет в любом случае (`unknown`, там своя аномалия), и
|
|
7543
|
+
* переспрос нужен только на случай, что оператор переиграет блокер новым прогоном.
|
|
7544
|
+
* • `done` сюда не доходит вовсе — монотонен, отфильтрован раньше.
|
|
7545
|
+
*/
|
|
7546
|
+
depsRetryDelay(ref) {
|
|
7547
|
+
const fail = this.depsUnanswered.get(ref);
|
|
7548
|
+
if (fail) {
|
|
7549
|
+
const backoff = _SwarmClient.DEPS_RETRY_BASE_MS * 2 ** Math.min(fail.attempts - 1, 10);
|
|
7550
|
+
return Math.min(backoff, _SwarmClient.DEPS_TTL_MS);
|
|
7551
|
+
}
|
|
7552
|
+
const known = this.depsProd.get(ref);
|
|
7553
|
+
if (!known || known.outcome === "pending") return 0;
|
|
7554
|
+
return _SwarmClient.DEPS_TTL_MS;
|
|
7555
|
+
}
|
|
7556
|
+
/** Запомнить, что прод по этим ссылкам не ответил (см. depsUnanswered / TASWM-486). */
|
|
7557
|
+
noteDepsUnanswered(refs, nowMs, error) {
|
|
7558
|
+
for (const ref of refs) {
|
|
7559
|
+
const prev = this.depsUnanswered.get(ref);
|
|
7560
|
+
this.depsUnanswered.set(ref, {
|
|
7561
|
+
ref,
|
|
7562
|
+
slug: this.depSlug.get(ref) ?? ref,
|
|
7563
|
+
// `since` — начало ЭПИЗОДА слепоты, а не последняя попытка: именно возраст эпизода
|
|
7564
|
+
// отвечает на вопрос «сколько прогон уже стоит вслепую».
|
|
7565
|
+
since: prev?.since ?? nowMs,
|
|
7566
|
+
attempts: (prev?.attempts ?? 0) + 1,
|
|
7567
|
+
error
|
|
7568
|
+
});
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
/**
|
|
7572
|
+
* TASWM-486: ссылки `dependsOn`, по которым ВЕРДИКТА ПРОДА НЕТ — не «блокер жив», а «мы
|
|
7573
|
+
* не знаем, жив ли он». Снаружи оба выглядели одинаково (`pending`) и вели себя одинаково
|
|
7574
|
+
* молча, а разница ровно в том, дождётся ли ждущий чего-нибудь сам: живой блокер
|
|
7575
|
+
* закончится, а неотвечающий прод — нет, и прогон стоит до ручной побудки.
|
|
7576
|
+
*
|
|
7577
|
+
* Что сюда НЕ попадает и почему:
|
|
7578
|
+
* • прод отвечает — слепоты нет вовсе (запись стирается первым же ответом);
|
|
7579
|
+
* • последний вердикт уже СНЯЛ зависимость (`met`) — прогон ею не задерживается,
|
|
7580
|
+
* свежесть ответа ничего не решает;
|
|
7581
|
+
* • последний вердикт — `unknown`: это территория `dep_missing`, у неё и текст
|
|
7582
|
+
* точнее («блокера не знает никто»), и лечение другое.
|
|
7583
|
+
* А вот УСТАРЕВШИЙ `pending` при замолчавшем проде — как раз слепота: когда-то блокер
|
|
7584
|
+
* был жив, с тех пор он мог закрыться, и узнать это не у кого.
|
|
7585
|
+
*/
|
|
7586
|
+
depsUnconfirmed(refs) {
|
|
7587
|
+
const out = [];
|
|
7588
|
+
for (const ref of new Set(refs)) {
|
|
7589
|
+
const known = this.depsProd.get(ref);
|
|
7590
|
+
if (known?.met || known?.outcome === "unknown") continue;
|
|
7591
|
+
const fail = this.depsUnanswered.get(ref);
|
|
7592
|
+
if (!fail) continue;
|
|
7593
|
+
out.push({ ...fail, slug: this.depSlug.get(ref) ?? fail.slug });
|
|
7594
|
+
}
|
|
7595
|
+
return out;
|
|
7596
|
+
}
|
|
7389
7597
|
/**
|
|
7390
7598
|
* TASWM-300: точечно перечитать прогон из прода — см. докблок SwarmStore.refreshRun.
|
|
7391
7599
|
* `swarm_list_runs` фильтруем по задаче прогона (taskId принимает человеко-слаг) и берём
|
|
@@ -7702,7 +7910,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7702
7910
|
* транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
|
|
7703
7911
|
*/
|
|
7704
7912
|
reconcileRuntime(opts) {
|
|
7705
|
-
const wtExists = opts.worktreeExists ??
|
|
7913
|
+
const wtExists = opts.worktreeExists ?? existsSync7;
|
|
7706
7914
|
const pidByWorktree = /* @__PURE__ */ new Map();
|
|
7707
7915
|
try {
|
|
7708
7916
|
for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
|
|
@@ -8895,13 +9103,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
8895
9103
|
}
|
|
8896
9104
|
persistPending() {
|
|
8897
9105
|
try {
|
|
8898
|
-
|
|
9106
|
+
writeFileSync5(this.outboxPath, this.pending.map((e) => JSON.stringify(e)).join("\n") + (this.pending.length ? "\n" : ""));
|
|
8899
9107
|
} catch {
|
|
8900
9108
|
}
|
|
8901
9109
|
}
|
|
8902
9110
|
persistMap() {
|
|
8903
9111
|
try {
|
|
8904
|
-
|
|
9112
|
+
writeFileSync5(this.mapPath, JSON.stringify(Object.fromEntries(this.prodIdByKey)));
|
|
8905
9113
|
} catch {
|
|
8906
9114
|
}
|
|
8907
9115
|
}
|
|
@@ -8971,7 +9179,7 @@ function tickPulse(health, now, intervalMs) {
|
|
|
8971
9179
|
}
|
|
8972
9180
|
|
|
8973
9181
|
// src/logRotate.ts
|
|
8974
|
-
import { copyFileSync, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as
|
|
9182
|
+
import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
|
|
8975
9183
|
var realIo = {
|
|
8976
9184
|
size: (path) => {
|
|
8977
9185
|
try {
|
|
@@ -8980,11 +9188,11 @@ var realIo = {
|
|
|
8980
9188
|
return null;
|
|
8981
9189
|
}
|
|
8982
9190
|
},
|
|
8983
|
-
copy:
|
|
9191
|
+
copy: copyFileSync2,
|
|
8984
9192
|
rename: renameSync5,
|
|
8985
9193
|
truncate: (path) => truncateSync(path, 0),
|
|
8986
9194
|
unlink: unlinkSync2,
|
|
8987
|
-
exists:
|
|
9195
|
+
exists: existsSync8
|
|
8988
9196
|
};
|
|
8989
9197
|
var realOwnerIo = {
|
|
8990
9198
|
statFd: (fd) => {
|
|
@@ -9111,13 +9319,13 @@ async function buildRepoRegistry(paths) {
|
|
|
9111
9319
|
}
|
|
9112
9320
|
|
|
9113
9321
|
// src/repoMemory.ts
|
|
9114
|
-
import { readFileSync as
|
|
9115
|
-
import { join as
|
|
9322
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
9323
|
+
import { join as join10 } from "path";
|
|
9116
9324
|
var FILE = "repos.json";
|
|
9117
9325
|
function loadRememberedRepos(home) {
|
|
9118
9326
|
let raw;
|
|
9119
9327
|
try {
|
|
9120
|
-
raw =
|
|
9328
|
+
raw = readFileSync8(join10(home, FILE), "utf8");
|
|
9121
9329
|
} catch {
|
|
9122
9330
|
return [];
|
|
9123
9331
|
}
|
|
@@ -9132,7 +9340,7 @@ function loadRememberedRepos(home) {
|
|
|
9132
9340
|
function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
|
|
9133
9341
|
const clean = paths.filter((p) => p.trim().length > 0);
|
|
9134
9342
|
if (clean.length === 0) return;
|
|
9135
|
-
|
|
9343
|
+
writeFileSync8(join10(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
|
|
9136
9344
|
`);
|
|
9137
9345
|
}
|
|
9138
9346
|
function pickRepoPaths(args) {
|
|
@@ -9146,13 +9354,13 @@ function pickRepoPaths(args) {
|
|
|
9146
9354
|
init_src();
|
|
9147
9355
|
init_config();
|
|
9148
9356
|
init_linkFailure();
|
|
9149
|
-
import { homedir as
|
|
9150
|
-
import { readFileSync as
|
|
9151
|
-
import { isAbsolute, join as
|
|
9357
|
+
import { homedir as homedir4 } from "os";
|
|
9358
|
+
import { readFileSync as readFileSync10, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
9359
|
+
import { isAbsolute, join as join12, resolve as resolve2 } from "path";
|
|
9152
9360
|
|
|
9153
9361
|
// src/repoScan.ts
|
|
9154
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
9155
|
-
import { join as
|
|
9362
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
9363
|
+
import { join as join11 } from "path";
|
|
9156
9364
|
var SKIP = /* @__PURE__ */ new Set([
|
|
9157
9365
|
"node_modules",
|
|
9158
9366
|
"vendor",
|
|
@@ -9182,7 +9390,7 @@ function parseOriginFromGitConfig(text) {
|
|
|
9182
9390
|
}
|
|
9183
9391
|
function originOf(dir) {
|
|
9184
9392
|
try {
|
|
9185
|
-
return parseOriginFromGitConfig(
|
|
9393
|
+
return parseOriginFromGitConfig(readFileSync9(join11(dir, ".git", "config"), "utf8"));
|
|
9186
9394
|
} catch {
|
|
9187
9395
|
return null;
|
|
9188
9396
|
}
|
|
@@ -9217,7 +9425,7 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
9217
9425
|
}
|
|
9218
9426
|
for (const name of entries) {
|
|
9219
9427
|
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
9220
|
-
const child =
|
|
9428
|
+
const child = join11(dir, name);
|
|
9221
9429
|
try {
|
|
9222
9430
|
if (!statSync6(child).isDirectory()) continue;
|
|
9223
9431
|
} catch {
|
|
@@ -9239,13 +9447,13 @@ function defaultScanRoots(home, knownPaths = []) {
|
|
|
9239
9447
|
if (parent && parent !== p) add(parent);
|
|
9240
9448
|
}
|
|
9241
9449
|
for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
|
|
9242
|
-
add(
|
|
9450
|
+
add(join11(home, name));
|
|
9243
9451
|
}
|
|
9244
9452
|
return out;
|
|
9245
9453
|
}
|
|
9246
9454
|
|
|
9247
9455
|
// src/repoDiscovery.ts
|
|
9248
|
-
function resolveScanRoots(raw, home =
|
|
9456
|
+
function resolveScanRoots(raw, home = homedir4(), knownPaths = []) {
|
|
9249
9457
|
const v = (raw ?? "").trim();
|
|
9250
9458
|
if (!v) return defaultScanRoots(home, knownPaths);
|
|
9251
9459
|
if (["off", "none", "0"].includes(v.toLowerCase())) return [];
|
|
@@ -9253,7 +9461,7 @@ function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
|
|
|
9253
9461
|
for (const part of v.split(",")) {
|
|
9254
9462
|
const p = part.trim();
|
|
9255
9463
|
if (!p) continue;
|
|
9256
|
-
const abs = p === "~" ? home : p.startsWith("~/") ?
|
|
9464
|
+
const abs = p === "~" ? home : p.startsWith("~/") ? join12(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
|
|
9257
9465
|
if (!out.includes(abs)) out.push(abs);
|
|
9258
9466
|
}
|
|
9259
9467
|
return out;
|
|
@@ -9467,7 +9675,7 @@ function repoMapRows(state) {
|
|
|
9467
9675
|
}
|
|
9468
9676
|
function loadRepoMapCache(path) {
|
|
9469
9677
|
try {
|
|
9470
|
-
const raw = JSON.parse(
|
|
9678
|
+
const raw = JSON.parse(readFileSync10(path, "utf8"));
|
|
9471
9679
|
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
9472
9680
|
return {
|
|
9473
9681
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
@@ -9482,7 +9690,7 @@ function loadRepoMapCache(path) {
|
|
|
9482
9690
|
function writeRepoMapCache(path, entries, scannedAt = stamp()) {
|
|
9483
9691
|
try {
|
|
9484
9692
|
const tmp = `${path}.tmp`;
|
|
9485
|
-
|
|
9693
|
+
writeFileSync9(tmp, JSON.stringify({ at: stamp(), scannedAt, entries }, null, 2));
|
|
9486
9694
|
renameSync6(tmp, path);
|
|
9487
9695
|
} catch {
|
|
9488
9696
|
}
|
|
@@ -9490,17 +9698,17 @@ function writeRepoMapCache(path, entries, scannedAt = stamp()) {
|
|
|
9490
9698
|
|
|
9491
9699
|
// src/hostIdentity.ts
|
|
9492
9700
|
import { hostname } from "os";
|
|
9493
|
-
import { existsSync as
|
|
9494
|
-
import { join as
|
|
9701
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
9702
|
+
import { join as join13 } from "path";
|
|
9495
9703
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9496
9704
|
function hostIdentityPath(home) {
|
|
9497
|
-
return
|
|
9705
|
+
return join13(home, "host.json");
|
|
9498
9706
|
}
|
|
9499
9707
|
function loadHostIdentity(deps) {
|
|
9500
|
-
const exists = deps.exists ??
|
|
9501
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
9708
|
+
const exists = deps.exists ?? existsSync9;
|
|
9709
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync11(p, "utf8"));
|
|
9502
9710
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
9503
|
-
|
|
9711
|
+
writeFileSync10(`${p}.tmp`, data);
|
|
9504
9712
|
renameSync7(`${p}.tmp`, p);
|
|
9505
9713
|
});
|
|
9506
9714
|
const host = deps.hostname ?? hostname;
|
|
@@ -9683,15 +9891,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
9683
9891
|
|
|
9684
9892
|
// src/launcher.ts
|
|
9685
9893
|
import { spawn } from "child_process";
|
|
9686
|
-
import { closeSync as closeSync2, existsSync as
|
|
9894
|
+
import { closeSync as closeSync2, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
|
|
9687
9895
|
import { createRequire } from "module";
|
|
9688
|
-
import { homedir as
|
|
9689
|
-
import { dirname as
|
|
9690
|
-
import { fileURLToPath } from "url";
|
|
9896
|
+
import { homedir as homedir5 } from "os";
|
|
9897
|
+
import { dirname as dirname3, join as join15 } from "path";
|
|
9898
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9691
9899
|
|
|
9692
9900
|
// src/droneHome.ts
|
|
9693
|
-
import { lstatSync, mkdirSync as
|
|
9694
|
-
import { join as
|
|
9901
|
+
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
9902
|
+
import { join as join14, relative, sep } from "path";
|
|
9695
9903
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
9696
9904
|
function droneHomeDeny(env = process.env) {
|
|
9697
9905
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -9709,26 +9917,26 @@ function buildDroneHome(opts) {
|
|
|
9709
9917
|
const { realHome, droneHome } = opts;
|
|
9710
9918
|
const deny = opts.deny ?? droneHomeDeny();
|
|
9711
9919
|
const platform = opts.platform ?? process.platform;
|
|
9712
|
-
|
|
9920
|
+
mkdirSync7(droneHome, { recursive: true, mode: 448 });
|
|
9713
9921
|
const wanted = new Set(mirroredEntries(readdirSync7(realHome), deny, platform));
|
|
9714
9922
|
const rel = relative(realHome, droneHome);
|
|
9715
9923
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
9716
9924
|
for (const existing of readdirSync7(droneHome)) {
|
|
9717
9925
|
if (wanted.has(existing)) continue;
|
|
9718
|
-
const p =
|
|
9926
|
+
const p = join14(droneHome, existing);
|
|
9719
9927
|
try {
|
|
9720
9928
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
9721
9929
|
} catch {
|
|
9722
9930
|
}
|
|
9723
9931
|
}
|
|
9724
9932
|
for (const name of wanted) {
|
|
9725
|
-
const link =
|
|
9933
|
+
const link = join14(droneHome, name);
|
|
9726
9934
|
try {
|
|
9727
9935
|
lstatSync(link);
|
|
9728
9936
|
continue;
|
|
9729
9937
|
} catch {
|
|
9730
9938
|
}
|
|
9731
|
-
const target =
|
|
9939
|
+
const target = join14(realHome, name);
|
|
9732
9940
|
let isDir;
|
|
9733
9941
|
try {
|
|
9734
9942
|
isDir = statSync7(target).isDirectory();
|
|
@@ -9749,7 +9957,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
9749
9957
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
9750
9958
|
];
|
|
9751
9959
|
function realPathEnvPins(realHome) {
|
|
9752
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
9960
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join14(realHome, ...segments)]));
|
|
9753
9961
|
}
|
|
9754
9962
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
9755
9963
|
return [
|
|
@@ -9805,7 +10013,7 @@ function droneTestEnv(droneCap, base3 = process.env, host = hostForkSpec()) {
|
|
|
9805
10013
|
|
|
9806
10014
|
// src/swarmModels.ts
|
|
9807
10015
|
init_src();
|
|
9808
|
-
function
|
|
10016
|
+
function pick2(v) {
|
|
9809
10017
|
const s = typeof v === "string" ? v.trim() : "";
|
|
9810
10018
|
return s.length > 0 ? s : null;
|
|
9811
10019
|
}
|
|
@@ -9821,10 +10029,10 @@ function forKind(kind, swarm, norm) {
|
|
|
9821
10029
|
}
|
|
9822
10030
|
function resolveRunModelRef(run, swarm, env = process.env) {
|
|
9823
10031
|
const configured = parseModelRef(
|
|
9824
|
-
|
|
10032
|
+
pick2(run.model) ?? forKind(run.kind, swarm, pick2) ?? pick2(env.SWARM_DRONE_MODEL)
|
|
9825
10033
|
);
|
|
9826
10034
|
if (configured) return configured;
|
|
9827
|
-
const providerId =
|
|
10035
|
+
const providerId = pick2(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
|
|
9828
10036
|
return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
|
|
9829
10037
|
}
|
|
9830
10038
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
@@ -9861,7 +10069,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
9861
10069
|
const env = { ...base3 };
|
|
9862
10070
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
9863
10071
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
9864
|
-
env.TURBO_CACHE_DIR =
|
|
10072
|
+
env.TURBO_CACHE_DIR = join15(worktree, ".turbo", "cache");
|
|
9865
10073
|
}
|
|
9866
10074
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
9867
10075
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -9889,18 +10097,18 @@ var DAEMON_STATE_ENV = [
|
|
|
9889
10097
|
"TASKLESS_TOKEN"
|
|
9890
10098
|
];
|
|
9891
10099
|
function bundledRoleDir(kind) {
|
|
9892
|
-
return
|
|
10100
|
+
return join15(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
9893
10101
|
}
|
|
9894
10102
|
function roleDir(worktree, kind) {
|
|
9895
|
-
return
|
|
10103
|
+
return join15(worktree, ".claude", "roles", kind);
|
|
9896
10104
|
}
|
|
9897
10105
|
function roleDirs(worktree, kind) {
|
|
9898
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
10106
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync10(dir));
|
|
9899
10107
|
}
|
|
9900
10108
|
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
9901
10109
|
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
9902
10110
|
if (!dir) throw new Error("\u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 (SwarmStore.runLogKeys)");
|
|
9903
|
-
|
|
10111
|
+
mkdirSync8(dir, { recursive: true });
|
|
9904
10112
|
let files = [];
|
|
9905
10113
|
try {
|
|
9906
10114
|
files = readdirSync8(dir);
|
|
@@ -9911,7 +10119,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
9911
10119
|
const m = /^(\d+)\.json/.exec(f);
|
|
9912
10120
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
9913
10121
|
}
|
|
9914
|
-
return
|
|
10122
|
+
return join15(dir, `${next}.json`);
|
|
9915
10123
|
}
|
|
9916
10124
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
9917
10125
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -9992,11 +10200,11 @@ function makeLauncher(cfg) {
|
|
|
9992
10200
|
} catch (e) {
|
|
9993
10201
|
console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0441\u0438\u043B\u0438\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
|
|
9994
10202
|
}
|
|
9995
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
10203
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir5();
|
|
9996
10204
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
9997
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
10205
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync10(join15(cfg.droneIsolation.swarmHome, "token"))) {
|
|
9998
10206
|
console.error(
|
|
9999
|
-
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${
|
|
10207
|
+
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join15(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
|
|
10000
10208
|
);
|
|
10001
10209
|
}
|
|
10002
10210
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10057,8 +10265,8 @@ function makeLauncher(cfg) {
|
|
|
10057
10265
|
init_procCleanup();
|
|
10058
10266
|
|
|
10059
10267
|
// src/cost.ts
|
|
10060
|
-
import { readFileSync as
|
|
10061
|
-
import { join as
|
|
10268
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
|
|
10269
|
+
import { join as join16 } from "path";
|
|
10062
10270
|
init_executors();
|
|
10063
10271
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
10064
10272
|
function unprocessedLogFiles(dir) {
|
|
@@ -10080,10 +10288,10 @@ function accountRunCost(deps, run) {
|
|
|
10080
10288
|
let durationMs = 0;
|
|
10081
10289
|
const processed = [];
|
|
10082
10290
|
for (const f of files) {
|
|
10083
|
-
const full =
|
|
10291
|
+
const full = join16(dir, f);
|
|
10084
10292
|
let raw = null;
|
|
10085
10293
|
try {
|
|
10086
|
-
raw =
|
|
10294
|
+
raw = readFileSync12(full, "utf8");
|
|
10087
10295
|
} catch {
|
|
10088
10296
|
continue;
|
|
10089
10297
|
}
|
|
@@ -10132,8 +10340,8 @@ function makeCostSweeper(deps) {
|
|
|
10132
10340
|
}
|
|
10133
10341
|
|
|
10134
10342
|
// src/runOutcome.ts
|
|
10135
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
10136
|
-
import { join as
|
|
10343
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync13 } from "fs";
|
|
10344
|
+
import { join as join17 } from "path";
|
|
10137
10345
|
init_executors();
|
|
10138
10346
|
function endedItself(outcome) {
|
|
10139
10347
|
return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
|
|
@@ -10160,7 +10368,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
|
10160
10368
|
}
|
|
10161
10369
|
if (!latest) return null;
|
|
10162
10370
|
try {
|
|
10163
|
-
return
|
|
10371
|
+
return readFileSync13(join17(dir, latest.name), "utf8");
|
|
10164
10372
|
} catch {
|
|
10165
10373
|
return null;
|
|
10166
10374
|
}
|
|
@@ -10193,20 +10401,20 @@ init_src();
|
|
|
10193
10401
|
init_git();
|
|
10194
10402
|
|
|
10195
10403
|
// src/deps.ts
|
|
10196
|
-
import { existsSync as
|
|
10197
|
-
import { join as
|
|
10404
|
+
import { existsSync as existsSync12, readdirSync as readdirSync12, readFileSync as readFileSync14 } from "fs";
|
|
10405
|
+
import { join as join20 } from "path";
|
|
10198
10406
|
import { execa } from "execa";
|
|
10199
10407
|
|
|
10200
10408
|
// src/prepareLock.ts
|
|
10201
10409
|
import { createHash } from "crypto";
|
|
10202
10410
|
import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
|
|
10203
|
-
import { join as
|
|
10411
|
+
import { join as join19 } from "path";
|
|
10204
10412
|
import { tmpdir } from "os";
|
|
10205
10413
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
10206
10414
|
var POLL_MS = 300;
|
|
10207
10415
|
var STALE_MS = 20 * 6e4;
|
|
10208
10416
|
function machineLockPath(key, dir = tmpdir()) {
|
|
10209
|
-
return
|
|
10417
|
+
return join19(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
10210
10418
|
}
|
|
10211
10419
|
function defaultTryCreate(path, content) {
|
|
10212
10420
|
try {
|
|
@@ -10280,7 +10488,7 @@ async function runCommand(cmd, worktree) {
|
|
|
10280
10488
|
}
|
|
10281
10489
|
function safeReadText(p) {
|
|
10282
10490
|
try {
|
|
10283
|
-
return
|
|
10491
|
+
return readFileSync14(p, "utf8");
|
|
10284
10492
|
} catch {
|
|
10285
10493
|
return null;
|
|
10286
10494
|
}
|
|
@@ -10320,21 +10528,21 @@ function overridePlan(override) {
|
|
|
10320
10528
|
};
|
|
10321
10529
|
}
|
|
10322
10530
|
function detectPackageManager(worktree, io = {}) {
|
|
10323
|
-
const exists = io.exists ??
|
|
10531
|
+
const exists = io.exists ?? existsSync12;
|
|
10324
10532
|
const readText = io.readText ?? safeReadText;
|
|
10325
|
-
const hasPkgJson = exists(
|
|
10533
|
+
const hasPkgJson = exists(join20(worktree, "package.json"));
|
|
10326
10534
|
if (!hasPkgJson) {
|
|
10327
|
-
if (exists(
|
|
10535
|
+
if (exists(join20(worktree, "deno.json")) || exists(join20(worktree, "deno.jsonc")) || exists(join20(worktree, "deno.lock"))) return "deno";
|
|
10328
10536
|
return null;
|
|
10329
10537
|
}
|
|
10330
|
-
const pkg = readJson(readText,
|
|
10538
|
+
const pkg = readJson(readText, join20(worktree, "package.json"));
|
|
10331
10539
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
10332
10540
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
10333
|
-
if (exists(
|
|
10334
|
-
if (exists(
|
|
10335
|
-
if (exists(
|
|
10336
|
-
if (exists(
|
|
10337
|
-
if (exists(
|
|
10541
|
+
if (exists(join20(worktree, "bun.lock")) || exists(join20(worktree, "bun.lockb"))) return "bun";
|
|
10542
|
+
if (exists(join20(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
10543
|
+
if (exists(join20(worktree, "yarn.lock"))) return "yarn";
|
|
10544
|
+
if (exists(join20(worktree, "package-lock.json"))) return "npm";
|
|
10545
|
+
if (exists(join20(worktree, "deno.lock"))) return "deno";
|
|
10338
10546
|
return "npm";
|
|
10339
10547
|
}
|
|
10340
10548
|
function jsInstallCommand(pm) {
|
|
@@ -10359,7 +10567,10 @@ var ECOSYSTEM_MARKERS = [
|
|
|
10359
10567
|
// pyproject сам по себе не говорит, кем ставить — решает лок-файл рядом.
|
|
10360
10568
|
command: null,
|
|
10361
10569
|
refine: [
|
|
10362
|
-
|
|
10570
|
+
// --frozen: ставим ровно по локу и не переписываем его (TAS-572). Без
|
|
10571
|
+
// флага uv доресолвливает лок под pyproject прямо в worktree дрона, и
|
|
10572
|
+
// чужой дифф на полторы сотни строк уезжает в PR по несвязанной задаче.
|
|
10573
|
+
{ file: "uv.lock", ecosystem: "python/uv", command: "uv sync --frozen" },
|
|
10363
10574
|
{ file: "poetry.lock", ecosystem: "python/poetry", command: "poetry install" },
|
|
10364
10575
|
{ file: "requirements.txt", ecosystem: "python/pip", command: "pip install -r requirements.txt" }
|
|
10365
10576
|
]
|
|
@@ -10387,7 +10598,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
10387
10598
|
// конфигурация — дело сборки, не подготовки
|
|
10388
10599
|
];
|
|
10389
10600
|
function detectPlan(worktree, io) {
|
|
10390
|
-
const exists = io.exists ??
|
|
10601
|
+
const exists = io.exists ?? existsSync12;
|
|
10391
10602
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
10392
10603
|
const pm = detectPackageManager(worktree, io);
|
|
10393
10604
|
if (pm) {
|
|
@@ -10395,7 +10606,7 @@ function detectPlan(worktree, io) {
|
|
|
10395
10606
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
10396
10607
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
10397
10608
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
10398
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
10609
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join20(worktree, "node_modules"), source: "detect" }
|
|
10399
10610
|
];
|
|
10400
10611
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
10401
10612
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -10404,12 +10615,12 @@ function detectPlan(worktree, io) {
|
|
|
10404
10615
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
10405
10616
|
}
|
|
10406
10617
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
10407
|
-
const hit = marker.files.some((f) => exists(
|
|
10618
|
+
const hit = marker.files.some((f) => exists(join20(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
10408
10619
|
if (!hit) continue;
|
|
10409
10620
|
let ecosystem = marker.ecosystem;
|
|
10410
10621
|
let command = marker.command;
|
|
10411
10622
|
for (const refine of marker.refine ?? []) {
|
|
10412
|
-
if (!exists(
|
|
10623
|
+
if (!exists(join20(worktree, refine.file))) continue;
|
|
10413
10624
|
ecosystem = refine.ecosystem;
|
|
10414
10625
|
command = refine.command;
|
|
10415
10626
|
break;
|
|
@@ -10466,12 +10677,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
10466
10677
|
function ciWorkflowPlan(worktree, io) {
|
|
10467
10678
|
const readText = io.readText ?? safeReadText;
|
|
10468
10679
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
10469
|
-
const dir =
|
|
10680
|
+
const dir = join20(worktree, ".github", "workflows");
|
|
10470
10681
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
10471
10682
|
const seen = /* @__PURE__ */ new Set();
|
|
10472
10683
|
const commands = [];
|
|
10473
10684
|
for (const name of files.sort()) {
|
|
10474
|
-
const yaml = readText(
|
|
10685
|
+
const yaml = readText(join20(dir, name));
|
|
10475
10686
|
if (yaml === null) continue;
|
|
10476
10687
|
for (const raw of workflowRunLines(yaml)) {
|
|
10477
10688
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -10486,7 +10697,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
10486
10697
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
10487
10698
|
}
|
|
10488
10699
|
function workspaceGlobs(worktree, readText) {
|
|
10489
|
-
const yaml = readText(
|
|
10700
|
+
const yaml = readText(join20(worktree, "pnpm-workspace.yaml"));
|
|
10490
10701
|
if (yaml !== null) {
|
|
10491
10702
|
const globs = [];
|
|
10492
10703
|
let inPackages = false;
|
|
@@ -10506,7 +10717,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
10506
10717
|
}
|
|
10507
10718
|
if (globs.length) return globs;
|
|
10508
10719
|
}
|
|
10509
|
-
const pkg = readJson(readText,
|
|
10720
|
+
const pkg = readJson(readText, join20(worktree, "package.json"));
|
|
10510
10721
|
const ws = pkg?.workspaces;
|
|
10511
10722
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
10512
10723
|
return list.filter((g) => typeof g === "string");
|
|
@@ -10517,14 +10728,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
10517
10728
|
if (glob.startsWith("!")) continue;
|
|
10518
10729
|
const star = glob.indexOf("*");
|
|
10519
10730
|
if (star === -1) {
|
|
10520
|
-
const dir =
|
|
10521
|
-
if (io.exists(
|
|
10731
|
+
const dir = join20(worktree, glob);
|
|
10732
|
+
if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
|
|
10522
10733
|
continue;
|
|
10523
10734
|
}
|
|
10524
|
-
const base3 =
|
|
10735
|
+
const base3 = join20(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
10525
10736
|
for (const name of io.listDirs(base3)) {
|
|
10526
|
-
const dir =
|
|
10527
|
-
if (io.exists(
|
|
10737
|
+
const dir = join20(base3, name);
|
|
10738
|
+
if (io.exists(join20(dir, "package.json"))) dirs.add(dir);
|
|
10528
10739
|
}
|
|
10529
10740
|
}
|
|
10530
10741
|
return [...dirs];
|
|
@@ -10545,18 +10756,18 @@ function entryPaths(pkg) {
|
|
|
10545
10756
|
return out;
|
|
10546
10757
|
}
|
|
10547
10758
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
10548
|
-
const exists = io.exists ??
|
|
10759
|
+
const exists = io.exists ?? existsSync12;
|
|
10549
10760
|
const readText = io.readText ?? safeReadText;
|
|
10550
10761
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
10551
10762
|
const globs = workspaceGlobs(worktree, readText);
|
|
10552
10763
|
if (!globs.length) return [];
|
|
10553
10764
|
const pkgs = [];
|
|
10554
10765
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
10555
|
-
const pkg = readJson(readText,
|
|
10766
|
+
const pkg = readJson(readText, join20(dir, "package.json"));
|
|
10556
10767
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
10557
10768
|
const scripts = pkg.scripts ?? {};
|
|
10558
10769
|
const entries = entryPaths(pkg);
|
|
10559
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
10770
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join20(dir, e)));
|
|
10560
10771
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
10561
10772
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
10562
10773
|
}
|
|
@@ -10584,7 +10795,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
10584
10795
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
10585
10796
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
10586
10797
|
for (const file of [".env", ".env.example"]) {
|
|
10587
|
-
const raw = readText(
|
|
10798
|
+
const raw = readText(join20(worktree, file));
|
|
10588
10799
|
if (raw === null) continue;
|
|
10589
10800
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
10590
10801
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -10600,11 +10811,11 @@ function isLocalDatabase(url) {
|
|
|
10600
10811
|
}
|
|
10601
10812
|
}
|
|
10602
10813
|
function prismaPackages(worktree, io) {
|
|
10603
|
-
const exists = io.exists ??
|
|
10814
|
+
const exists = io.exists ?? existsSync12;
|
|
10604
10815
|
const readText = io.readText ?? safeReadText;
|
|
10605
10816
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
10606
10817
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
10607
|
-
return dirs.filter((dir) => exists(
|
|
10818
|
+
return dirs.filter((dir) => exists(join20(dir, "prisma", "schema.prisma")) && listDirs(join20(dir, "prisma", "migrations")).length > 0);
|
|
10608
10819
|
}
|
|
10609
10820
|
function jsExecBin(pm, command) {
|
|
10610
10821
|
switch (pm) {
|
|
@@ -10637,7 +10848,7 @@ function planPrepare(worktree, io = {}) {
|
|
|
10637
10848
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
10638
10849
|
}
|
|
10639
10850
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
10640
|
-
const exists = io.exists ??
|
|
10851
|
+
const exists = io.exists ?? existsSync12;
|
|
10641
10852
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
10642
10853
|
const approve = io.approve ?? (() => true);
|
|
10643
10854
|
const plan = planPrepare(worktree, io);
|
|
@@ -10833,6 +11044,10 @@ function isPreStartFailure(run) {
|
|
|
10833
11044
|
function isSpawnParked(run) {
|
|
10834
11045
|
return (run.stop_reason ?? "").includes(SPAWN_PARK_MARK);
|
|
10835
11046
|
}
|
|
11047
|
+
var UNRETRIABLE_PARK_MARK = "\u043F\u043E\u0432\u0442\u043E\u0440 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442";
|
|
11048
|
+
function isUnretriableParked(run) {
|
|
11049
|
+
return isPreStartFailure(run) && (run.stop_reason ?? "").includes(UNRETRIABLE_PARK_MARK);
|
|
11050
|
+
}
|
|
10836
11051
|
var SPAWN_ATTEMPT_WINDOW_MS = 60 * 6e4;
|
|
10837
11052
|
function preStartAttempts(runs, taskSlug, opts = {}) {
|
|
10838
11053
|
const windowMs = opts.windowMs ?? SPAWN_ATTEMPT_WINDOW_MS;
|
|
@@ -10846,9 +11061,13 @@ function preStartAttempts(runs, taskSlug, opts = {}) {
|
|
|
10846
11061
|
const at = run.created_at ? stampMs(run.created_at) : Number.NaN;
|
|
10847
11062
|
if (Number.isFinite(at) && now - at >= windowMs) break;
|
|
10848
11063
|
n += 1;
|
|
11064
|
+
if (isUnretriableParked(run)) return Math.max(n, PRE_START_ATTEMPT_LIMIT);
|
|
10849
11065
|
}
|
|
10850
11066
|
return n;
|
|
10851
11067
|
}
|
|
11068
|
+
function lastPreStartAttempt(runs, taskSlug) {
|
|
11069
|
+
return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort((a, b) => b.id - a.id)[0] ?? null;
|
|
11070
|
+
}
|
|
10852
11071
|
function unresolvedRepoReason(repo, served) {
|
|
10853
11072
|
const raw = repo.trim();
|
|
10854
11073
|
if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
|
|
@@ -10858,6 +11077,9 @@ function unresolvedRepoReason(repo, served) {
|
|
|
10858
11077
|
function parkReason(attempts, reason) {
|
|
10859
11078
|
return `${PRE_START_MARK} ${attempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
|
|
10860
11079
|
}
|
|
11080
|
+
function parkReasonUnretriable(reason) {
|
|
11081
|
+
return `${PRE_START_MARK}, \u0438 ${UNRETRIABLE_PARK_MARK} \u2014 ${SPAWN_PARK_MARK} \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0442\u0440\u0430\u0442\u044F ${PRE_START_ATTEMPT_LIMIT} \u043F\u043E\u043F\u044B\u0442\u043A\u0438. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
|
|
11082
|
+
}
|
|
10861
11083
|
|
|
10862
11084
|
// src/worktreeReap.ts
|
|
10863
11085
|
init_src();
|
|
@@ -11046,7 +11268,7 @@ async function latestVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
|
|
|
11046
11268
|
return APPROVE_RE.test(m[1]) ? "approve" : "changes";
|
|
11047
11269
|
});
|
|
11048
11270
|
}
|
|
11049
|
-
async function scanPrComments(prUrl, sinceIso, run,
|
|
11271
|
+
async function scanPrComments(prUrl, sinceIso, run, pick4) {
|
|
11050
11272
|
if (!isGithubPrUrl(prUrl)) return null;
|
|
11051
11273
|
let out;
|
|
11052
11274
|
try {
|
|
@@ -11066,7 +11288,7 @@ async function scanPrComments(prUrl, sinceIso, run, pick3) {
|
|
|
11066
11288
|
const c = comments[i];
|
|
11067
11289
|
if (!c?.body || !c.createdAt) continue;
|
|
11068
11290
|
if (Number.isFinite(sinceMs) && Date.parse(c.createdAt) < sinceMs) continue;
|
|
11069
|
-
const verdict =
|
|
11291
|
+
const verdict = pick4(c.body);
|
|
11070
11292
|
if (verdict == null) continue;
|
|
11071
11293
|
return { verdict, createdAt: c.createdAt };
|
|
11072
11294
|
}
|
|
@@ -11243,6 +11465,7 @@ ${logs.join("\n---\n")}` : header;
|
|
|
11243
11465
|
// src/droneServer.ts
|
|
11244
11466
|
init_droneChannel();
|
|
11245
11467
|
init_taskless();
|
|
11468
|
+
init_toolFailure();
|
|
11246
11469
|
init_attachments();
|
|
11247
11470
|
init_shotUpload();
|
|
11248
11471
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -11300,8 +11523,11 @@ init_src();
|
|
|
11300
11523
|
|
|
11301
11524
|
// src/reviewCycle.ts
|
|
11302
11525
|
init_src();
|
|
11526
|
+
function needsFixIteration(run) {
|
|
11527
|
+
return run.review_verdict === "changes" || isNegativeVerifyVerdict(toVerifyVerdict(run.verify_verdict));
|
|
11528
|
+
}
|
|
11303
11529
|
function reviewTransitionPatch(run, prUrl) {
|
|
11304
|
-
const isFixIteration =
|
|
11530
|
+
const isFixIteration = needsFixIteration(run);
|
|
11305
11531
|
if (isFixIteration) {
|
|
11306
11532
|
const cycle = (run.review_cycle ?? 0) + 1;
|
|
11307
11533
|
return {
|
|
@@ -11336,6 +11562,18 @@ function applyReviewTransition(db, run, prUrl) {
|
|
|
11336
11562
|
|
|
11337
11563
|
// src/droneServer.ts
|
|
11338
11564
|
init_src();
|
|
11565
|
+
function readTaskFailureInstruction(slug, failure) {
|
|
11566
|
+
const why = describeToolFailure(failure);
|
|
11567
|
+
if (TOOL_FAILURE_RETRIABLE[failure.code]) {
|
|
11568
|
+
return `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${why}. \u042D\u0442\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E\u043A\u0430 \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u0443\u0436\u0435 \u0432 \u043F\u0440\u043E\u043C\u043F\u0442\u0435, \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 read_task \u0435\u0449\u0451 \u0440\u0430\u0437 \u043F\u043E\u0437\u0436\u0435 (\u043F\u0435\u0440\u0435\u0434 \u0441\u0434\u0430\u0447\u0435\u0439 \u2014 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E).`;
|
|
11569
|
+
}
|
|
11570
|
+
return [
|
|
11571
|
+
`\u26D4 \u0417\u0410\u0414\u0410\u0427\u0423 ${slug} \u041F\u0420\u041E\u0427\u0418\u0422\u0410\u0422\u042C \u041D\u0415\u041B\u042C\u0417\u042F: ${why}.`,
|
|
11572
|
+
"\u041F\u043E\u0432\u0442\u043E\u0440 \u0442\u0443\u0442 \u043D\u0435 \u043F\u043E\u043C\u043E\u0436\u0435\u0442 \u2014 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0432 \u0441\u0435\u0442\u0438, \u043E\u043D\u0430 \u0441\u0430\u043C\u0430 \u043D\u0435 \u0440\u0430\u0437\u043E\u0439\u0434\u0451\u0442\u0441\u044F.",
|
|
11573
|
+
"\u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E \u0441\u043B\u0430\u0433\u0443 \u0438 \u043F\u043E \u043F\u0430\u043C\u044F\u0442\u0438: \u043F\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0442\u044B \u043D\u0435 \u0432\u0438\u0434\u0435\u043B, \u043D\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u0438\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 (TASWM-288).",
|
|
11574
|
+
"\u0421\u043F\u0440\u043E\u0441\u0438 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0447\u0435\u0440\u0435\u0437 ask() \u2014 \u043D\u0430\u0437\u043E\u0432\u0438 \u0435\u043C\u0443 \u044D\u0442\u0443 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0434\u043E\u0441\u043B\u043E\u0432\u043D\u043E \u2014 \u0438 \u0434\u0430\u043B\u044C\u0448\u0435 \u0434\u0435\u043B\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043E\u043D \u043E\u0442\u0432\u0435\u0442\u0438\u0442."
|
|
11575
|
+
].join("\n");
|
|
11576
|
+
}
|
|
11339
11577
|
function freezeInstruction(branch, reason = "\u0435\u0433\u043E \u043F\u0443\u043B\u044C\u0441 (heartbeat) \u0443\u0441\u0442\u0430\u0440\u0435\u043B") {
|
|
11340
11578
|
return [
|
|
11341
11579
|
`\u26D4 OVERMIND \u041D\u0415\u0414\u041E\u0421\u0422\u0423\u041F\u0415\u041D \u2014 ${reason}. \u041D\u0430\u0434\u0437\u043E\u0440\u0430 \u043D\u0430\u0434 \u0442\u043E\u0431\u043E\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435\u0442: \u043D\u0435\u043A\u043E\u043C\u0443`,
|
|
@@ -11433,16 +11671,17 @@ async function buildDroneServer(store, opts = {}) {
|
|
|
11433
11671
|
mismatchNote = `\u26A0\uFE0F \u0434\u0435\u043C\u043E\u043D \u0447\u0438\u0441\u043B\u0438\u0442 \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043A\u0430\u043A \xAB${run.task_slug}\xBB \u2014 \u044D\u0442\u043E \u041D\u0415 \u0442\u0432\u043E\u044F \u0437\u0430\u0434\u0430\u0447\u0430 (TASWM-211). \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D \u0434\u043E\u0432\u0435\u0440\u0435\u043D\u043D\u044B\u0439 slug \u0438\u0437 \u0441\u043F\u0430\u0432\u043D\u0430.`;
|
|
11434
11672
|
}
|
|
11435
11673
|
if (!slug) return { content: [{ type: "text", text: "\u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D" }], isError: true };
|
|
11436
|
-
const
|
|
11437
|
-
if (!
|
|
11438
|
-
const
|
|
11674
|
+
const read = await opts.taskless.getTaskDetailed(slug);
|
|
11675
|
+
if (!read.ok) {
|
|
11676
|
+
const failText = readTaskFailureInstruction(slug, read.failure);
|
|
11439
11677
|
return {
|
|
11440
11678
|
content: [{ type: "text", text: mismatchNote ? `${mismatchNote}
|
|
11441
11679
|
|
|
11442
|
-
${
|
|
11680
|
+
${failText}` : failText }],
|
|
11443
11681
|
isError: true
|
|
11444
11682
|
};
|
|
11445
11683
|
}
|
|
11684
|
+
const task = read.task;
|
|
11446
11685
|
const context = await fetchTaskContext(opts.taskless, task);
|
|
11447
11686
|
const attachments = await prepareTaskAttachments({
|
|
11448
11687
|
worktree: opts.worktree ?? process.cwd(),
|
|
@@ -12062,8 +12301,9 @@ ${report}${shots.warning}`,
|
|
|
12062
12301
|
if (!taskless) return errText2("Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D.");
|
|
12063
12302
|
const hero = await heroRun();
|
|
12064
12303
|
if (!hero) return errText2("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
|
|
12065
|
-
const
|
|
12066
|
-
if (!
|
|
12304
|
+
const read = await taskless.getTaskDetailed(slug);
|
|
12305
|
+
if (!read.ok) return errText2(`\u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${describeToolFailure(read.failure)}.`);
|
|
12306
|
+
const task = read.task;
|
|
12067
12307
|
const ep = await epic(hero.task_slug);
|
|
12068
12308
|
if (!ep || task.parentId !== ep.id) {
|
|
12069
12309
|
return errText2(`${slug} \u2014 \u043D\u0435 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0432\u043E\u0435\u0433\u043E \u044D\u043F\u0438\u043A\u0430 ${hero.task_slug}. \u0414\u0438\u0441\u043F\u0430\u0442\u0447\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u0432\u043E\u0438 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438.`);
|
|
@@ -12576,6 +12816,31 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
12576
12816
|
})
|
|
12577
12817
|
});
|
|
12578
12818
|
}
|
|
12819
|
+
const GROUP_DEP_UNCONFIRMED = {
|
|
12820
|
+
id: "dep_unconfirmed",
|
|
12821
|
+
title: (n) => `${n} ${plural2(n, ["\u043F\u0440\u043E\u0433\u043E\u043D \u0436\u0434\u0451\u0442", "\u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0436\u0434\u0443\u0442", "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0436\u0434\u0443\u0442"])} \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043F\u0440\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442`
|
|
12822
|
+
};
|
|
12823
|
+
for (const run of allRuns) {
|
|
12824
|
+
if (run.status !== "queued") continue;
|
|
12825
|
+
if (!run.depends_on || run.depends_on.length === 0) continue;
|
|
12826
|
+
const blind = db.depsUnconfirmed?.(run.depends_on) ?? [];
|
|
12827
|
+
if (!blind.length) continue;
|
|
12828
|
+
candidates.push({
|
|
12829
|
+
key: `dep_unconfirmed:${run.task_slug}`,
|
|
12830
|
+
// по СЛАГУ, а не по id зеркала (см. dep_missing)
|
|
12831
|
+
minDurationMs: thresholds.depMissingMs,
|
|
12832
|
+
group: GROUP_DEP_UNCONFIRMED,
|
|
12833
|
+
build: () => ({
|
|
12834
|
+
runId: run.id,
|
|
12835
|
+
// Пуш: сам рой из этого не выйдет — вердикт приходит только от прода, а прод
|
|
12836
|
+
// отказывает устойчиво. Лечит человек (обновить прод / перевыдать токен демона).
|
|
12837
|
+
level: "decision",
|
|
12838
|
+
title: runTitle(run, "\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043F\u0440\u043E\u0434 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442"),
|
|
12839
|
+
body: `${droneOf(run)}: \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0442\u043E\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C\u044E ${joinCapped(blind.map((d) => d.slug))}, \u043D\u043E \u043F\u0440\u043E\u0434 \u043D\u0430 swarm_deps_status \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (${blind[0].attempts} \u043F\u043E\u043F\u044B\u0442(\u043E\u043A), \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u2014 ${blind[0].error}). \u0412\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442, \u043F\u043E\u043A\u0430 \u044D\u0442\u043E \u043D\u0435 \u043F\u043E\u0447\u0438\u043D\u0438\u0442\u044C: \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442, \u0430 \u043C\u0430\u0448\u0438\u043D\u0430 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u0430.`,
|
|
12840
|
+
...hrefOf(run.task_url)
|
|
12841
|
+
})
|
|
12842
|
+
});
|
|
12843
|
+
}
|
|
12579
12844
|
if (opts.gateReason && queuedCount > 0) {
|
|
12580
12845
|
const reason = opts.gateReason;
|
|
12581
12846
|
candidates.push({
|
|
@@ -12892,6 +13157,7 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
12892
13157
|
|
|
12893
13158
|
// src/overmind.ts
|
|
12894
13159
|
init_taskless();
|
|
13160
|
+
init_toolFailure();
|
|
12895
13161
|
|
|
12896
13162
|
// src/workspaceAccess.ts
|
|
12897
13163
|
function workspaceAccessLines(input) {
|
|
@@ -13123,29 +13389,27 @@ function makeDroneCapReader(opts) {
|
|
|
13123
13389
|
|
|
13124
13390
|
// src/mergeGate.ts
|
|
13125
13391
|
init_src();
|
|
13392
|
+
init_config();
|
|
13126
13393
|
|
|
13127
13394
|
// src/mergeQueue.ts
|
|
13128
13395
|
init_src();
|
|
13396
|
+
init_config();
|
|
13129
13397
|
|
|
13130
13398
|
// src/prNumber.ts
|
|
13131
|
-
|
|
13132
|
-
const s = (ref || "").trim();
|
|
13133
|
-
if (!s) return null;
|
|
13134
|
-
const url = s.match(/\/pull\/(\d+)/);
|
|
13135
|
-
if (url) return Number(url[1]);
|
|
13136
|
-
const bare = s.match(/^#?(\d+)$/);
|
|
13137
|
-
return bare ? Number(bare[1]) : null;
|
|
13138
|
-
}
|
|
13399
|
+
init_src();
|
|
13139
13400
|
|
|
13140
13401
|
// src/mergeQueue.ts
|
|
13141
13402
|
var TERMINAL2 = /* @__PURE__ */ new Set(["done", "failed", "stalled", "stopped", "superseded", "returned"]);
|
|
13142
13403
|
function isMergeCandidate(r) {
|
|
13143
|
-
return !!r.pr_url && r
|
|
13404
|
+
return !!r.pr_url && isPrAuthorRun(r) && !TERMINAL2.has(r.status) && prNumberOf(r.pr_url) != null;
|
|
13405
|
+
}
|
|
13406
|
+
function isPrAuthorRun(r) {
|
|
13407
|
+
return r.reviews_run_id == null && r.verifies_run_id == null && r.resolves_run_id == null;
|
|
13144
13408
|
}
|
|
13145
|
-
function mergeQueueCandidates(runs, repo) {
|
|
13409
|
+
function mergeQueueCandidates(runs, repo, repoFallback) {
|
|
13146
13410
|
const byPr = /* @__PURE__ */ new Map();
|
|
13147
13411
|
for (const r of runs) {
|
|
13148
|
-
if (r.repo !== repo || !isMergeCandidate(r)) continue;
|
|
13412
|
+
if (effectiveRepoOf(r.repo, repoFallback) !== repo || !isMergeCandidate(r)) continue;
|
|
13149
13413
|
const num = prNumberOf(r.pr_url);
|
|
13150
13414
|
const seen = byPr.get(num);
|
|
13151
13415
|
if (!seen || r.id > seen.id) byPr.set(num, r);
|
|
@@ -13175,7 +13439,7 @@ function makeMergeQueueResolver(deps) {
|
|
|
13175
13439
|
if (!deps.baseSync || !run.pr_url) return null;
|
|
13176
13440
|
const num = prNumberOf(run.pr_url);
|
|
13177
13441
|
if (num == null) return null;
|
|
13178
|
-
const candidates = mergeQueueCandidates(deps.listRuns(), run.repo);
|
|
13442
|
+
const candidates = mergeQueueCandidates(deps.listRuns(), effectiveRepoOf(run.repo, deps.repoFallback), deps.repoFallback);
|
|
13179
13443
|
const mine = candidates.find((c) => prNumberOf(c.pr_url) === num);
|
|
13180
13444
|
if (!mine) return null;
|
|
13181
13445
|
const ready = [];
|
|
@@ -13209,15 +13473,16 @@ function needsBaseRefresh(d) {
|
|
|
13209
13473
|
}
|
|
13210
13474
|
|
|
13211
13475
|
// src/mergeGate.ts
|
|
13212
|
-
function findRunByPr(runs, ref) {
|
|
13213
|
-
const
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13476
|
+
function findRunByPr(runs, ref, scope = {}) {
|
|
13477
|
+
const rows = runs.map((run) => ({
|
|
13478
|
+
run,
|
|
13479
|
+
prUrl: run.pr_url,
|
|
13480
|
+
repoKey: scope.repoOfRun ? scope.repoOfRun(run) : null,
|
|
13481
|
+
author: isPrAuthorRun(run),
|
|
13482
|
+
reviewVerdict: run.review_verdict,
|
|
13483
|
+
order: run.id
|
|
13484
|
+
}));
|
|
13485
|
+
return pickRunForPr(rows, ref, scope.repo ?? null)?.run ?? null;
|
|
13221
13486
|
}
|
|
13222
13487
|
function makeMergeGateResolver(deps) {
|
|
13223
13488
|
return makeMergeGateParts(deps).gate;
|
|
@@ -13233,15 +13498,17 @@ function makeMergeGateParts(deps) {
|
|
|
13233
13498
|
verifyVerdict: run.verify_verdict ?? null
|
|
13234
13499
|
});
|
|
13235
13500
|
};
|
|
13501
|
+
const repoOfRun = deps.repoFullNameOf ? (r) => deps.repoFullNameOf(effectiveRepoOf(r.repo, deps.repoFallback)) : void 0;
|
|
13236
13502
|
const queueFor = makeMergeQueueResolver({
|
|
13237
13503
|
listRuns: deps.listRuns,
|
|
13238
13504
|
gateAllows: async (r) => (await gateFor(r)).allowed,
|
|
13505
|
+
...deps.repoFallback ? { repoFallback: deps.repoFallback } : {},
|
|
13239
13506
|
...deps.baseSync ? { baseSync: deps.baseSync } : {},
|
|
13240
13507
|
...deps.checks ? { checks: deps.checks } : {},
|
|
13241
13508
|
...deps.ttlMs != null ? { ttlMs: deps.ttlMs } : {}
|
|
13242
13509
|
});
|
|
13243
|
-
const gate = async (ref) => {
|
|
13244
|
-
const run = findRunByPr(deps.listRuns(), ref);
|
|
13510
|
+
const gate = async (ref, ctxRepo) => {
|
|
13511
|
+
const run = findRunByPr(deps.listRuns(), ref, { repo: ctxRepo ?? null, ...repoOfRun ? { repoOfRun } : {} });
|
|
13245
13512
|
if (!run) {
|
|
13246
13513
|
return { ...computeMergeDecision(computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), null), run: null };
|
|
13247
13514
|
}
|
|
@@ -13264,22 +13531,22 @@ function makeMergeGateParts(deps) {
|
|
|
13264
13531
|
}
|
|
13265
13532
|
|
|
13266
13533
|
// src/envGate.ts
|
|
13267
|
-
import { existsSync as
|
|
13268
|
-
import { join as
|
|
13534
|
+
import { existsSync as existsSync15 } from "fs";
|
|
13535
|
+
import { join as join26 } from "path";
|
|
13269
13536
|
import { createConnection } from "net";
|
|
13270
13537
|
import { execa as execa2 } from "execa";
|
|
13271
13538
|
|
|
13272
13539
|
// src/platform.ts
|
|
13273
|
-
import { existsSync as
|
|
13274
|
-
import { delimiter, join as
|
|
13540
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
|
|
13541
|
+
import { delimiter, join as join25 } from "path";
|
|
13275
13542
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
13276
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
13543
|
+
if (name.includes("/") || name.includes("\\")) return existsSync14(name) ? name : null;
|
|
13277
13544
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
13278
13545
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
13279
13546
|
for (const dir of dirs) {
|
|
13280
13547
|
for (const ext of exts) {
|
|
13281
|
-
const candidate =
|
|
13282
|
-
if (
|
|
13548
|
+
const candidate = join25(dir, name + ext);
|
|
13549
|
+
if (existsSync14(candidate)) return candidate;
|
|
13283
13550
|
}
|
|
13284
13551
|
}
|
|
13285
13552
|
return null;
|
|
@@ -13362,8 +13629,8 @@ var COMPOSE_MARKERS = [
|
|
|
13362
13629
|
"compose.yaml"
|
|
13363
13630
|
];
|
|
13364
13631
|
function detectRequirements(repo, io = {}) {
|
|
13365
|
-
const exists = io.exists ??
|
|
13366
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
13632
|
+
const exists = io.exists ?? existsSync15;
|
|
13633
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join26(repo, f)));
|
|
13367
13634
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
13368
13635
|
}
|
|
13369
13636
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -13500,7 +13767,7 @@ import { serve as serve2 } from "@hono/node-server";
|
|
|
13500
13767
|
|
|
13501
13768
|
// src/diff.ts
|
|
13502
13769
|
import { statSync as statSync10 } from "fs";
|
|
13503
|
-
import { join as
|
|
13770
|
+
import { join as join27 } from "path";
|
|
13504
13771
|
import { $ as $4 } from "execa";
|
|
13505
13772
|
var MAX_FILES = 200;
|
|
13506
13773
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -13668,7 +13935,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
13668
13935
|
files.push(pf);
|
|
13669
13936
|
}
|
|
13670
13937
|
}
|
|
13671
|
-
files.sort((a, b) => mtime(
|
|
13938
|
+
files.sort((a, b) => mtime(join27(worktree, b.path)) - mtime(join27(worktree, a.path)));
|
|
13672
13939
|
const truncatedFiles = files.length > MAX_FILES;
|
|
13673
13940
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
13674
13941
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -13701,7 +13968,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
13701
13968
|
// package.json
|
|
13702
13969
|
var package_default = {
|
|
13703
13970
|
name: "@taskless-app/swarm",
|
|
13704
|
-
version: "0.6.
|
|
13971
|
+
version: "0.6.25",
|
|
13705
13972
|
type: "module",
|
|
13706
13973
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
13707
13974
|
keywords: [
|
|
@@ -13731,6 +13998,7 @@ var package_default = {
|
|
|
13731
13998
|
files: [
|
|
13732
13999
|
"dist",
|
|
13733
14000
|
"docs/HERO.md",
|
|
14001
|
+
"hooks",
|
|
13734
14002
|
"roles"
|
|
13735
14003
|
],
|
|
13736
14004
|
scripts: {
|
|
@@ -13751,7 +14019,7 @@ var package_default = {
|
|
|
13751
14019
|
execa: "^10.0.0",
|
|
13752
14020
|
zod: "3.25.76"
|
|
13753
14021
|
},
|
|
13754
|
-
gitHead: "
|
|
14022
|
+
gitHead: "36aaed0414a46ea272ad762b81e051fecec40eaa"
|
|
13755
14023
|
};
|
|
13756
14024
|
|
|
13757
14025
|
// src/version.ts
|
|
@@ -15199,13 +15467,15 @@ function makeHandler(deps) {
|
|
|
15199
15467
|
if (p === "/merge-gate") {
|
|
15200
15468
|
const ref = url.searchParams.get("pr") ?? "";
|
|
15201
15469
|
if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
|
|
15470
|
+
const ctxRepo = url.searchParams.get("repo");
|
|
15202
15471
|
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
|
|
15203
15472
|
listRuns: () => deps.db.listRuns(),
|
|
15204
15473
|
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
15205
|
-
autoVerify: SWARM_AUTO_VERIFY
|
|
15474
|
+
autoVerify: SWARM_AUTO_VERIFY,
|
|
15206
15475
|
// TASWM-344
|
|
15476
|
+
...defaultRepo ? { repoFallback: defaultRepo } : {}
|
|
15207
15477
|
});
|
|
15208
|
-
return Response.json(await resolve4(ref));
|
|
15478
|
+
return Response.json(await resolve4(ref, ctxRepo));
|
|
15209
15479
|
}
|
|
15210
15480
|
if (p === "/resources") return Response.json(hostResourceSnapshot());
|
|
15211
15481
|
if (p === "/timeline") return Response.json(timelineState(deps.db, Date.now(), readNarratorLog(deps.narratorLog)));
|
|
@@ -15384,21 +15654,21 @@ async function checkDaemonVersion(start, opts = {}) {
|
|
|
15384
15654
|
|
|
15385
15655
|
// src/hostCommands.ts
|
|
15386
15656
|
init_src();
|
|
15387
|
-
import { readFileSync as
|
|
15388
|
-
import { join as
|
|
15657
|
+
import { readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
15658
|
+
import { join as join28 } from "path";
|
|
15389
15659
|
function hostCommandStatePath(home) {
|
|
15390
|
-
return
|
|
15660
|
+
return join28(home, "host-command.json");
|
|
15391
15661
|
}
|
|
15392
15662
|
function readState(path) {
|
|
15393
15663
|
try {
|
|
15394
|
-
return JSON.parse(
|
|
15664
|
+
return JSON.parse(readFileSync19(path, "utf8"));
|
|
15395
15665
|
} catch {
|
|
15396
15666
|
return {};
|
|
15397
15667
|
}
|
|
15398
15668
|
}
|
|
15399
15669
|
function writeState(path, state) {
|
|
15400
15670
|
try {
|
|
15401
|
-
|
|
15671
|
+
writeFileSync14(`${path}.tmp`, JSON.stringify(state));
|
|
15402
15672
|
renameSync9(`${path}.tmp`, path);
|
|
15403
15673
|
} catch {
|
|
15404
15674
|
}
|
|
@@ -15561,8 +15831,8 @@ async function doRestart(cmd, opts, note) {
|
|
|
15561
15831
|
|
|
15562
15832
|
// src/selfUpdate.ts
|
|
15563
15833
|
init_git();
|
|
15564
|
-
import { existsSync as
|
|
15565
|
-
import { join as
|
|
15834
|
+
import { existsSync as existsSync16 } from "fs";
|
|
15835
|
+
import { join as join29 } from "path";
|
|
15566
15836
|
import { $ as $5 } from "execa";
|
|
15567
15837
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
15568
15838
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -15575,7 +15845,7 @@ function tail(text, max = 400) {
|
|
|
15575
15845
|
}
|
|
15576
15846
|
async function runSelfUpdate(io) {
|
|
15577
15847
|
const run = io.run ?? defaultRun;
|
|
15578
|
-
const exists = io.exists ??
|
|
15848
|
+
const exists = io.exists ?? existsSync16;
|
|
15579
15849
|
const repo = io.repo;
|
|
15580
15850
|
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
15581
15851
|
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
@@ -15604,7 +15874,7 @@ async function runSelfUpdate(io) {
|
|
|
15604
15874
|
};
|
|
15605
15875
|
}
|
|
15606
15876
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
15607
|
-
if (!exists(
|
|
15877
|
+
if (!exists(join29(repo, "pnpm-lock.yaml"))) {
|
|
15608
15878
|
return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
|
|
15609
15879
|
}
|
|
15610
15880
|
const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -15763,19 +16033,19 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
15763
16033
|
}
|
|
15764
16034
|
|
|
15765
16035
|
// src/anomalyState.ts
|
|
15766
|
-
import { existsSync as
|
|
15767
|
-
import { join as
|
|
16036
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
|
|
16037
|
+
import { join as join30 } from "path";
|
|
15768
16038
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
15769
16039
|
var MAX_RESOLVED = 200;
|
|
15770
16040
|
function emptyState() {
|
|
15771
16041
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
15772
16042
|
}
|
|
15773
16043
|
function anomalyStatePath(home) {
|
|
15774
|
-
return
|
|
16044
|
+
return join30(home, "anomaly-state.json");
|
|
15775
16045
|
}
|
|
15776
16046
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
15777
|
-
const exists = io.exists ??
|
|
15778
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
16047
|
+
const exists = io.exists ?? existsSync17;
|
|
16048
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync20(p, "utf8"));
|
|
15779
16049
|
const path = anomalyStatePath(home);
|
|
15780
16050
|
if (!exists(path)) return emptyState();
|
|
15781
16051
|
try {
|
|
@@ -15789,9 +16059,9 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
15789
16059
|
)
|
|
15790
16060
|
);
|
|
15791
16061
|
return {
|
|
15792
|
-
since:
|
|
15793
|
-
lastFiredAt:
|
|
15794
|
-
fires:
|
|
16062
|
+
since: pick3(since, fresh),
|
|
16063
|
+
lastFiredAt: pick3(lastFiredAt, fresh),
|
|
16064
|
+
fires: pick3(fires, fresh),
|
|
15795
16065
|
// TAS-485: очередь гашения НЕ фильтруем тем же TTL, что ключи. Она про
|
|
15796
16066
|
// уведомление, которое УЖЕ висит у человека в инбоксе: пролежав неделю
|
|
15797
16067
|
// недоставленным, гашение не становится лишним — оно становится нужнее.
|
|
@@ -15805,7 +16075,7 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
15805
16075
|
function saveAnomalyState(home, state, io = {}) {
|
|
15806
16076
|
const path = anomalyStatePath(home);
|
|
15807
16077
|
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
15808
|
-
|
|
16078
|
+
writeFileSync15(`${p}.tmp`, data);
|
|
15809
16079
|
renameSync10(`${p}.tmp`, p);
|
|
15810
16080
|
});
|
|
15811
16081
|
writeFile3(path, JSON.stringify(state, null, 2));
|
|
@@ -15875,7 +16145,7 @@ function strings(raw) {
|
|
|
15875
16145
|
if (!Array.isArray(raw)) return [];
|
|
15876
16146
|
return raw.filter((v) => typeof v === "string" && v.length > 0);
|
|
15877
16147
|
}
|
|
15878
|
-
function
|
|
16148
|
+
function pick3(src, keys) {
|
|
15879
16149
|
const out = {};
|
|
15880
16150
|
for (const k of keys) if (src[k] != null) out[k] = src[k];
|
|
15881
16151
|
return out;
|
|
@@ -16227,7 +16497,7 @@ function withManualAreas(history, areas) {
|
|
|
16227
16497
|
|
|
16228
16498
|
// src/overmind.ts
|
|
16229
16499
|
var ANOMALY_RESOLVE_BATCH = 50;
|
|
16230
|
-
var THIS_FILE =
|
|
16500
|
+
var THIS_FILE = fileURLToPath3(import.meta.url);
|
|
16231
16501
|
var executorHasSession = (p) => EXECUTOR.session.exists(p);
|
|
16232
16502
|
var OVERMIND_ENTRY = THIS_FILE;
|
|
16233
16503
|
var STALL_MS = Number(process.env.SWARM_STALL_MS) || 10 * 60 * 1e3;
|
|
@@ -16890,10 +17160,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
16890
17160
|
}
|
|
16891
17161
|
return parts.join("\n");
|
|
16892
17162
|
}
|
|
16893
|
-
var HERO_CHARTER_PATH =
|
|
17163
|
+
var HERO_CHARTER_PATH = join31(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
16894
17164
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
16895
17165
|
const name = run.name || `#${run.id}`;
|
|
16896
|
-
const charter =
|
|
17166
|
+
const charter = readFileSync21(HERO_CHARTER_PATH, "utf8");
|
|
16897
17167
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
16898
17168
|
|
|
16899
17169
|
${task.description}` : `\u042D\u041F\u0418\u041A ${run.task_slug} (\u0442\u0435\u043B\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E slug \u0438 \u0437\u0434\u0440\u0430\u0432\u043E\u043C\u0443 \u0441\u043C\u044B\u0441\u043B\u0443).`;
|
|
@@ -16936,7 +17206,7 @@ function buildResolverPrompt(reviewRun, resolver, conflicts, branch, mainRef) {
|
|
|
16936
17206
|
`1. \u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u043C\u0430\u0440\u043A\u0435\u0440\u044B \u0432\u043E \u0432\u0441\u0435\u0445 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u0445 \u0444\u0430\u0439\u043B\u0430\u0445 (${files}); \`git status\` \u043F\u043E\u043A\u0430\u0436\u0435\u0442, \u0435\u0441\u043B\u0438 \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C.`,
|
|
16937
17207
|
"2. `git add -A && git commit --no-edit` \u2014 \u0437\u0430\u0432\u0435\u0440\u0448\u0438 merge-\u043A\u043E\u043C\u043C\u0438\u0442 (\u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u043E\u0441\u0442\u0430\u0432\u044C).",
|
|
16938
17208
|
"3. \u041F\u0440\u043E\u0433\u043E\u043D\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043A\u0430\u043A \u0432 CI (\u0437\u0430\u0433\u043B\u044F\u043D\u0438 \u0432 .github/workflows \u0438 package.json \u0440\u0435\u043F\u043E \u2014 \u0442\u0435\u0441\u0442\u044B/\u0442\u0438\u043F\u044B/\u0441\u0431\u043E\u0440\u043A\u0430/\u043B\u0438\u043D\u0442) \u0438 \u0443\u0431\u0435\u0434\u0438\u0441\u044C, \u0447\u0442\u043E \u0437\u0435\u043B\u0435\u043D\u043E. \u041A\u0440\u0430\u0441\u043D\u043E\u0435 \u2014 \u043F\u043E\u0447\u0438\u043D\u0438 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435, \u043D\u0435 \u0441\u0430\u043C\u0443 \u0444\u0438\u0447\u0443.",
|
|
16939
|
-
`4. \`git push origin HEAD
|
|
17209
|
+
`4. \`git push origin HEAD:refs/heads/${branch}\` \u2014 \u041E\u0411\u042B\u0427\u041D\u042B\u0419 push (\u044D\u0442\u043E fast-forward), \u043F\u043E\u043B\u043D\u044B\u0439 refspec (\u043D\u0435 \u0433\u043E\u043B\u043E\u0435 \`HEAD:${branch}\` \u2014 git \u043E\u0442\u043A\u0430\u0436\u0435\u0442\u0441\u044F \u0443\u0433\u0430\u0434\u044B\u0432\u0430\u0442\u044C, \u0435\u0441\u043B\u0438 \u0432\u0435\u0442\u043A\u0443 \u0442\u0435\u043C \u0432\u0440\u0435\u043C\u0435\u043D\u0435\u043C \u0443\u0434\u0430\u043B\u0438\u043B\u0438). \`--force\`/\`--force-with-lease\` \u041D\u0415 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439: \u0440\u0438\u0431\u0435\u0439\u0437/\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0441\u044C \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0442\u0443\u0442 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u044B \u0438 \u0433\u0435\u0439\u0442\u044F\u0442\u0441\u044F.`,
|
|
16940
17210
|
`5. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}) \u2014 \u0440\u043E\u0439 \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442, \u0447\u0442\u043E \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D \u0438 PR \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D.`,
|
|
16941
17211
|
"",
|
|
16942
17212
|
`\u0412 \u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u043E\u043C\u043C\u0438\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0442\u0440\u0435\u0439\u043B\u0435\u0440-\u0441\u0442\u0440\u043E\u043A\u0443 \xABDrone: ${name} \xB7 resolver run #${resolver.id}\xBB. \u041A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043F\u0443\u0448\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u2014 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0440\u0451\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0434\u0440\u043E\u043D\u043E\u043C. \u0414\u043E\u043B\u0433\u043E\u0435 (\u0442\u0435\u0441\u0442\u044B) \u0434\u0435\u0440\u0436\u0438 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u043C \u0432\u044B\u0437\u043E\u0432\u043E\u043C, \u043D\u0435 \u0432 \u0444\u043E\u043D\u0435.`
|
|
@@ -17197,7 +17467,7 @@ async function wakeRun(deps, runId) {
|
|
|
17197
17467
|
if (!run) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u0440\u043E\u044F` };
|
|
17198
17468
|
if (run.pid != null && deps.isAlive(run.pid)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 (pid ${run.pid}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
|
|
17199
17469
|
if (isTerminalStatus(run.status)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435\u043D (${run.status}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
|
|
17200
|
-
const worktreeExists = deps.worktreeExists ??
|
|
17470
|
+
const worktreeExists = deps.worktreeExists ?? existsSync18;
|
|
17201
17471
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
17202
17472
|
const wt = run.worktree_path;
|
|
17203
17473
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -17303,7 +17573,7 @@ function repoHasCi(deps, run) {
|
|
|
17303
17573
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
17304
17574
|
for (const base3 of [worktreePath, repo]) {
|
|
17305
17575
|
if (!base3) continue;
|
|
17306
|
-
const dir =
|
|
17576
|
+
const dir = join31(base3, ".github", "workflows");
|
|
17307
17577
|
try {
|
|
17308
17578
|
if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
17309
17579
|
} catch {
|
|
@@ -17489,7 +17759,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
17489
17759
|
return true;
|
|
17490
17760
|
}
|
|
17491
17761
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
17492
|
-
const exists = deps.worktreeExists ??
|
|
17762
|
+
const exists = deps.worktreeExists ?? existsSync18;
|
|
17493
17763
|
const transcript = deps.hasTranscript ?? executorHasSession;
|
|
17494
17764
|
return exists(worktree) && transcript(worktree);
|
|
17495
17765
|
}
|
|
@@ -17523,7 +17793,9 @@ async function spawnFreshReviewerAfterStall(deps, run, why) {
|
|
|
17523
17793
|
async function wakeAuthorOnChanges(deps, run, now, droneCap) {
|
|
17524
17794
|
if (!deps.respawn) return false;
|
|
17525
17795
|
if (run.pid != null && deps.isAlive(run.pid)) return false;
|
|
17526
|
-
if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled"))
|
|
17796
|
+
if (deps.db.activeReviewersFor(run.id).some((r) => r.status !== "stalled" && !isAwaitingCi(r.status, r.stop_reason))) {
|
|
17797
|
+
return false;
|
|
17798
|
+
}
|
|
17527
17799
|
if (deps.resourcesOk && !deps.resourcesOk()) return false;
|
|
17528
17800
|
if (countActiveDrones(deps.db) >= droneCap) return false;
|
|
17529
17801
|
if (!await projectQuotaGateAllows(deps, run)) return false;
|
|
@@ -17638,7 +17910,7 @@ async function envGateAllows(deps, run) {
|
|
|
17638
17910
|
return true;
|
|
17639
17911
|
}
|
|
17640
17912
|
function effectiveRepo(deps, run) {
|
|
17641
|
-
return
|
|
17913
|
+
return effectiveRepoOf(run.repo, deps.repoFallback);
|
|
17642
17914
|
}
|
|
17643
17915
|
async function zoneConflictFor(db, models, activeFiles, run, repo, repoFallback) {
|
|
17644
17916
|
const model = await models.forRepo(repo);
|
|
@@ -17797,7 +18069,8 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
17797
18069
|
}
|
|
17798
18070
|
const spentAttempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
|
|
17799
18071
|
if (spentAttempts >= PRE_START_ATTEMPT_LIMIT) {
|
|
17800
|
-
const
|
|
18072
|
+
const last = lastPreStartAttempt(deps.db.listRuns(), run.task_slug);
|
|
18073
|
+
const why = last && isUnretriableParked(last) ? `${SPAWN_PARK_MARK}: \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043E\u0442\u043A\u0430\u0437\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u043E\u0432\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-485). \u0427\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u2014 \u0432 \u043F\u0440\u043E\u0433\u043E\u043D\u0435 #${last.id}` : `${PRE_START_MARK} ${spentAttempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-422)`;
|
|
17801
18074
|
deps.db.updateRun(run.id, { status: "stopped", stop_reason: why });
|
|
17802
18075
|
deps.db.addEvent(run.id, "step", why);
|
|
17803
18076
|
const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), `pre-start-exhausted:${run.task_slug}`, why, GATE_LOG_HEARTBEAT_TICKS);
|
|
@@ -17816,7 +18089,8 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
17816
18089
|
const unmet = depVerdicts.filter((d) => !d.met);
|
|
17817
18090
|
if (unmet.length) {
|
|
17818
18091
|
const lost = unmet.filter((d) => d.outcome === "unknown");
|
|
17819
|
-
const
|
|
18092
|
+
const blind = deps.db.depsUnconfirmed?.(run.depends_on) ?? [];
|
|
18093
|
+
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(", ")}`;
|
|
17820
18094
|
return deferSpawn(deps, run, why);
|
|
17821
18095
|
}
|
|
17822
18096
|
if (!await envGateAllows(deps, run)) return "skipped";
|
|
@@ -17853,10 +18127,10 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
17853
18127
|
narrate(deps, `\u0411\u0435\u0440\u0443 ${run.task_slug} \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u0441\u043F\u0430\u0432\u043D\u044E ${droneLabel(run)} (\u043F\u0440\u0438\u0431\u0440\u0430\u043B \u0437\u0430 \u043C\u0451\u0440\u0442\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439).`);
|
|
17854
18128
|
return "spawned";
|
|
17855
18129
|
} catch (again) {
|
|
17856
|
-
return await failBeforeStart(deps, run, String(again));
|
|
18130
|
+
return await failBeforeStart(deps, run, String(again), spawnRetryOpts(again));
|
|
17857
18131
|
}
|
|
17858
18132
|
}
|
|
17859
|
-
return await failBeforeStart(deps, run, String(e));
|
|
18133
|
+
return await failBeforeStart(deps, run, String(e), spawnRetryOpts(e));
|
|
17860
18134
|
}
|
|
17861
18135
|
}
|
|
17862
18136
|
function holderStillHolds(deps, holder, now) {
|
|
@@ -17887,15 +18161,19 @@ async function healBusyWorktree(deps, run, e) {
|
|
|
17887
18161
|
if (holder) deps.db.addEvent(holder.id, "step", `worktree ${path} \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0451\u043D: ${why}, \u0441\u043B\u0430\u0433 \u043F\u043E\u043D\u0430\u0434\u043E\u0431\u0438\u043B\u0441\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id}`);
|
|
17888
18162
|
return true;
|
|
17889
18163
|
}
|
|
17890
|
-
|
|
18164
|
+
function spawnRetryOpts(e) {
|
|
18165
|
+
if (!(e instanceof TaskBodyUnavailableError)) return {};
|
|
18166
|
+
return { retriable: TOOL_FAILURE_RETRIABLE[e.code] };
|
|
18167
|
+
}
|
|
18168
|
+
async function failBeforeStart(deps, run, reason, opts = {}) {
|
|
17891
18169
|
deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
|
|
17892
18170
|
deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
|
|
17893
18171
|
const attempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
|
|
17894
|
-
if (attempts < PRE_START_ATTEMPT_LIMIT) {
|
|
18172
|
+
if (opts.retriable !== false && attempts < PRE_START_ATTEMPT_LIMIT) {
|
|
17895
18173
|
narrate(deps, `${droneLabel(run)} \u2014 ${PRE_START_MARK} (\u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempts} \u0438\u0437 ${PRE_START_ATTEMPT_LIMIT}): ${reason}`);
|
|
17896
18174
|
return "failed";
|
|
17897
18175
|
}
|
|
17898
|
-
const parked = parkReason(attempts, reason);
|
|
18176
|
+
const parked = opts.retriable === false ? parkReasonUnretriable(reason) : parkReason(attempts, reason);
|
|
17899
18177
|
deps.db.updateRun(run.id, { stop_reason: parked });
|
|
17900
18178
|
deps.db.addEvent(run.id, "step", parked);
|
|
17901
18179
|
narrate(deps, `${run.task_slug}: ${parked}`);
|
|
@@ -17931,7 +18209,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
17931
18209
|
}
|
|
17932
18210
|
if (!candidate) return;
|
|
17933
18211
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
17934
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18212
|
+
const worktreeExists = deps.worktreeExists ?? existsSync18;
|
|
17935
18213
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
17936
18214
|
const wt = candidate.worktree_path;
|
|
17937
18215
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -18100,7 +18378,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
18100
18378
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
18101
18379
|
if (!deps.respawn) return;
|
|
18102
18380
|
const wt = resumeCandidate.worktree_path;
|
|
18103
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18381
|
+
const worktreeExists = deps.worktreeExists ?? existsSync18;
|
|
18104
18382
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18105
18383
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
18106
18384
|
const mode = canContinue ? "continue" : "fresh";
|
|
@@ -18378,7 +18656,7 @@ function buildEscalationCandidates(db) {
|
|
|
18378
18656
|
}
|
|
18379
18657
|
async function tickOnce(deps) {
|
|
18380
18658
|
const now = deps.now ? deps.now() : Date.now();
|
|
18381
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18659
|
+
const worktreeExists = deps.worktreeExists ?? existsSync18;
|
|
18382
18660
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18383
18661
|
deps.sampleHost?.();
|
|
18384
18662
|
if (deps.rehydrate) {
|
|
@@ -18632,7 +18910,7 @@ async function tickOnce(deps) {
|
|
|
18632
18910
|
} else if (!st.conflicting && deps.ci && deps.respawn && !stormActive) {
|
|
18633
18911
|
await handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap);
|
|
18634
18912
|
}
|
|
18635
|
-
if ((run
|
|
18913
|
+
if (needsFixIteration(run) && !stormActive && !st.conflicting && !deps.db.activeResolverFor(run.id) && await wakeAuthorOnChanges(deps, run, now, droneCap)) {
|
|
18636
18914
|
continue;
|
|
18637
18915
|
}
|
|
18638
18916
|
const reviewerGatesOk = !st.conflicting && !st.isDraft && !!deps.spawnReviewer && !stormActive && (!deps.reviewCutoffAt || run.updated_at >= deps.reviewCutoffAt) && deps.db.reviewerCountFor(run.id) === 0 && // ГЕЙТ ПЕРВЫМ: живой/успешный ревьюер уже есть — ни капа, ни спавна не трогаем
|
|
@@ -19140,7 +19418,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
19140
19418
|
model: reviewRun.model,
|
|
19141
19419
|
review_model: reviewRun.review_model
|
|
19142
19420
|
});
|
|
19143
|
-
const wt =
|
|
19421
|
+
const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
19144
19422
|
try {
|
|
19145
19423
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
19146
19424
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -19175,6 +19453,28 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
19175
19453
|
throw e;
|
|
19176
19454
|
}
|
|
19177
19455
|
}
|
|
19456
|
+
function makeCheckerAttachmentsReader(tl) {
|
|
19457
|
+
return async (slug, worktree, workspaceId, onEvent) => {
|
|
19458
|
+
const client = tl.forWorkspace(workspaceId);
|
|
19459
|
+
const read = await client.getTaskDetailed(slug);
|
|
19460
|
+
if (!read.ok) {
|
|
19461
|
+
const why = `\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 ${slug} \u043D\u0435 \u0447\u0438\u0442\u0430\u044E\u0442\u0441\u044F: ${describeToolFailure(read.failure)}`;
|
|
19462
|
+
onEvent?.(`${why} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438\u0434\u0451\u0442 \u0411\u0415\u0417 \u043C\u0430\u043A\u0435\u0442\u043E\u0432 \u0438 \u0444\u0430\u0439\u043B\u043E\u0432 \u0437\u0430\u0434\u0430\u0447\u0438`);
|
|
19463
|
+
return [
|
|
19464
|
+
`\u26A0\uFE0F \u0412\u041B\u041E\u0416\u0415\u041D\u0418\u042F \u0417\u0410\u0414\u0410\u0427\u0418 \u041D\u0415\u0414\u041E\u0421\u0422\u0423\u041F\u041D\u042B: ${why}.`,
|
|
19465
|
+
"\u041C\u0430\u043A\u0435\u0442\u044B \u0438 \u0444\u0430\u0439\u043B\u044B \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u044B \u041D\u0415 \u0432\u0438\u0434\u0435\u043B \u2014 \u0438\u0445 \u043C\u043E\u0433\u043B\u043E \u043D\u0435 \u0431\u044B\u0442\u044C \u0432\u043E\u0432\u0441\u0435, \u0430 \u043C\u043E\u0433\u043B\u043E \u0431\u044B\u0442\u044C \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0443\u0433\u043E\u0434\u043D\u043E.",
|
|
19466
|
+
"\u041F\u0443\u043D\u043A\u0442\u044B \u0432\u0438\u0434\u0430 \xAB\u0441\u0434\u0435\u043B\u0430\u043D\u043E \u043B\u0438 \u043A\u0430\u043A \u043D\u0430 \u043C\u0430\u043A\u0435\u0442\u0435\xBB \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0442\u0430\u043A \u0438 \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435, \u0430 \u043D\u0435 \u0441\u0443\u0434\u0438 \u043F\u043E \u043A\u043E\u0434\u0443."
|
|
19467
|
+
].join("\n");
|
|
19468
|
+
}
|
|
19469
|
+
return prepareTaskAttachments({
|
|
19470
|
+
worktree,
|
|
19471
|
+
refs: read.task.attachments,
|
|
19472
|
+
fetchFile: client.fetchFile.bind(client),
|
|
19473
|
+
// Выложенное и пропущенное — тоже в ленту проверяющего, как у исполнителя (respawnRun).
|
|
19474
|
+
...onEvent ? { onEvent } : {}
|
|
19475
|
+
});
|
|
19476
|
+
};
|
|
19477
|
+
}
|
|
19178
19478
|
async function spawnReviewer(deps, reviewRun, files) {
|
|
19179
19479
|
const repo = normalizeRepo(reviewRun.repo || deps.repoFallback);
|
|
19180
19480
|
const branch = await worktreeBranchFor(reviewRun, deps.prHeadBranch);
|
|
@@ -19197,11 +19497,16 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
19197
19497
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
19198
19498
|
workspace_id: reviewRun.workspace_id
|
|
19199
19499
|
});
|
|
19200
|
-
const wt =
|
|
19500
|
+
const wt = join31(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
19201
19501
|
try {
|
|
19202
19502
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
19203
19503
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
19204
|
-
const attachments = await deps.taskAttachments?.(
|
|
19504
|
+
const attachments = await deps.taskAttachments?.(
|
|
19505
|
+
reviewRun.task_slug,
|
|
19506
|
+
wt,
|
|
19507
|
+
reviewRun.workspace_id,
|
|
19508
|
+
(text) => deps.db.addEvent(reviewer.id, "step", text)
|
|
19509
|
+
);
|
|
19205
19510
|
const pendingRules = await deps.pendingRepoRules?.(repo, reviewRun.task_slug).catch(() => []) ?? [];
|
|
19206
19511
|
const prompt = buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments, verifyMode, pendingRules);
|
|
19207
19512
|
const pid = deps.launch({ worktree: wt, run: reviewer, prompt });
|
|
@@ -19273,11 +19578,16 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
19273
19578
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
19274
19579
|
workspace_id: verifiedRun.workspace_id
|
|
19275
19580
|
});
|
|
19276
|
-
const wt =
|
|
19581
|
+
const wt = join31(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
19277
19582
|
try {
|
|
19278
19583
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
19279
19584
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
19280
|
-
const attachments = await deps.taskAttachments?.(
|
|
19585
|
+
const attachments = await deps.taskAttachments?.(
|
|
19586
|
+
verifiedRun.task_slug,
|
|
19587
|
+
wt,
|
|
19588
|
+
verifiedRun.workspace_id,
|
|
19589
|
+
(text) => deps.db.addEvent(verifier.id, "step", text)
|
|
19590
|
+
);
|
|
19281
19591
|
const prompt = buildVerifierPrompt(verifiedRun, verifier, branch, files, attachments, verifyMode);
|
|
19282
19592
|
const pid = deps.launch({ worktree: wt, run: verifier, prompt });
|
|
19283
19593
|
deps.db.updateRun(verifier.id, { status: "running", worktree_path: wt, branch, pid, pr_url: verifiedRun.pr_url });
|
|
@@ -19314,7 +19624,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
19314
19624
|
]);
|
|
19315
19625
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
19316
19626
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
19317
|
-
if (!
|
|
19627
|
+
if (!existsSync18(wt)) {
|
|
19318
19628
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
19319
19629
|
const source = await addWorktree(repo, wt, branch, {
|
|
19320
19630
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -19341,7 +19651,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
19341
19651
|
deps.db.updateRun(run.id, { status: "running", pid });
|
|
19342
19652
|
}
|
|
19343
19653
|
async function syncCrashedOnStartup(deps) {
|
|
19344
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19654
|
+
const worktreeExists = deps.worktreeExists ?? existsSync18;
|
|
19345
19655
|
let orphaned = 0;
|
|
19346
19656
|
let merged = 0;
|
|
19347
19657
|
let requeued = 0;
|
|
@@ -19404,7 +19714,7 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
19404
19714
|
console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u044C \u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E (${e instanceof Error ? e.message : e}) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u0441\u043D\u043E\u0432\u0430 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0442 --repo`);
|
|
19405
19715
|
}
|
|
19406
19716
|
}
|
|
19407
|
-
const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW,
|
|
19717
|
+
const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir6(), repoPaths);
|
|
19408
19718
|
const repoMap = emptyRepoMapState(scanRootsList);
|
|
19409
19719
|
if (SWARM_BACKEND !== "client") {
|
|
19410
19720
|
const { SwarmDb: SwarmDb2 } = await loadSqlite();
|
|
@@ -19471,7 +19781,11 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
19471
19781
|
// TASWM-422: что машина обслуживает ПРЯМО СЕЙЧАС — читается из ЖИВОГО реестра (он
|
|
19472
19782
|
// пересобирается тиком выше), чтобы причина «клона нет на этой машине» называла
|
|
19473
19783
|
// актуальный список, а не снимок на старте демона.
|
|
19474
|
-
reposServed: () => repoRegistry.map((e) => e.repoFullName)
|
|
19784
|
+
reposServed: () => repoRegistry.map((e) => e.repoFullName),
|
|
19785
|
+
// TASWM-492: обратная сторона того же реестра — путь прогона в owner/repo. Читаем из
|
|
19786
|
+
// живого `repoRegistry` (не из снимка на старте): репозиторий могли клонировать уже
|
|
19787
|
+
// после запуска демона, и гейт мержа не должен считать его прогон «неопознанным».
|
|
19788
|
+
repoFullNameOf: (repoPath) => repoRegistry.find((e) => e.path === repoPath)?.repoFullName ?? null
|
|
19475
19789
|
};
|
|
19476
19790
|
}
|
|
19477
19791
|
var crashGuardsInstalled = false;
|
|
@@ -19491,7 +19805,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
19491
19805
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
19492
19806
|
targetDir: WORKTREES_DIR,
|
|
19493
19807
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
19494
|
-
exists: (d) =>
|
|
19808
|
+
exists: (d) => existsSync18(d),
|
|
19495
19809
|
isNonEmptyDir: (d) => {
|
|
19496
19810
|
try {
|
|
19497
19811
|
return readdirSync13(d).length > 0;
|
|
@@ -19506,7 +19820,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
19506
19820
|
});
|
|
19507
19821
|
} catch (e) {
|
|
19508
19822
|
console.error("[migrate] \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F worktrees \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u2014 \u0444\u043E\u043B\u0431\u044D\u043A:", e);
|
|
19509
|
-
return
|
|
19823
|
+
return existsSync18(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
19510
19824
|
}
|
|
19511
19825
|
}
|
|
19512
19826
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -19547,7 +19861,8 @@ async function runDaemon(opts) {
|
|
|
19547
19861
|
repoMap: repoMapState,
|
|
19548
19862
|
refreshRepoMap: rebuildRepoMap,
|
|
19549
19863
|
prodMcp,
|
|
19550
|
-
reposServed
|
|
19864
|
+
reposServed,
|
|
19865
|
+
repoFullNameOf
|
|
19551
19866
|
} = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
|
|
19552
19867
|
await preflightAuth();
|
|
19553
19868
|
const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
|
|
@@ -19869,6 +20184,7 @@ async function runDaemon(opts) {
|
|
|
19869
20184
|
ttlMs: 6e4
|
|
19870
20185
|
});
|
|
19871
20186
|
}
|
|
20187
|
+
const checkerAttachments = tl?.fetchFile ? makeCheckerAttachmentsReader(tl) : void 0;
|
|
19872
20188
|
let spawnReviewerFn;
|
|
19873
20189
|
let verdictCommentFn;
|
|
19874
20190
|
let reviewCutoffAtValue;
|
|
@@ -19892,20 +20208,10 @@ async function runDaemon(opts) {
|
|
|
19892
20208
|
...pendingRepoRules ? { pendingRepoRules } : {},
|
|
19893
20209
|
// TASWM-283: те же вложения задачи, что у исполнителя — ревьюер без макета
|
|
19894
20210
|
// не может проверить «сделано ли как просили».
|
|
19895
|
-
|
|
19896
|
-
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
taskAttachments: async (slug, worktree, workspaceId) => {
|
|
19900
|
-
const client = tl.forWorkspace(workspaceId);
|
|
19901
|
-
const task = await client.getTask(slug);
|
|
19902
|
-
return prepareTaskAttachments({
|
|
19903
|
-
worktree,
|
|
19904
|
-
refs: task?.attachments,
|
|
19905
|
-
fetchFile: client.fetchFile.bind(client)
|
|
19906
|
-
});
|
|
19907
|
-
}
|
|
19908
|
-
} : {}
|
|
20211
|
+
// TASWM-288: вложения читаем клиентом ВОРКСПЕЙСА проверяемого прогона — иначе
|
|
20212
|
+
// ревьюер задачи из чужого воркспейса остался бы без макетов («сделано ли как
|
|
20213
|
+
// просили» проверять нечем), как и исполнитель до фикса.
|
|
20214
|
+
...checkerAttachments ? { taskAttachments: checkerAttachments } : {}
|
|
19909
20215
|
},
|
|
19910
20216
|
reviewRun,
|
|
19911
20217
|
files
|
|
@@ -19927,17 +20233,7 @@ async function runDaemon(opts) {
|
|
|
19927
20233
|
git: { prepareDetachedWorktree, setWorktreeIdentity, removeWorktree },
|
|
19928
20234
|
prHeadBranch,
|
|
19929
20235
|
...verifyModeReader ? { verifyMode: verifyModeReader } : {},
|
|
19930
|
-
...
|
|
19931
|
-
taskAttachments: async (slug, worktree, workspaceId) => {
|
|
19932
|
-
const client = tl.forWorkspace(workspaceId);
|
|
19933
|
-
const task = await client.getTask(slug);
|
|
19934
|
-
return prepareTaskAttachments({
|
|
19935
|
-
worktree,
|
|
19936
|
-
refs: task?.attachments,
|
|
19937
|
-
fetchFile: client.fetchFile.bind(client)
|
|
19938
|
-
});
|
|
19939
|
-
}
|
|
19940
|
-
} : {}
|
|
20236
|
+
...checkerAttachments ? { taskAttachments: checkerAttachments } : {}
|
|
19941
20237
|
},
|
|
19942
20238
|
verifiedRun,
|
|
19943
20239
|
files
|
|
@@ -19976,6 +20272,13 @@ async function runDaemon(opts) {
|
|
|
19976
20272
|
const mergeParts = makeMergeGateParts({
|
|
19977
20273
|
listRuns: () => db.listRuns(),
|
|
19978
20274
|
...autoReviewProject ? { autoReview: autoReviewProject } : {},
|
|
20275
|
+
// TASWM-492: чей это PR. Номер PR у семи репозиториев роя независим, поэтому гейт
|
|
20276
|
+
// сверяет и репозиторий: owner/repo из pr_url, а короткий `#N` — по репозиторию
|
|
20277
|
+
// КОНТЕКСТА ВЫЗОВА (origin каталога, откуда спросили). repoFallback + репо-реестр
|
|
20278
|
+
// нужны для второй стороны сравнения: у прогона в базе лежит локальный путь, и пустой
|
|
20279
|
+
// он означает дефолт роя, а не «отдельный проект».
|
|
20280
|
+
repoFallback: opts.repo,
|
|
20281
|
+
...repoFullNameOf ? { repoFullNameOf } : {},
|
|
19979
20282
|
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
19980
20283
|
autoVerify: SWARM_AUTO_VERIFY,
|
|
19981
20284
|
// TASWM-344: вторая ось приёмки — вердикт верификатора
|
|
@@ -20162,7 +20465,7 @@ async function runDaemon(opts) {
|
|
|
20162
20465
|
isDirty: (p) => worktreeDirty(p),
|
|
20163
20466
|
releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
|
|
20164
20467
|
pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
|
|
20165
|
-
worktreeExists:
|
|
20468
|
+
worktreeExists: existsSync18,
|
|
20166
20469
|
liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
|
|
20167
20470
|
narrate: narrator.narrate
|
|
20168
20471
|
});
|
|
@@ -20285,7 +20588,7 @@ async function runDaemon(opts) {
|
|
|
20285
20588
|
mode,
|
|
20286
20589
|
nudge
|
|
20287
20590
|
),
|
|
20288
|
-
worktreeExists: (p) =>
|
|
20591
|
+
worktreeExists: (p) => existsSync18(p),
|
|
20289
20592
|
hasTranscript: executorHasSession,
|
|
20290
20593
|
// CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
|
|
20291
20594
|
// (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
|
|
@@ -20471,7 +20774,7 @@ async function runDaemon(opts) {
|
|
|
20471
20774
|
onStep
|
|
20472
20775
|
});
|
|
20473
20776
|
}
|
|
20474
|
-
const repo = await repoRoot(
|
|
20777
|
+
const repo = await repoRoot(dirname5(THIS_FILE)).catch(() => null);
|
|
20475
20778
|
if (!repo) {
|
|
20476
20779
|
return { ok: false, step: "fetch", message: "\u0431\u0435\u0433\u0443 \u0438\u0437 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u043E\u0432, \u043D\u043E git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0430\u0434 \u043D\u0438\u043C\u0438 \u043D\u0435\u0442 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" };
|
|
20477
20780
|
}
|
|
@@ -20781,6 +21084,7 @@ export {
|
|
|
20781
21084
|
lastChangesSummary,
|
|
20782
21085
|
listDroneProcesses,
|
|
20783
21086
|
livePpids,
|
|
21087
|
+
makeCheckerAttachmentsReader,
|
|
20784
21088
|
replayDecision,
|
|
20785
21089
|
repoHasCiWorkflows,
|
|
20786
21090
|
resolveConflict,
|