@taskless-app/swarm 0.6.38 → 0.6.39
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 +265 -148
- package/dist/overmind.js +613 -252
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -2211,10 +2211,94 @@ var init_claudeResult = __esm({
|
|
|
2211
2211
|
}
|
|
2212
2212
|
});
|
|
2213
2213
|
|
|
2214
|
-
// src/
|
|
2215
|
-
import { readdirSync as readdirSync2, statSync
|
|
2214
|
+
// src/transcriptSpend.ts
|
|
2215
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
2216
2216
|
import { homedir as homedir2 } from "os";
|
|
2217
2217
|
import { join as join3 } from "path";
|
|
2218
|
+
function transcriptDir(worktree, projectsDir) {
|
|
2219
|
+
return join3(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
2220
|
+
}
|
|
2221
|
+
function transcriptSpendInWindow(worktree, fromMs, toMs, opts = {}) {
|
|
2222
|
+
const projectsDir = opts.projectsDir ?? join3(homedir2(), ".claude", "projects");
|
|
2223
|
+
const empty = { costUsd: 0, tokensIn: 0, tokensOut: 0, durationMs: 0, replies: 0, unpriced: 0, unpricedModel: null };
|
|
2224
|
+
const dir = transcriptDir(worktree, projectsDir);
|
|
2225
|
+
let files;
|
|
2226
|
+
try {
|
|
2227
|
+
files = readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
2228
|
+
} catch {
|
|
2229
|
+
return empty;
|
|
2230
|
+
}
|
|
2231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2232
|
+
let costUsd = 0;
|
|
2233
|
+
let tokensIn = 0;
|
|
2234
|
+
let tokensOut = 0;
|
|
2235
|
+
let firstAt = null;
|
|
2236
|
+
let lastAt = null;
|
|
2237
|
+
let replies = 0;
|
|
2238
|
+
let unpriced = 0;
|
|
2239
|
+
let unpricedModel = null;
|
|
2240
|
+
for (const f of files) {
|
|
2241
|
+
const path = join3(dir, f);
|
|
2242
|
+
let raw;
|
|
2243
|
+
try {
|
|
2244
|
+
if (statSync(path).mtimeMs < fromMs) continue;
|
|
2245
|
+
raw = readFileSync3(path, "utf8");
|
|
2246
|
+
} catch {
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
for (const line of raw.split("\n")) {
|
|
2250
|
+
if (!line.startsWith("{") || !line.includes('"usage"')) continue;
|
|
2251
|
+
let entry;
|
|
2252
|
+
try {
|
|
2253
|
+
entry = JSON.parse(line);
|
|
2254
|
+
} catch {
|
|
2255
|
+
continue;
|
|
2256
|
+
}
|
|
2257
|
+
if (entry.type !== "assistant") continue;
|
|
2258
|
+
const usage = entry.message?.usage;
|
|
2259
|
+
if (!usage) continue;
|
|
2260
|
+
const at = entry.timestamp ? Date.parse(entry.timestamp) : NaN;
|
|
2261
|
+
if (!Number.isFinite(at) || at < fromMs || at >= toMs) continue;
|
|
2262
|
+
const key = entry.requestId ?? entry.message?.id ?? entry.uuid;
|
|
2263
|
+
if (!key || seen.has(key)) continue;
|
|
2264
|
+
seen.add(key);
|
|
2265
|
+
const model = entry.message?.model ?? null;
|
|
2266
|
+
const est = costFromUsage(usage, model, opts.table);
|
|
2267
|
+
costUsd += est.costUsd;
|
|
2268
|
+
if (est.warning) {
|
|
2269
|
+
unpriced += 1;
|
|
2270
|
+
unpricedModel ??= model;
|
|
2271
|
+
}
|
|
2272
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
2273
|
+
tokensIn += num(usage.input_tokens) + num(usage.cache_creation_input_tokens) + num(usage.cache_read_input_tokens);
|
|
2274
|
+
tokensOut += num(usage.output_tokens);
|
|
2275
|
+
if (firstAt === null || at < firstAt) firstAt = at;
|
|
2276
|
+
if (lastAt === null || at > lastAt) lastAt = at;
|
|
2277
|
+
replies += 1;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (replies === 0) return empty;
|
|
2281
|
+
return {
|
|
2282
|
+
costUsd,
|
|
2283
|
+
tokensIn,
|
|
2284
|
+
tokensOut,
|
|
2285
|
+
durationMs: firstAt !== null && lastAt !== null ? lastAt - firstAt : 0,
|
|
2286
|
+
replies,
|
|
2287
|
+
unpriced,
|
|
2288
|
+
unpricedModel
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
var init_transcriptSpend = __esm({
|
|
2292
|
+
"src/transcriptSpend.ts"() {
|
|
2293
|
+
"use strict";
|
|
2294
|
+
init_pricing();
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
// src/executors/claudeCode.ts
|
|
2299
|
+
import { readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2300
|
+
import { homedir as homedir3 } from "os";
|
|
2301
|
+
import { join as join4 } from "path";
|
|
2218
2302
|
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
2219
2303
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
2220
2304
|
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
@@ -2246,21 +2330,21 @@ function droneSettingsJson(paths, mergeGateHook = null) {
|
|
|
2246
2330
|
if (mergeGateHook) settings.hooks = { PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: mergeGateHook }] }] };
|
|
2247
2331
|
return JSON.stringify(settings);
|
|
2248
2332
|
}
|
|
2249
|
-
function
|
|
2250
|
-
return
|
|
2333
|
+
function transcriptDir2(worktree, projectsDir) {
|
|
2334
|
+
return join4(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
2251
2335
|
}
|
|
2252
|
-
function sessionTranscriptExists(worktree, projectsDir =
|
|
2336
|
+
function sessionTranscriptExists(worktree, projectsDir = join4(homedir3(), ".claude", "projects")) {
|
|
2253
2337
|
try {
|
|
2254
|
-
return
|
|
2338
|
+
return readdirSync3(transcriptDir2(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
|
|
2255
2339
|
} catch {
|
|
2256
2340
|
return false;
|
|
2257
2341
|
}
|
|
2258
2342
|
}
|
|
2259
|
-
function transcriptMtimeMs(worktree, projectsDir =
|
|
2260
|
-
const dir =
|
|
2343
|
+
function transcriptMtimeMs(worktree, projectsDir = join4(homedir3(), ".claude", "projects")) {
|
|
2344
|
+
const dir = transcriptDir2(worktree, projectsDir);
|
|
2261
2345
|
let files;
|
|
2262
2346
|
try {
|
|
2263
|
-
files =
|
|
2347
|
+
files = readdirSync3(dir);
|
|
2264
2348
|
} catch {
|
|
2265
2349
|
return null;
|
|
2266
2350
|
}
|
|
@@ -2268,7 +2352,7 @@ function transcriptMtimeMs(worktree, projectsDir = join3(homedir2(), ".claude",
|
|
|
2268
2352
|
for (const f of files) {
|
|
2269
2353
|
if (!f.endsWith(".jsonl")) continue;
|
|
2270
2354
|
try {
|
|
2271
|
-
const mtime2 =
|
|
2355
|
+
const mtime2 = statSync2(join4(dir, f)).mtimeMs;
|
|
2272
2356
|
if (latest === null || mtime2 > latest) latest = mtime2;
|
|
2273
2357
|
} catch {
|
|
2274
2358
|
}
|
|
@@ -2281,7 +2365,7 @@ function writeMcpConfig(spec) {
|
|
|
2281
2365
|
[spec.mcp.name]: { command: spec.mcp.command, args: spec.mcp.args, env: spec.mcp.env }
|
|
2282
2366
|
}
|
|
2283
2367
|
};
|
|
2284
|
-
const path =
|
|
2368
|
+
const path = join4(spec.worktree, MCP_CONFIG_FILE);
|
|
2285
2369
|
writeFileSync3(path, JSON.stringify(cfg, null, 2));
|
|
2286
2370
|
return path;
|
|
2287
2371
|
}
|
|
@@ -2296,6 +2380,7 @@ var init_claudeCode = __esm({
|
|
|
2296
2380
|
init_swarmFiles();
|
|
2297
2381
|
init_mergeGateHook();
|
|
2298
2382
|
init_claudeResult();
|
|
2383
|
+
init_transcriptSpend();
|
|
2299
2384
|
ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
|
|
2300
2385
|
MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
|
|
2301
2386
|
claudeCodeExecutor = {
|
|
@@ -2334,6 +2419,26 @@ var init_claudeCode = __esm({
|
|
|
2334
2419
|
...abortField(sessionAbort(result))
|
|
2335
2420
|
};
|
|
2336
2421
|
},
|
|
2422
|
+
// TASWM-548: итога нет — берём трату из транскрипта сессии. Тот пишется по ходу работы и
|
|
2423
|
+
// переживает SIGTERM разгрузки, из-за которого итог и не был напечатан; формат usage там
|
|
2424
|
+
// тот же, что в итоге, поэтому цена считается тем же прайсингом (см. transcriptSpend.ts).
|
|
2425
|
+
rescueSpend(worktree, fromMs, toMs) {
|
|
2426
|
+
const spend = transcriptSpendInWindow(worktree, fromMs, toMs);
|
|
2427
|
+
if (spend.replies === 0) return null;
|
|
2428
|
+
return {
|
|
2429
|
+
costUsd: spend.costUsd,
|
|
2430
|
+
tokensIn: spend.tokensIn,
|
|
2431
|
+
tokensOut: spend.tokensOut,
|
|
2432
|
+
durationMs: spend.durationMs,
|
|
2433
|
+
// Часть ответов не по чему было тарифицировать (модели нет в MODEL_PRICING) — их
|
|
2434
|
+
// токены в трату вошли, а деньги НЕТ. Тот же канал, что и у warning штатного итога
|
|
2435
|
+
// (cost.accountRunCost его печатает): иначе спасённые $0 при сожжённых токенах
|
|
2436
|
+
// выглядели бы как честный ноль.
|
|
2437
|
+
...spend.unpriced > 0 ? {
|
|
2438
|
+
warning: `\u0437\u0430\u043F\u0438\u0441\u044C \u0441\u0435\u0441\u0441\u0438\u0438: ${spend.unpriced} \u0438\u0437 ${spend.replies} \u043E\u0442\u0432\u0435\u0442\u043E\u0432 \u0431\u0435\u0437 \u0441\u0442\u0440\u043E\u043A\u0438 \u0432 \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0435 (\u043C\u043E\u0434\u0435\u043B\u044C "${spend.unpricedModel ?? ""}") \u2014 \u0438\u0445 \u0446\u0435\u043D\u0430 \u0432 \u0441\u043F\u0430\u0441\u0451\u043D\u043D\u0443\u044E \u0442\u0440\u0430\u0442\u0443 \u041D\u0415 \u0432\u043E\u0448\u043B\u0430`
|
|
2439
|
+
} : {}
|
|
2440
|
+
};
|
|
2441
|
+
},
|
|
2337
2442
|
session: {
|
|
2338
2443
|
exists: (worktree) => sessionTranscriptExists(worktree),
|
|
2339
2444
|
mtimeMs: (worktree) => transcriptMtimeMs(worktree)
|
|
@@ -2351,34 +2456,34 @@ var init_claudeCode = __esm({
|
|
|
2351
2456
|
});
|
|
2352
2457
|
|
|
2353
2458
|
// src/swarmHome.ts
|
|
2354
|
-
import { homedir as
|
|
2355
|
-
import { join as
|
|
2459
|
+
import { homedir as homedir4 } from "os";
|
|
2460
|
+
import { join as join5 } from "path";
|
|
2356
2461
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
2357
2462
|
var SWARM_HOME;
|
|
2358
2463
|
var init_swarmHome = __esm({
|
|
2359
2464
|
"src/swarmHome.ts"() {
|
|
2360
2465
|
"use strict";
|
|
2361
|
-
SWARM_HOME = process.env.SWARM_HOME ||
|
|
2466
|
+
SWARM_HOME = process.env.SWARM_HOME || join5(homedir4(), ".taskless-swarm");
|
|
2362
2467
|
mkdirSync2(SWARM_HOME, { recursive: true });
|
|
2363
2468
|
}
|
|
2364
2469
|
});
|
|
2365
2470
|
|
|
2366
2471
|
// src/executors/codex.ts
|
|
2367
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as
|
|
2368
|
-
import { join as
|
|
2472
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, statSync as statSync3, symlinkSync } from "fs";
|
|
2473
|
+
import { join as join6 } from "path";
|
|
2369
2474
|
function resolveCodexBin(env = process.env) {
|
|
2370
2475
|
return (env.SWARM_CODEX_BIN ?? "").trim() || "codex";
|
|
2371
2476
|
}
|
|
2372
2477
|
function codexHomePath(worktree, swarmHome = SWARM_HOME) {
|
|
2373
|
-
return
|
|
2478
|
+
return join6(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
|
|
2374
2479
|
}
|
|
2375
2480
|
function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWARM_HOME) {
|
|
2376
2481
|
const home = codexHomePath(worktree, swarmHome);
|
|
2377
2482
|
mkdirSync3(home, { recursive: true });
|
|
2378
|
-
const real = (env.CODEX_HOME ?? "").trim() ||
|
|
2483
|
+
const real = (env.CODEX_HOME ?? "").trim() || join6(realHome, ".codex");
|
|
2379
2484
|
for (const file of ["auth.json", "config.toml"]) {
|
|
2380
|
-
const target =
|
|
2381
|
-
const link2 =
|
|
2485
|
+
const target = join6(real, file);
|
|
2486
|
+
const link2 = join6(home, file);
|
|
2382
2487
|
if (!existsSync4(target) || existsSync4(link2)) continue;
|
|
2383
2488
|
try {
|
|
2384
2489
|
symlinkSync(target, link2);
|
|
@@ -2437,16 +2542,16 @@ function parseCodexStream(raw) {
|
|
|
2437
2542
|
function newestRolloutMtime(dir, depth = 4) {
|
|
2438
2543
|
let entries;
|
|
2439
2544
|
try {
|
|
2440
|
-
entries =
|
|
2545
|
+
entries = readdirSync4(dir);
|
|
2441
2546
|
} catch {
|
|
2442
2547
|
return null;
|
|
2443
2548
|
}
|
|
2444
2549
|
let latest = null;
|
|
2445
2550
|
for (const name of entries) {
|
|
2446
|
-
const full =
|
|
2551
|
+
const full = join6(dir, name);
|
|
2447
2552
|
let st;
|
|
2448
2553
|
try {
|
|
2449
|
-
st =
|
|
2554
|
+
st = statSync3(full);
|
|
2450
2555
|
} catch {
|
|
2451
2556
|
continue;
|
|
2452
2557
|
}
|
|
@@ -2517,8 +2622,8 @@ ${notice}` : spec.prompt;
|
|
|
2517
2622
|
return est.warning ? { costUsd: est.costUsd, warning: est.warning } : { costUsd: est.costUsd };
|
|
2518
2623
|
},
|
|
2519
2624
|
session: {
|
|
2520
|
-
exists: (worktree) => newestRolloutMtime(
|
|
2521
|
-
mtimeMs: (worktree) => newestRolloutMtime(
|
|
2625
|
+
exists: (worktree) => newestRolloutMtime(join6(codexHomePath(worktree), "sessions")) !== null,
|
|
2626
|
+
mtimeMs: (worktree) => newestRolloutMtime(join6(codexHomePath(worktree), "sessions"))
|
|
2522
2627
|
},
|
|
2523
2628
|
probe: {
|
|
2524
2629
|
// Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
|
|
@@ -2666,8 +2771,8 @@ __export(config_exports, {
|
|
|
2666
2771
|
restEndpoint: () => restEndpoint,
|
|
2667
2772
|
reviewCutoffAt: () => reviewCutoffAt
|
|
2668
2773
|
});
|
|
2669
|
-
import { basename, join as
|
|
2670
|
-
import { readFileSync as
|
|
2774
|
+
import { basename, join as join7, resolve } from "path";
|
|
2775
|
+
import { readFileSync as readFileSync4, realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2671
2776
|
function normalizeRepo(repo) {
|
|
2672
2777
|
const r = (repo ?? "").trim();
|
|
2673
2778
|
if (!r) return "";
|
|
@@ -2681,10 +2786,10 @@ function effectiveRepoOf(repo, fallback) {
|
|
|
2681
2786
|
return normalizeRepo(repo || fallback || "");
|
|
2682
2787
|
}
|
|
2683
2788
|
function droneWorktreePath(worktreesDir, repo, slug) {
|
|
2684
|
-
return
|
|
2789
|
+
return join7(droneWorktreeRoot(worktreesDir, repo), slug);
|
|
2685
2790
|
}
|
|
2686
2791
|
function droneWorktreeRoot(worktreesDir, repo) {
|
|
2687
|
-
return
|
|
2792
|
+
return join7(worktreesDir, basename(normalizeRepo(repo)));
|
|
2688
2793
|
}
|
|
2689
2794
|
function resolveEffectiveWorktreesDir(deps) {
|
|
2690
2795
|
if (deps.envOverride) return deps.envOverride;
|
|
@@ -2732,14 +2837,14 @@ function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
|
|
|
2732
2837
|
}
|
|
2733
2838
|
function tokenFromFile() {
|
|
2734
2839
|
try {
|
|
2735
|
-
return
|
|
2840
|
+
return readFileSync4(join7(SWARM_HOME, "token"), "utf8").trim();
|
|
2736
2841
|
} catch {
|
|
2737
2842
|
return "";
|
|
2738
2843
|
}
|
|
2739
2844
|
}
|
|
2740
2845
|
function reviewCutoffAt() {
|
|
2741
2846
|
try {
|
|
2742
|
-
const raw = JSON.parse(
|
|
2847
|
+
const raw = JSON.parse(readFileSync4(REVIEW_CUTOFF_PATH, "utf8"));
|
|
2743
2848
|
if (raw.at) return raw.at;
|
|
2744
2849
|
} catch {
|
|
2745
2850
|
}
|
|
@@ -2760,25 +2865,25 @@ var init_config = __esm({
|
|
|
2760
2865
|
init_executors();
|
|
2761
2866
|
init_swarmHome();
|
|
2762
2867
|
init_swarmHome();
|
|
2763
|
-
DB_PATH = process.env.SWARM_DB ||
|
|
2868
|
+
DB_PATH = process.env.SWARM_DB || join7(SWARM_HOME, "swarm.db");
|
|
2764
2869
|
EXECUTOR = resolveExecutor(process.env.SWARM_EXECUTOR);
|
|
2765
|
-
DRONE_HOME = process.env.SWARM_DRONE_HOME ||
|
|
2870
|
+
DRONE_HOME = process.env.SWARM_DRONE_HOME || join7(SWARM_HOME, "drone-home");
|
|
2766
2871
|
DRONE_HOME_ISOLATION = process.env.SWARM_DRONE_ISOLATE_HOME !== "0" && process.platform !== "win32";
|
|
2767
2872
|
DRONE_ISOLATION = DRONE_HOME_ISOLATION ? { home: DRONE_HOME, swarmHome: SWARM_HOME } : void 0;
|
|
2768
|
-
WORKTREES_NOINDEX_DIR =
|
|
2769
|
-
WORKTREES_LEGACY_DIR =
|
|
2873
|
+
WORKTREES_NOINDEX_DIR = join7(SWARM_HOME, "worktrees.noindex");
|
|
2874
|
+
WORKTREES_LEGACY_DIR = join7(SWARM_HOME, "worktrees");
|
|
2770
2875
|
WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
|
|
2771
|
-
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS ||
|
|
2772
|
-
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG ||
|
|
2876
|
+
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join7(SWARM_HOME, "run-logs");
|
|
2877
|
+
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join7(SWARM_HOME, "overmind.log");
|
|
2773
2878
|
DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
|
|
2774
2879
|
DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
|
|
2775
2880
|
DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
|
|
2776
2881
|
DIST_BUILD = true;
|
|
2777
2882
|
SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
|
|
2778
|
-
SWARM_OUTBOX = process.env.SWARM_OUTBOX ||
|
|
2883
|
+
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join7(SWARM_HOME, "outbox.jsonl");
|
|
2779
2884
|
SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
|
|
2780
2885
|
SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
|
|
2781
|
-
ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR ||
|
|
2886
|
+
ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR || join7(SWARM_HOME, "env-cache");
|
|
2782
2887
|
ENV_CACHE_ENABLED = process.env.SWARM_ENV_CACHE !== "0";
|
|
2783
2888
|
ENV_CACHE_KEEP = Math.trunc(envNum("SWARM_ENV_CACHE_KEEP", 4));
|
|
2784
2889
|
SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
|
|
@@ -2808,15 +2913,15 @@ var init_config = __esm({
|
|
|
2808
2913
|
DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
|
|
2809
2914
|
DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
|
|
2810
2915
|
DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
|
|
2811
|
-
DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET ||
|
|
2916
|
+
DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET || join7(SWARM_HOME, "drone-channel.key");
|
|
2812
2917
|
DRONE_CHANNEL_GRACE_MS = envNum("SWARM_DRONE_CHANNEL_GRACE_MS", 12e4);
|
|
2813
|
-
HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT ||
|
|
2918
|
+
HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join7(SWARM_HOME, "overmind.heartbeat");
|
|
2814
2919
|
HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
|
|
2815
2920
|
SWARM_REPO = process.env.SWARM_REPO || "";
|
|
2816
2921
|
SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
|
|
2817
2922
|
SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2818
2923
|
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
2819
|
-
REPO_MAP_PATH = process.env.SWARM_REPO_MAP ||
|
|
2924
|
+
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join7(SWARM_HOME, "repo-map.json");
|
|
2820
2925
|
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
2821
2926
|
SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
|
|
2822
2927
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
@@ -2831,7 +2936,7 @@ var init_config = __esm({
|
|
|
2831
2936
|
SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
|
|
2832
2937
|
SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
|
|
2833
2938
|
SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
|
|
2834
|
-
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF ||
|
|
2939
|
+
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join7(SWARM_HOME, "review-cutoff.json");
|
|
2835
2940
|
SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
|
|
2836
2941
|
SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
|
|
2837
2942
|
WEB_PORT = Number(process.env.SWARM_WEB_PORT) || 7420;
|
|
@@ -2871,7 +2976,7 @@ var init_quietExec = __esm({
|
|
|
2871
2976
|
|
|
2872
2977
|
// src/resources.ts
|
|
2873
2978
|
import os from "os";
|
|
2874
|
-
import { readFileSync as
|
|
2979
|
+
import { readFileSync as readFileSync5, statfsSync } from "fs";
|
|
2875
2980
|
function isTransientResourceError(e) {
|
|
2876
2981
|
const code = e?.code;
|
|
2877
2982
|
return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
|
|
@@ -2929,7 +3034,7 @@ function fileDescriptorLoadRatio() {
|
|
|
2929
3034
|
}
|
|
2930
3035
|
if (process.platform === "linux") {
|
|
2931
3036
|
try {
|
|
2932
|
-
const [allocated, , max] =
|
|
3037
|
+
const [allocated, , max] = readFileSync5("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
|
|
2933
3038
|
if (Number.isFinite(allocated ?? NaN) && Number.isFinite(max ?? NaN) && max) return allocated / max;
|
|
2934
3039
|
} catch {
|
|
2935
3040
|
}
|
|
@@ -3031,6 +3136,14 @@ function hostResourcesOk() {
|
|
|
3031
3136
|
function reliefDecision(r, minFreeGb = SWARM_RELIEF_MIN_FREE_GB, maxCpuBusy = SWARM_RELIEF_CPU_BUSY, maxFdLoadFactor = SWARM_RELIEF_MAX_FD_LOAD_FACTOR) {
|
|
3032
3137
|
const cores = r.cpus > 0 ? r.cpus : 1;
|
|
3033
3138
|
const pct = (v) => `${Math.round(v * 100)}%`;
|
|
3139
|
+
const pctPair = (v, threshold) => {
|
|
3140
|
+
for (const digits of [0, 1, 2, 3, 4]) {
|
|
3141
|
+
const a = (v * 100).toFixed(digits);
|
|
3142
|
+
const b = (threshold * 100).toFixed(digits);
|
|
3143
|
+
if (a !== b) return `${a}% \u043F\u0440\u0438 \u043F\u043E\u0440\u043E\u0433\u0435 ${b}%`;
|
|
3144
|
+
}
|
|
3145
|
+
return `${pct(v)} \u043F\u0440\u0438 \u043F\u043E\u0440\u043E\u0433\u0435 ${pct(threshold)}`;
|
|
3146
|
+
};
|
|
3034
3147
|
const signals = [];
|
|
3035
3148
|
signals.push(
|
|
3036
3149
|
r.cpuBusy == null ? {
|
|
@@ -3039,7 +3152,7 @@ function reliefDecision(r, minFreeGb = SWARM_RELIEF_MIN_FREE_GB, maxCpuBusy = SW
|
|
|
3039
3152
|
tripped: false
|
|
3040
3153
|
} : {
|
|
3041
3154
|
key: "cpu",
|
|
3042
|
-
text: `CPU \u0437\u0430\u043D\u044F\u0442 ${
|
|
3155
|
+
text: `CPU \u0437\u0430\u043D\u044F\u0442 ${pctPair(r.cpuBusy, maxCpuBusy)}`,
|
|
3043
3156
|
tripped: r.cpuBusy > maxCpuBusy
|
|
3044
3157
|
}
|
|
3045
3158
|
);
|
|
@@ -3051,7 +3164,7 @@ function reliefDecision(r, minFreeGb = SWARM_RELIEF_MIN_FREE_GB, maxCpuBusy = SW
|
|
|
3051
3164
|
signals.push(
|
|
3052
3165
|
r.fdLoadRatio == null ? { key: "fd", text: "\u0434\u0435\u0441\u043A\u0440\u0438\u043F\u0442\u043E\u0440\u044B: \u0438\u0437\u043C\u0435\u0440\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C", tripped: false } : {
|
|
3053
3166
|
key: "fd",
|
|
3054
|
-
text: `\u0434\u0435\u0441\u043A\u0440\u0438\u043F\u0442\u043E\u0440\u044B \u0437\u0430\u043D\u044F\u0442\u044B ${
|
|
3167
|
+
text: `\u0434\u0435\u0441\u043A\u0440\u0438\u043F\u0442\u043E\u0440\u044B \u0437\u0430\u043D\u044F\u0442\u044B ${pctPair(r.fdLoadRatio, maxFdLoadFactor)}`,
|
|
3055
3168
|
tripped: r.fdLoadRatio > maxFdLoadFactor
|
|
3056
3169
|
}
|
|
3057
3170
|
);
|
|
@@ -3335,11 +3448,11 @@ var init_zoneNotes = __esm({
|
|
|
3335
3448
|
});
|
|
3336
3449
|
|
|
3337
3450
|
// src/agentToken.ts
|
|
3338
|
-
import { readFileSync as
|
|
3339
|
-
import { join as
|
|
3451
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
3452
|
+
import { join as join8 } from "path";
|
|
3340
3453
|
function loadAgentToken() {
|
|
3341
3454
|
try {
|
|
3342
|
-
const s = JSON.parse(
|
|
3455
|
+
const s = JSON.parse(readFileSync6(STORE_PATH, "utf8"));
|
|
3343
3456
|
return s.token ? s : null;
|
|
3344
3457
|
} catch {
|
|
3345
3458
|
return null;
|
|
@@ -3392,7 +3505,7 @@ var init_agentToken = __esm({
|
|
|
3392
3505
|
"src/agentToken.ts"() {
|
|
3393
3506
|
"use strict";
|
|
3394
3507
|
init_config();
|
|
3395
|
-
STORE_PATH =
|
|
3508
|
+
STORE_PATH = join8(SWARM_HOME, "agent.json");
|
|
3396
3509
|
base = (api) => api.replace(/\/$/, "");
|
|
3397
3510
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
3398
3511
|
}
|
|
@@ -3506,7 +3619,7 @@ var init_toolFailure = __esm({
|
|
|
3506
3619
|
});
|
|
3507
3620
|
|
|
3508
3621
|
// src/heartbeat.ts
|
|
3509
|
-
import { readFileSync as
|
|
3622
|
+
import { readFileSync as readFileSync8, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
3510
3623
|
function writeHeartbeat(path, now) {
|
|
3511
3624
|
const tmp = `${path}.tmp`;
|
|
3512
3625
|
writeFileSync7(tmp, String(now));
|
|
@@ -3514,7 +3627,7 @@ function writeHeartbeat(path, now) {
|
|
|
3514
3627
|
}
|
|
3515
3628
|
function readHeartbeat(path) {
|
|
3516
3629
|
try {
|
|
3517
|
-
const ms = Number(
|
|
3630
|
+
const ms = Number(readFileSync8(path, "utf8").trim());
|
|
3518
3631
|
return Number.isFinite(ms) ? ms : null;
|
|
3519
3632
|
} catch {
|
|
3520
3633
|
return null;
|
|
@@ -4000,9 +4113,9 @@ var init_procCleanup = __esm({
|
|
|
4000
4113
|
});
|
|
4001
4114
|
|
|
4002
4115
|
// src/git.ts
|
|
4003
|
-
import { existsSync as existsSync12, readdirSync as
|
|
4116
|
+
import { existsSync as existsSync12, readdirSync as readdirSync12, statSync as statSync10 } from "fs";
|
|
4004
4117
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
4005
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4118
|
+
import { isAbsolute as isAbsolute2, join as join20, sep as sep2 } from "path";
|
|
4006
4119
|
import { $ as $2 } from "execa";
|
|
4007
4120
|
function gitNet(repo, args, timeoutMs) {
|
|
4008
4121
|
return withHardTimeout(
|
|
@@ -4025,7 +4138,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
4025
4138
|
}
|
|
4026
4139
|
function isLinkedWorktree(dir) {
|
|
4027
4140
|
try {
|
|
4028
|
-
return
|
|
4141
|
+
return statSync10(join20(dir, ".git")).isFile();
|
|
4029
4142
|
} catch {
|
|
4030
4143
|
return false;
|
|
4031
4144
|
}
|
|
@@ -4135,8 +4248,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
4135
4248
|
try {
|
|
4136
4249
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
4137
4250
|
if (!commonDir) return false;
|
|
4138
|
-
const infoDir =
|
|
4139
|
-
const excludeFile =
|
|
4251
|
+
const infoDir = join20(commonDir, "info");
|
|
4252
|
+
const excludeFile = join20(infoDir, "exclude");
|
|
4140
4253
|
let current = "";
|
|
4141
4254
|
try {
|
|
4142
4255
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -4156,7 +4269,7 @@ async function gitPath(worktree, flag) {
|
|
|
4156
4269
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
4157
4270
|
const raw = out.stdout.trim();
|
|
4158
4271
|
if (out.exitCode !== 0 || !raw) return null;
|
|
4159
|
-
return isAbsolute2(raw) ? raw :
|
|
4272
|
+
return isAbsolute2(raw) ? raw : join20(worktree, raw);
|
|
4160
4273
|
}
|
|
4161
4274
|
async function ensureWorktreeHygiene(worktree) {
|
|
4162
4275
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -4166,12 +4279,12 @@ async function installGuardHooks(worktree) {
|
|
|
4166
4279
|
try {
|
|
4167
4280
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
4168
4281
|
if (!gitDir) return false;
|
|
4169
|
-
const hooksDir =
|
|
4282
|
+
const hooksDir = join20(gitDir, "swarm-hooks");
|
|
4170
4283
|
const prev = await previousHooksPath(worktree);
|
|
4171
4284
|
await mkdir(hooksDir, { recursive: true });
|
|
4172
4285
|
for (const hook of GUARD_HOOKS) {
|
|
4173
|
-
await writeFile(
|
|
4174
|
-
await chmod(
|
|
4286
|
+
await writeFile(join20(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
4287
|
+
await chmod(join20(hooksDir, hook), 493);
|
|
4175
4288
|
}
|
|
4176
4289
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
4177
4290
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -4190,7 +4303,7 @@ async function previousHooksPath(worktree) {
|
|
|
4190
4303
|
if (out.exitCode === 0 && value) return value;
|
|
4191
4304
|
}
|
|
4192
4305
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
4193
|
-
return commonDir ?
|
|
4306
|
+
return commonDir ? join20(commonDir, "hooks") : null;
|
|
4194
4307
|
}
|
|
4195
4308
|
async function removeWorktree(repo, path) {
|
|
4196
4309
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -4215,7 +4328,7 @@ async function worktreeDirty(path) {
|
|
|
4215
4328
|
async function repairMovedWorktrees(targetDir) {
|
|
4216
4329
|
const subdirs = (dir) => {
|
|
4217
4330
|
try {
|
|
4218
|
-
return
|
|
4331
|
+
return readdirSync12(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join20(dir, d.name));
|
|
4219
4332
|
} catch {
|
|
4220
4333
|
return [];
|
|
4221
4334
|
}
|
|
@@ -4345,12 +4458,12 @@ __export(droneChannel_exports, {
|
|
|
4345
4458
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
4346
4459
|
});
|
|
4347
4460
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
4348
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as
|
|
4461
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync12 } from "fs";
|
|
4349
4462
|
import { dirname as dirname4 } from "path";
|
|
4350
4463
|
import { serve } from "@hono/node-server";
|
|
4351
4464
|
function loadChannelSecret(path) {
|
|
4352
4465
|
try {
|
|
4353
|
-
const s =
|
|
4466
|
+
const s = readFileSync19(path, "utf8").trim();
|
|
4354
4467
|
if (s.length >= 32) return s;
|
|
4355
4468
|
} catch {
|
|
4356
4469
|
}
|
|
@@ -4746,7 +4859,7 @@ var init_droneChannel = __esm({
|
|
|
4746
4859
|
|
|
4747
4860
|
// src/attachments.ts
|
|
4748
4861
|
import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4749
|
-
import { join as
|
|
4862
|
+
import { join as join24 } from "path";
|
|
4750
4863
|
function attachmentsFromDoc(doc, origin) {
|
|
4751
4864
|
const out = [];
|
|
4752
4865
|
const walk2 = (node) => {
|
|
@@ -4837,7 +4950,7 @@ async function materializeAttachments(opts) {
|
|
|
4837
4950
|
};
|
|
4838
4951
|
const result = { files: [], skipped: [] };
|
|
4839
4952
|
if (opts.refs.length === 0) return result;
|
|
4840
|
-
const dir =
|
|
4953
|
+
const dir = join24(opts.worktree, ATTACHMENTS_DIR);
|
|
4841
4954
|
await io.removeDir(dir);
|
|
4842
4955
|
let dirReady = false;
|
|
4843
4956
|
let total = 0;
|
|
@@ -4883,7 +4996,7 @@ async function materializeAttachments(opts) {
|
|
|
4883
4996
|
await io.mkdir(dir);
|
|
4884
4997
|
dirReady = true;
|
|
4885
4998
|
}
|
|
4886
|
-
await io.writeFile(
|
|
4999
|
+
await io.writeFile(join24(dir, name), file.bytes);
|
|
4887
5000
|
} catch (e) {
|
|
4888
5001
|
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText2(e)})`);
|
|
4889
5002
|
continue;
|
|
@@ -5036,7 +5149,7 @@ var init_attachments = __esm({
|
|
|
5036
5149
|
|
|
5037
5150
|
// src/shotUpload.ts
|
|
5038
5151
|
import { readFile as fsReadFile } from "fs/promises";
|
|
5039
|
-
import { basename as basename4, isAbsolute as isAbsolute3, join as
|
|
5152
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join25 } from "path";
|
|
5040
5153
|
function shotMimetype(path) {
|
|
5041
5154
|
const dot = path.lastIndexOf(".");
|
|
5042
5155
|
if (dot < 0) return null;
|
|
@@ -5062,7 +5175,7 @@ async function readShots(paths, opts) {
|
|
|
5062
5175
|
}
|
|
5063
5176
|
let bytes;
|
|
5064
5177
|
try {
|
|
5065
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
5178
|
+
bytes = await read(isAbsolute3(path) ? path : join25(opts.worktree, path));
|
|
5066
5179
|
} catch (e) {
|
|
5067
5180
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
5068
5181
|
continue;
|
|
@@ -5143,8 +5256,8 @@ var init_shotUpload = __esm({
|
|
|
5143
5256
|
|
|
5144
5257
|
// src/oauth.ts
|
|
5145
5258
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
5146
|
-
import { readFileSync as
|
|
5147
|
-
import { join as
|
|
5259
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, unlinkSync as unlinkSync4 } from "fs";
|
|
5260
|
+
import { join as join26 } from "path";
|
|
5148
5261
|
function makePkce() {
|
|
5149
5262
|
const verifier = randomBytes2(48).toString("base64url");
|
|
5150
5263
|
const challenge = createHash3("sha256").update(verifier).digest("base64url");
|
|
@@ -5228,7 +5341,7 @@ function toStore(clientId, t, now) {
|
|
|
5228
5341
|
}
|
|
5229
5342
|
function loadStore() {
|
|
5230
5343
|
try {
|
|
5231
|
-
return JSON.parse(
|
|
5344
|
+
return JSON.parse(readFileSync20(STORE_PATH2, "utf8"));
|
|
5232
5345
|
} catch {
|
|
5233
5346
|
return null;
|
|
5234
5347
|
}
|
|
@@ -5244,7 +5357,7 @@ function clearStore() {
|
|
|
5244
5357
|
}
|
|
5245
5358
|
function loadClientId() {
|
|
5246
5359
|
try {
|
|
5247
|
-
return JSON.parse(
|
|
5360
|
+
return JSON.parse(readFileSync20(CLIENT_PATH, "utf8")).clientId;
|
|
5248
5361
|
} catch {
|
|
5249
5362
|
return null;
|
|
5250
5363
|
}
|
|
@@ -5279,8 +5392,8 @@ var init_oauth = __esm({
|
|
|
5279
5392
|
AUTHORIZE = "/api/oauth/authorize";
|
|
5280
5393
|
TOKEN = "/api/oauth/token";
|
|
5281
5394
|
SCOPE = "openid profile email taskless:full";
|
|
5282
|
-
STORE_PATH2 =
|
|
5283
|
-
CLIENT_PATH =
|
|
5395
|
+
STORE_PATH2 = join26(SWARM_HOME, "oauth.json");
|
|
5396
|
+
CLIENT_PATH = join26(SWARM_HOME, "oauth-client.json");
|
|
5284
5397
|
base2 = (api) => api.replace(/\/$/, "");
|
|
5285
5398
|
}
|
|
5286
5399
|
});
|
|
@@ -5326,11 +5439,11 @@ var init_authState = __esm({
|
|
|
5326
5439
|
});
|
|
5327
5440
|
|
|
5328
5441
|
// src/workspace.ts
|
|
5329
|
-
import { readFileSync as
|
|
5330
|
-
import { join as
|
|
5442
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5 } from "fs";
|
|
5443
|
+
import { join as join27 } from "path";
|
|
5331
5444
|
function loadWorkspace() {
|
|
5332
5445
|
try {
|
|
5333
|
-
const w = JSON.parse(
|
|
5446
|
+
const w = JSON.parse(readFileSync21(WORKSPACE_PATH, "utf8"));
|
|
5334
5447
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
5335
5448
|
} catch {
|
|
5336
5449
|
return null;
|
|
@@ -5344,7 +5457,7 @@ var init_workspace = __esm({
|
|
|
5344
5457
|
"src/workspace.ts"() {
|
|
5345
5458
|
"use strict";
|
|
5346
5459
|
init_config();
|
|
5347
|
-
WORKSPACE_PATH =
|
|
5460
|
+
WORKSPACE_PATH = join27(SWARM_HOME, "workspace.json");
|
|
5348
5461
|
}
|
|
5349
5462
|
});
|
|
5350
5463
|
|
|
@@ -5990,6 +6103,7 @@ function rowToRun(row) {
|
|
|
5990
6103
|
tokens_in: row.tokens_in ?? 0,
|
|
5991
6104
|
tokens_out: row.tokens_out ?? 0,
|
|
5992
6105
|
duration_ms: row.duration_ms ?? 0,
|
|
6106
|
+
cost_unmeasured: row.cost_unmeasured ?? 0,
|
|
5993
6107
|
pr_url: row.pr_url ?? null,
|
|
5994
6108
|
// TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
|
|
5995
6109
|
changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
|
|
@@ -6070,6 +6184,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
6070
6184
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
6071
6185
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
6072
6186
|
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
6187
|
+
cost_unmeasured INTEGER NOT NULL DEFAULT 0,
|
|
6073
6188
|
pr_url TEXT,
|
|
6074
6189
|
superseded_by INTEGER,
|
|
6075
6190
|
decision TEXT,
|
|
@@ -6185,6 +6300,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6185
6300
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
6186
6301
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
6187
6302
|
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
6303
|
+
// TASWM-548: сколько запусков прогона остались без снятой цены — отличает «бесплатно»
|
|
6304
|
+
// от «не измерено». Прогоны до фичи получают 0: их нули мы перепроверить уже не можем,
|
|
6305
|
+
// а задним числом объявлять их неизмеренными значило бы утверждать непроверенное.
|
|
6306
|
+
"ALTER TABLE runs ADD COLUMN cost_unmeasured INTEGER NOT NULL DEFAULT 0",
|
|
6188
6307
|
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
6189
6308
|
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
6190
6309
|
"ALTER TABLE runs ADD COLUMN effort TEXT",
|
|
@@ -6943,9 +7062,9 @@ var init_droneStore = __esm({
|
|
|
6943
7062
|
});
|
|
6944
7063
|
|
|
6945
7064
|
// src/overmind.ts
|
|
6946
|
-
import { homedir as
|
|
6947
|
-
import { existsSync as existsSync20, readFileSync as
|
|
6948
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
7065
|
+
import { homedir as homedir7 } from "os";
|
|
7066
|
+
import { existsSync as existsSync20, readFileSync as readFileSync25, readdirSync as readdirSync14, renameSync as renameSync12 } from "fs";
|
|
7067
|
+
import { basename as basename6, dirname as dirname5, join as join34 } from "path";
|
|
6949
7068
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
6950
7069
|
import { spawn as spawn4 } from "child_process";
|
|
6951
7070
|
|
|
@@ -7025,7 +7144,7 @@ init_config();
|
|
|
7025
7144
|
init_resources();
|
|
7026
7145
|
init_names2();
|
|
7027
7146
|
init_storm();
|
|
7028
|
-
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as
|
|
7147
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
7029
7148
|
import { dirname as dirname2 } from "path";
|
|
7030
7149
|
import { randomUUID } from "crypto";
|
|
7031
7150
|
|
|
@@ -7082,12 +7201,12 @@ init_zoneNotes();
|
|
|
7082
7201
|
init_toolFailure();
|
|
7083
7202
|
|
|
7084
7203
|
// src/logTail.ts
|
|
7085
|
-
import { closeSync, existsSync as existsSync6, openSync, readSync, readdirSync as
|
|
7086
|
-
import { join as
|
|
7204
|
+
import { closeSync, existsSync as existsSync6, openSync, readSync, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
7205
|
+
import { join as join10 } from "path";
|
|
7087
7206
|
|
|
7088
7207
|
// src/runLogDir.ts
|
|
7089
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as
|
|
7090
|
-
import { join as
|
|
7208
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync5, renameSync as renameSync3, statSync as statSync4 } from "fs";
|
|
7209
|
+
import { join as join9 } from "path";
|
|
7091
7210
|
var LEGACY_RUN_LOGS_SUBDIR = "legacy";
|
|
7092
7211
|
function runLogDirName(runLogKey) {
|
|
7093
7212
|
const safe2 = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
@@ -7095,7 +7214,7 @@ function runLogDirName(runLogKey) {
|
|
|
7095
7214
|
return /^\d+$/.test(safe2) ? `k-${safe2}` : safe2;
|
|
7096
7215
|
}
|
|
7097
7216
|
function runLogDirPath(runLogsDir, runLogKey) {
|
|
7098
|
-
return
|
|
7217
|
+
return join9(runLogsDir, runLogDirName(runLogKey));
|
|
7099
7218
|
}
|
|
7100
7219
|
function findRunLogDir(runLogsDir, keys) {
|
|
7101
7220
|
for (const key of keys) {
|
|
@@ -7110,10 +7229,10 @@ function runLogDirForWrite(runLogsDir, keys) {
|
|
|
7110
7229
|
return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
|
|
7111
7230
|
}
|
|
7112
7231
|
function sweepLegacyRunLogs(runLogsDir) {
|
|
7113
|
-
const legacyDir =
|
|
7232
|
+
const legacyDir = join9(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
|
|
7114
7233
|
let entries;
|
|
7115
7234
|
try {
|
|
7116
|
-
entries =
|
|
7235
|
+
entries = readdirSync5(runLogsDir);
|
|
7117
7236
|
} catch {
|
|
7118
7237
|
return { moved: 0, failed: 0, legacyDir };
|
|
7119
7238
|
}
|
|
@@ -7121,12 +7240,12 @@ function sweepLegacyRunLogs(runLogsDir) {
|
|
|
7121
7240
|
let failed = 0;
|
|
7122
7241
|
for (const name of entries) {
|
|
7123
7242
|
if (!/^\d+$/.test(name)) continue;
|
|
7124
|
-
const from =
|
|
7243
|
+
const from = join9(runLogsDir, name);
|
|
7125
7244
|
try {
|
|
7126
|
-
if (!
|
|
7245
|
+
if (!statSync4(from).isDirectory()) continue;
|
|
7127
7246
|
mkdirSync5(legacyDir, { recursive: true });
|
|
7128
|
-
let to =
|
|
7129
|
-
for (let n = 2; existsSync5(to) && n < 100; n++) to =
|
|
7247
|
+
let to = join9(legacyDir, name);
|
|
7248
|
+
for (let n = 2; existsSync5(to) && n < 100; n++) to = join9(legacyDir, `${name}-${n}`);
|
|
7130
7249
|
renameSync3(from, to);
|
|
7131
7250
|
moved++;
|
|
7132
7251
|
} catch {
|
|
@@ -7146,7 +7265,7 @@ function fileHeader(name, size, shown) {
|
|
|
7146
7265
|
function readTail(path, opts) {
|
|
7147
7266
|
let size;
|
|
7148
7267
|
try {
|
|
7149
|
-
size =
|
|
7268
|
+
size = statSync5(path).size;
|
|
7150
7269
|
} catch {
|
|
7151
7270
|
return null;
|
|
7152
7271
|
}
|
|
@@ -7181,7 +7300,7 @@ function latestRunLaunch(runLogsDir, runLogKeys) {
|
|
|
7181
7300
|
if (!dir) return null;
|
|
7182
7301
|
let files;
|
|
7183
7302
|
try {
|
|
7184
|
-
files =
|
|
7303
|
+
files = readdirSync6(dir);
|
|
7185
7304
|
} catch {
|
|
7186
7305
|
return null;
|
|
7187
7306
|
}
|
|
@@ -7192,8 +7311,8 @@ function latestRunLaunch(runLogsDir, runLogKeys) {
|
|
|
7192
7311
|
}
|
|
7193
7312
|
if (indexes.size === 0) return null;
|
|
7194
7313
|
const index = Math.max(...indexes);
|
|
7195
|
-
const out = [
|
|
7196
|
-
const err = existsSync6(
|
|
7314
|
+
const out = [join10(dir, `${index}.json`), join10(dir, `${index}.json.done`)].find((p) => existsSync6(p)) ?? null;
|
|
7315
|
+
const err = existsSync6(join10(dir, `${index}.err.log`)) ? join10(dir, `${index}.err.log`) : null;
|
|
7197
7316
|
return { index, total: indexes.size, out, err };
|
|
7198
7317
|
}
|
|
7199
7318
|
function runLogTail(runLogsDir, runLogKeys, opts) {
|
|
@@ -7464,13 +7583,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7464
7583
|
recover() {
|
|
7465
7584
|
if (existsSync7(this.mapPath)) {
|
|
7466
7585
|
try {
|
|
7467
|
-
const obj = JSON.parse(
|
|
7586
|
+
const obj = JSON.parse(readFileSync7(this.mapPath, "utf8"));
|
|
7468
7587
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
7469
7588
|
} catch {
|
|
7470
7589
|
}
|
|
7471
7590
|
}
|
|
7472
7591
|
if (existsSync7(this.outboxPath)) {
|
|
7473
|
-
const txt =
|
|
7592
|
+
const txt = readFileSync7(this.outboxPath, "utf8");
|
|
7474
7593
|
for (const line of txt.split("\n")) {
|
|
7475
7594
|
const s = line.trim();
|
|
7476
7595
|
if (!s) continue;
|
|
@@ -7493,7 +7612,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7493
7612
|
if (!existsSync7(this.stormPath)) return;
|
|
7494
7613
|
let txt;
|
|
7495
7614
|
try {
|
|
7496
|
-
txt =
|
|
7615
|
+
txt = readFileSync7(this.stormPath, "utf8");
|
|
7497
7616
|
} catch {
|
|
7498
7617
|
return;
|
|
7499
7618
|
}
|
|
@@ -7577,6 +7696,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7577
7696
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
7578
7697
|
if ("tokens_out" in patch && patch.tokens_out !== void 0) out.tokensOut = patch.tokens_out;
|
|
7579
7698
|
if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
|
|
7699
|
+
if ("cost_unmeasured" in patch && patch.cost_unmeasured !== void 0) out.costUnmeasured = patch.cost_unmeasured;
|
|
7580
7700
|
if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
|
|
7581
7701
|
if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
|
|
7582
7702
|
if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
|
|
@@ -7659,6 +7779,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7659
7779
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
7660
7780
|
tokens_out: 0,
|
|
7661
7781
|
duration_ms: 0,
|
|
7782
|
+
cost_unmeasured: 0,
|
|
7783
|
+
// запусков без снятой цены (TASWM-548) — копится тем же учётом
|
|
7662
7784
|
pr_url: null,
|
|
7663
7785
|
changed_paths: [],
|
|
7664
7786
|
// файлы прогона снимаются при открытии PR (TASWM-506)
|
|
@@ -9279,6 +9401,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
9279
9401
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
9280
9402
|
tokens_out: 0,
|
|
9281
9403
|
duration_ms: 0,
|
|
9404
|
+
cost_unmeasured: 0,
|
|
9405
|
+
// запусков без снятой цены (TASWM-548) — прод-поле, заполняется ниже
|
|
9282
9406
|
pr_url: null,
|
|
9283
9407
|
changed_paths: [],
|
|
9284
9408
|
// занятые файлы (TASWM-506) — прод-поле, заполняется ниже
|
|
@@ -9360,6 +9484,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
9360
9484
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
9361
9485
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
9362
9486
|
local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
|
|
9487
|
+
local.cost_unmeasured = typeof pr.costUnmeasured === "number" ? pr.costUnmeasured : 0;
|
|
9363
9488
|
local.pr_url = pr.prUrl ?? null;
|
|
9364
9489
|
local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
|
|
9365
9490
|
local.stop_reason = pr.stopReason ?? null;
|
|
@@ -9694,11 +9819,11 @@ function tickPulse(health, now, intervalMs) {
|
|
|
9694
9819
|
}
|
|
9695
9820
|
|
|
9696
9821
|
// src/logRotate.ts
|
|
9697
|
-
import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as
|
|
9822
|
+
import { copyFileSync as copyFileSync2, renameSync as renameSync5, statSync as statSync6, fstatSync, truncateSync, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
|
|
9698
9823
|
var realIo = {
|
|
9699
9824
|
size: (path) => {
|
|
9700
9825
|
try {
|
|
9701
|
-
return
|
|
9826
|
+
return statSync6(path).size;
|
|
9702
9827
|
} catch {
|
|
9703
9828
|
return null;
|
|
9704
9829
|
}
|
|
@@ -9720,7 +9845,7 @@ var realOwnerIo = {
|
|
|
9720
9845
|
},
|
|
9721
9846
|
statPath: (path) => {
|
|
9722
9847
|
try {
|
|
9723
|
-
const s =
|
|
9848
|
+
const s = statSync6(path);
|
|
9724
9849
|
return { dev: s.dev, ino: s.ino };
|
|
9725
9850
|
} catch {
|
|
9726
9851
|
return null;
|
|
@@ -9754,7 +9879,7 @@ function rotateDaemonLog(path, opts, io = realIo) {
|
|
|
9754
9879
|
}
|
|
9755
9880
|
|
|
9756
9881
|
// src/daemonLaunch.ts
|
|
9757
|
-
import { openSync as openSync2, closeSync as closeSync2, readFileSync as
|
|
9882
|
+
import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync9 } from "fs";
|
|
9758
9883
|
import { spawn } from "child_process";
|
|
9759
9884
|
var realRespawnIo = {
|
|
9760
9885
|
isTty: () => process.stdout.isTTY === true,
|
|
@@ -9854,13 +9979,13 @@ async function buildRepoRegistry(paths) {
|
|
|
9854
9979
|
}
|
|
9855
9980
|
|
|
9856
9981
|
// src/repoMemory.ts
|
|
9857
|
-
import { readFileSync as
|
|
9858
|
-
import { join as
|
|
9982
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
9983
|
+
import { join as join11 } from "path";
|
|
9859
9984
|
var FILE = "repos.json";
|
|
9860
9985
|
function loadRememberedRepos(home) {
|
|
9861
9986
|
let raw;
|
|
9862
9987
|
try {
|
|
9863
|
-
raw =
|
|
9988
|
+
raw = readFileSync10(join11(home, FILE), "utf8");
|
|
9864
9989
|
} catch {
|
|
9865
9990
|
return [];
|
|
9866
9991
|
}
|
|
@@ -9875,7 +10000,7 @@ function loadRememberedRepos(home) {
|
|
|
9875
10000
|
function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
|
|
9876
10001
|
const clean = paths.filter((p) => p.trim().length > 0);
|
|
9877
10002
|
if (clean.length === 0) return;
|
|
9878
|
-
writeFileSync8(
|
|
10003
|
+
writeFileSync8(join11(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
|
|
9879
10004
|
`);
|
|
9880
10005
|
}
|
|
9881
10006
|
function pickRepoPaths(args) {
|
|
@@ -9889,13 +10014,13 @@ function pickRepoPaths(args) {
|
|
|
9889
10014
|
init_src();
|
|
9890
10015
|
init_config();
|
|
9891
10016
|
init_linkFailure();
|
|
9892
|
-
import { homedir as
|
|
9893
|
-
import { readFileSync as
|
|
9894
|
-
import { isAbsolute, join as
|
|
10017
|
+
import { homedir as homedir5 } from "os";
|
|
10018
|
+
import { readFileSync as readFileSync12, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
10019
|
+
import { isAbsolute, join as join13, resolve as resolve2 } from "path";
|
|
9895
10020
|
|
|
9896
10021
|
// src/repoScan.ts
|
|
9897
|
-
import { readdirSync as
|
|
9898
|
-
import { join as
|
|
10022
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync7 } from "fs";
|
|
10023
|
+
import { join as join12 } from "path";
|
|
9899
10024
|
var SKIP = /* @__PURE__ */ new Set([
|
|
9900
10025
|
"node_modules",
|
|
9901
10026
|
"vendor",
|
|
@@ -9925,7 +10050,7 @@ function parseOriginFromGitConfig(text) {
|
|
|
9925
10050
|
}
|
|
9926
10051
|
function originOf(dir) {
|
|
9927
10052
|
try {
|
|
9928
|
-
return parseOriginFromGitConfig(
|
|
10053
|
+
return parseOriginFromGitConfig(readFileSync11(join12(dir, ".git", "config"), "utf8"));
|
|
9929
10054
|
} catch {
|
|
9930
10055
|
return null;
|
|
9931
10056
|
}
|
|
@@ -9954,15 +10079,15 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
9954
10079
|
}
|
|
9955
10080
|
let entries;
|
|
9956
10081
|
try {
|
|
9957
|
-
entries =
|
|
10082
|
+
entries = readdirSync7(dir);
|
|
9958
10083
|
} catch {
|
|
9959
10084
|
return;
|
|
9960
10085
|
}
|
|
9961
10086
|
for (const name of entries) {
|
|
9962
10087
|
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
9963
|
-
const child =
|
|
10088
|
+
const child = join12(dir, name);
|
|
9964
10089
|
try {
|
|
9965
|
-
if (!
|
|
10090
|
+
if (!statSync7(child).isDirectory()) continue;
|
|
9966
10091
|
} catch {
|
|
9967
10092
|
continue;
|
|
9968
10093
|
}
|
|
@@ -9982,13 +10107,13 @@ function defaultScanRoots(home, knownPaths = []) {
|
|
|
9982
10107
|
if (parent && parent !== p) add(parent);
|
|
9983
10108
|
}
|
|
9984
10109
|
for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
|
|
9985
|
-
add(
|
|
10110
|
+
add(join12(home, name));
|
|
9986
10111
|
}
|
|
9987
10112
|
return out;
|
|
9988
10113
|
}
|
|
9989
10114
|
|
|
9990
10115
|
// src/repoDiscovery.ts
|
|
9991
|
-
function resolveScanRoots(raw, home =
|
|
10116
|
+
function resolveScanRoots(raw, home = homedir5(), knownPaths = []) {
|
|
9992
10117
|
const v = (raw ?? "").trim();
|
|
9993
10118
|
if (!v) return defaultScanRoots(home, knownPaths);
|
|
9994
10119
|
if (["off", "none", "0"].includes(v.toLowerCase())) return [];
|
|
@@ -9996,7 +10121,7 @@ function resolveScanRoots(raw, home = homedir4(), knownPaths = []) {
|
|
|
9996
10121
|
for (const part of v.split(",")) {
|
|
9997
10122
|
const p = part.trim();
|
|
9998
10123
|
if (!p) continue;
|
|
9999
|
-
const abs = p === "~" ? home : p.startsWith("~/") ?
|
|
10124
|
+
const abs = p === "~" ? home : p.startsWith("~/") ? join13(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
|
|
10000
10125
|
if (!out.includes(abs)) out.push(abs);
|
|
10001
10126
|
}
|
|
10002
10127
|
return out;
|
|
@@ -10210,7 +10335,7 @@ function repoMapRows(state) {
|
|
|
10210
10335
|
}
|
|
10211
10336
|
function loadRepoMapCache(path) {
|
|
10212
10337
|
try {
|
|
10213
|
-
const raw = JSON.parse(
|
|
10338
|
+
const raw = JSON.parse(readFileSync12(path, "utf8"));
|
|
10214
10339
|
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
10215
10340
|
return {
|
|
10216
10341
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
@@ -10233,15 +10358,15 @@ function writeRepoMapCache(path, entries, scannedAt = stamp()) {
|
|
|
10233
10358
|
|
|
10234
10359
|
// src/hostIdentity.ts
|
|
10235
10360
|
import { hostname } from "os";
|
|
10236
|
-
import { existsSync as existsSync9, readFileSync as
|
|
10237
|
-
import { join as
|
|
10361
|
+
import { existsSync as existsSync9, readFileSync as readFileSync13, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
10362
|
+
import { join as join14 } from "path";
|
|
10238
10363
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
10239
10364
|
function hostIdentityPath(home) {
|
|
10240
|
-
return
|
|
10365
|
+
return join14(home, "host.json");
|
|
10241
10366
|
}
|
|
10242
10367
|
function loadHostIdentity(deps) {
|
|
10243
10368
|
const exists = deps.exists ?? existsSync9;
|
|
10244
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
10369
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync13(p, "utf8"));
|
|
10245
10370
|
const writeFile4 = deps.writeFile ?? ((p, data) => {
|
|
10246
10371
|
writeFileSync10(`${p}.tmp`, data);
|
|
10247
10372
|
renameSync7(`${p}.tmp`, p);
|
|
@@ -10305,7 +10430,18 @@ function pauseDue(reliefSince, lastPauseAt, now, cfg) {
|
|
|
10305
10430
|
if (reliefSince == null || now - reliefSince < cfg.sustainMs) return false;
|
|
10306
10431
|
return lastPauseAt == null || now - lastPauseAt >= cfg.pauseWindowMs;
|
|
10307
10432
|
}
|
|
10433
|
+
function loadBearingRuns(runs) {
|
|
10434
|
+
return runs.filter((r) => r.status === "running" && r.pid != null);
|
|
10435
|
+
}
|
|
10436
|
+
function reliefWouldStrangleSoleWorker(runs) {
|
|
10437
|
+
return loadBearingRuns(runs).length <= 1;
|
|
10438
|
+
}
|
|
10439
|
+
function reliefSoleWorkerReason(decision) {
|
|
10440
|
+
const head = `${RELIEF_PAUSE_MARK}: \u041D\u0415 \u0441\u043D\u0438\u043C\u0430\u044E \u2014 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043E\u0434\u0438\u043D \u0434\u0440\u043E\u043D, \u0438 \u0432\u0441\u044F \u043D\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0435\u0433\u043E \u0436\u0435 (\u0441\u043D\u044F\u0442\u044C \u0435\u0433\u043E \u0437\u043D\u0430\u0447\u0438\u0442 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u0438\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \u043E\u0442 \u0442\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B, \u0440\u0430\u0434\u0438 \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043E\u043D\u0430 \u043D\u0443\u0436\u043D\u0430; TASWM-550)`;
|
|
10441
|
+
return decision ? `${head} \xB7 ${decision}` : head;
|
|
10442
|
+
}
|
|
10308
10443
|
function pickPauseCandidate(runs, protectedRunIds) {
|
|
10444
|
+
if (reliefWouldStrangleSoleWorker(runs)) return null;
|
|
10309
10445
|
const candidates = runs.filter(
|
|
10310
10446
|
(r) => r.kind === "drone" && r.status === "running" && r.pid != null && r.resolves_run_id == null && r.reviews_run_id == null && // TASWM-535: прогон, признанный ТЯЖЁЛЫМ, разгрузка не снимает. Правильная реакция на
|
|
10311
10447
|
// «тяжело» — уменьшить число соседей, а не убить того, кто работает: 27.08 перегруженная
|
|
@@ -10325,8 +10461,8 @@ function pickResumeCandidate(pausedForRelief) {
|
|
|
10325
10461
|
}
|
|
10326
10462
|
|
|
10327
10463
|
// src/reliefLoop.ts
|
|
10328
|
-
import { existsSync as existsSync10, readFileSync as
|
|
10329
|
-
import { join as
|
|
10464
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14, renameSync as renameSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
10465
|
+
import { join as join15 } from "path";
|
|
10330
10466
|
var numEnv3 = (name, def) => {
|
|
10331
10467
|
const v = Number(process.env[name]);
|
|
10332
10468
|
return Number.isFinite(v) && v > 0 ? v : def;
|
|
@@ -10361,11 +10497,11 @@ function reliefLoopTripped(entry, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
|
10361
10497
|
return !!entry && entry.pauses >= cfg.limit;
|
|
10362
10498
|
}
|
|
10363
10499
|
function reliefLoopStatePath(home) {
|
|
10364
|
-
return
|
|
10500
|
+
return join15(home, "relief-loop.json");
|
|
10365
10501
|
}
|
|
10366
10502
|
function loadReliefLoopState(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10367
10503
|
const exists = io.exists ?? existsSync10;
|
|
10368
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
10504
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync14(p, "utf8"));
|
|
10369
10505
|
const path = reliefLoopStatePath(home);
|
|
10370
10506
|
if (!exists(path)) return {};
|
|
10371
10507
|
try {
|
|
@@ -10549,15 +10685,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
10549
10685
|
|
|
10550
10686
|
// src/launcher.ts
|
|
10551
10687
|
import { spawn as spawn2 } from "child_process";
|
|
10552
|
-
import { closeSync as closeSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as
|
|
10688
|
+
import { closeSync as closeSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync9 } from "fs";
|
|
10553
10689
|
import { createRequire } from "module";
|
|
10554
|
-
import { homedir as
|
|
10555
|
-
import { dirname as dirname3, join as
|
|
10690
|
+
import { homedir as homedir6 } from "os";
|
|
10691
|
+
import { dirname as dirname3, join as join17 } from "path";
|
|
10556
10692
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10557
10693
|
|
|
10558
10694
|
// src/droneHome.ts
|
|
10559
|
-
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as
|
|
10560
|
-
import { join as
|
|
10695
|
+
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync8, rmSync, statSync as statSync8, symlinkSync as symlinkSync2 } from "fs";
|
|
10696
|
+
import { join as join16, relative, sep } from "path";
|
|
10561
10697
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
10562
10698
|
function droneHomeDeny(env = process.env) {
|
|
10563
10699
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -10576,28 +10712,28 @@ function buildDroneHome(opts) {
|
|
|
10576
10712
|
const deny = opts.deny ?? droneHomeDeny();
|
|
10577
10713
|
const platform = opts.platform ?? process.platform;
|
|
10578
10714
|
mkdirSync7(droneHome, { recursive: true, mode: 448 });
|
|
10579
|
-
const wanted = new Set(mirroredEntries(
|
|
10715
|
+
const wanted = new Set(mirroredEntries(readdirSync8(realHome), deny, platform));
|
|
10580
10716
|
const rel = relative(realHome, droneHome);
|
|
10581
10717
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
10582
|
-
for (const existing of
|
|
10718
|
+
for (const existing of readdirSync8(droneHome)) {
|
|
10583
10719
|
if (wanted.has(existing)) continue;
|
|
10584
|
-
const p =
|
|
10720
|
+
const p = join16(droneHome, existing);
|
|
10585
10721
|
try {
|
|
10586
10722
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
10587
10723
|
} catch {
|
|
10588
10724
|
}
|
|
10589
10725
|
}
|
|
10590
10726
|
for (const name of wanted) {
|
|
10591
|
-
const link2 =
|
|
10727
|
+
const link2 = join16(droneHome, name);
|
|
10592
10728
|
try {
|
|
10593
10729
|
lstatSync(link2);
|
|
10594
10730
|
continue;
|
|
10595
10731
|
} catch {
|
|
10596
10732
|
}
|
|
10597
|
-
const target =
|
|
10733
|
+
const target = join16(realHome, name);
|
|
10598
10734
|
let isDir;
|
|
10599
10735
|
try {
|
|
10600
|
-
isDir =
|
|
10736
|
+
isDir = statSync8(target).isDirectory();
|
|
10601
10737
|
} catch {
|
|
10602
10738
|
continue;
|
|
10603
10739
|
}
|
|
@@ -10615,7 +10751,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
10615
10751
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
10616
10752
|
];
|
|
10617
10753
|
function realPathEnvPins(realHome) {
|
|
10618
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
10754
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join16(realHome, ...segments)]));
|
|
10619
10755
|
}
|
|
10620
10756
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
10621
10757
|
return [
|
|
@@ -10727,7 +10863,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
10727
10863
|
const env = { ...base3 };
|
|
10728
10864
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
10729
10865
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
10730
|
-
env.TURBO_CACHE_DIR =
|
|
10866
|
+
env.TURBO_CACHE_DIR = join17(worktree, ".turbo", "cache");
|
|
10731
10867
|
}
|
|
10732
10868
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
10733
10869
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -10755,10 +10891,10 @@ var DAEMON_STATE_ENV = [
|
|
|
10755
10891
|
"TASKLESS_TOKEN"
|
|
10756
10892
|
];
|
|
10757
10893
|
function bundledRoleDir(kind) {
|
|
10758
|
-
return
|
|
10894
|
+
return join17(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
10759
10895
|
}
|
|
10760
10896
|
function roleDir(worktree, kind) {
|
|
10761
|
-
return
|
|
10897
|
+
return join17(worktree, ".claude", "roles", kind);
|
|
10762
10898
|
}
|
|
10763
10899
|
function roleDirs(worktree, kind) {
|
|
10764
10900
|
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync11(dir));
|
|
@@ -10769,7 +10905,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
10769
10905
|
mkdirSync8(dir, { recursive: true });
|
|
10770
10906
|
let files = [];
|
|
10771
10907
|
try {
|
|
10772
|
-
files =
|
|
10908
|
+
files = readdirSync9(dir);
|
|
10773
10909
|
} catch {
|
|
10774
10910
|
}
|
|
10775
10911
|
let next = 0;
|
|
@@ -10777,7 +10913,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
10777
10913
|
const m = /^(\d+)\.json/.exec(f);
|
|
10778
10914
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
10779
10915
|
}
|
|
10780
|
-
return
|
|
10916
|
+
return join17(dir, `${next}.json`);
|
|
10781
10917
|
}
|
|
10782
10918
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
10783
10919
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -10858,11 +10994,11 @@ function makeLauncher(cfg) {
|
|
|
10858
10994
|
} catch (e) {
|
|
10859
10995
|
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)}`);
|
|
10860
10996
|
}
|
|
10861
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
10997
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir6();
|
|
10862
10998
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
10863
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync11(
|
|
10999
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync11(join17(cfg.droneIsolation.swarmHome, "token"))) {
|
|
10864
11000
|
console.error(
|
|
10865
|
-
`[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 ${
|
|
11001
|
+
`[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 ${join17(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.`
|
|
10866
11002
|
);
|
|
10867
11003
|
}
|
|
10868
11004
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10923,39 +11059,94 @@ function makeLauncher(cfg) {
|
|
|
10923
11059
|
init_procCleanup();
|
|
10924
11060
|
|
|
10925
11061
|
// src/cost.ts
|
|
10926
|
-
import { readFileSync as
|
|
10927
|
-
import { join as
|
|
11062
|
+
import { readFileSync as readFileSync15, readdirSync as readdirSync10, renameSync as renameSync9, statSync as statSync9 } from "fs";
|
|
11063
|
+
import { join as join18 } from "path";
|
|
10928
11064
|
init_executors();
|
|
10929
11065
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
11066
|
+
var SEGMENT_LOG_RE = /^(\d+)\.json(\.done)?$/;
|
|
11067
|
+
var RESCUE_OPEN_WINDOW_MAX_MS = 12 * 60 * 60 * 1e3;
|
|
10930
11068
|
function unprocessedLogFiles(dir) {
|
|
10931
11069
|
try {
|
|
10932
|
-
return
|
|
11070
|
+
return readdirSync10(dir).filter((f) => UNPROCESSED_LOG_RE.test(f));
|
|
10933
11071
|
} catch {
|
|
10934
11072
|
return [];
|
|
10935
11073
|
}
|
|
10936
11074
|
}
|
|
11075
|
+
function segmentStarts(dir) {
|
|
11076
|
+
const starts = /* @__PURE__ */ new Map();
|
|
11077
|
+
let entries;
|
|
11078
|
+
try {
|
|
11079
|
+
entries = readdirSync10(dir);
|
|
11080
|
+
} catch {
|
|
11081
|
+
return starts;
|
|
11082
|
+
}
|
|
11083
|
+
for (const f of entries) {
|
|
11084
|
+
const m = SEGMENT_LOG_RE.exec(f);
|
|
11085
|
+
if (!m) continue;
|
|
11086
|
+
try {
|
|
11087
|
+
const st = statSync9(join18(dir, f));
|
|
11088
|
+
starts.set(Number(m[1]), st.birthtimeMs > 0 ? st.birthtimeMs : st.ctimeMs);
|
|
11089
|
+
} catch {
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
return starts;
|
|
11093
|
+
}
|
|
11094
|
+
function segmentWindow(starts, index, nowMs) {
|
|
11095
|
+
const from = starts.get(index);
|
|
11096
|
+
if (from === void 0) return null;
|
|
11097
|
+
let to = null;
|
|
11098
|
+
for (const [i, at] of starts) {
|
|
11099
|
+
if (i > index && (to === null || at < to)) to = at;
|
|
11100
|
+
}
|
|
11101
|
+
if (to !== null) return { from, to: Math.max(to, from) };
|
|
11102
|
+
if (nowMs - from > RESCUE_OPEN_WINDOW_MAX_MS) return null;
|
|
11103
|
+
return { from, to: nowMs };
|
|
11104
|
+
}
|
|
11105
|
+
function rescueSegmentSpend(deps, run, executor, starts, file, nowMs) {
|
|
11106
|
+
if (!executor.rescueSpend || !run.worktree_path) return null;
|
|
11107
|
+
const index = Number(/^(\d+)\.json$/.exec(file)?.[1]);
|
|
11108
|
+
if (!Number.isFinite(index)) return null;
|
|
11109
|
+
const window = segmentWindow(starts, index, nowMs);
|
|
11110
|
+
if (!window) return null;
|
|
11111
|
+
let spend = null;
|
|
11112
|
+
try {
|
|
11113
|
+
spend = executor.rescueSpend(run.worktree_path, window.from, window.to);
|
|
11114
|
+
} catch (e) {
|
|
11115
|
+
console.warn(`[overmind] cost \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: \u0437\u0430\u043F\u0438\u0441\u044C \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043B\u0430\u0441\u044C (${file}) \u2014 ${String(e)}`);
|
|
11116
|
+
return null;
|
|
11117
|
+
}
|
|
11118
|
+
if (!spend) return null;
|
|
11119
|
+
console.warn(
|
|
11120
|
+
`[overmind] cost \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: \u0438\u0442\u043E\u0433 ${file} \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D (\u0443\u0431\u0438\u0442 \u043D\u0430 \u043F\u043E\u043B\u043F\u0443\u0442\u0438) \u2014 \u0442\u0440\u0430\u0442\u0430 \u0441\u043D\u044F\u0442\u0430 \u0438\u0437 \u0437\u0430\u043F\u0438\u0441\u0438 \u0441\u0435\u0441\u0441\u0438\u0438: $${spend.costUsd.toFixed(4)}, \u0442\u043E\u043A\u0435\u043D\u043E\u0432 ${spend.tokensIn ?? 0}/${spend.tokensOut ?? 0} (TASWM-548)`
|
|
11121
|
+
);
|
|
11122
|
+
return spend;
|
|
11123
|
+
}
|
|
10937
11124
|
function accountRunCost(deps, run) {
|
|
10938
11125
|
const dir = findRunLogDir(deps.runLogsDir, deps.db.runLogKeys(run.id));
|
|
10939
11126
|
const files = dir ? unprocessedLogFiles(dir) : [];
|
|
10940
11127
|
if (!dir || files.length === 0) return;
|
|
10941
11128
|
const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
|
|
10942
11129
|
const executor = deps.executor ?? resolveExecutor();
|
|
11130
|
+
const now = deps.now?.() ?? Date.now();
|
|
11131
|
+
const starts = segmentStarts(dir);
|
|
10943
11132
|
let added = 0;
|
|
10944
11133
|
let tokensIn = 0;
|
|
10945
11134
|
let tokensOut = 0;
|
|
10946
11135
|
let durationMs = 0;
|
|
11136
|
+
let unmeasured = 0;
|
|
10947
11137
|
const processed = [];
|
|
10948
11138
|
for (const f of files) {
|
|
10949
|
-
const full =
|
|
11139
|
+
const full = join18(dir, f);
|
|
10950
11140
|
let raw = null;
|
|
10951
11141
|
try {
|
|
10952
|
-
raw =
|
|
11142
|
+
raw = readFileSync15(full, "utf8");
|
|
10953
11143
|
} catch {
|
|
10954
11144
|
continue;
|
|
10955
11145
|
}
|
|
10956
|
-
const result = executor.parseResult(raw, model);
|
|
11146
|
+
const result = executor.parseResult(raw, model) ?? rescueSegmentSpend(deps, run, executor, starts, f, now);
|
|
10957
11147
|
if (!result) {
|
|
10958
|
-
|
|
11148
|
+
unmeasured += 1;
|
|
11149
|
+
console.warn(`[overmind] cost \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: \u043D\u0435 \u0440\u0430\u0441\u043F\u0430\u0440\u0441\u0438\u043B\u0441\u044F ${full} \u0438 \u043D\u0435 \u0441\u043F\u0430\u0441\u0451\u043D \u0438\u0437 \u0437\u0430\u043F\u0438\u0441\u0438 \u0441\u0435\u0441\u0441\u0438\u0438 \u2014 \u043F\u043E\u043C\u0435\u0447\u0435\u043D \xAB\u043D\u0435 \u0438\u0437\u043C\u0435\u0440\u0435\u043D\u043E\xBB (TASWM-548)`);
|
|
10959
11150
|
} else {
|
|
10960
11151
|
if (result.warning) console.warn(`[overmind] cost \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${result.warning}`);
|
|
10961
11152
|
added += result.costUsd;
|
|
@@ -10965,12 +11156,16 @@ function accountRunCost(deps, run) {
|
|
|
10965
11156
|
}
|
|
10966
11157
|
processed.push(full);
|
|
10967
11158
|
}
|
|
10968
|
-
if (added > 0 || tokensIn > 0 || tokensOut > 0 || durationMs > 0) {
|
|
11159
|
+
if (added > 0 || tokensIn > 0 || tokensOut > 0 || durationMs > 0 || unmeasured > 0) {
|
|
10969
11160
|
deps.db.updateRun(run.id, {
|
|
10970
11161
|
cost_usd: run.cost_usd + added,
|
|
10971
11162
|
tokens_in: run.tokens_in + tokensIn,
|
|
10972
11163
|
tokens_out: run.tokens_out + tokensOut,
|
|
10973
|
-
duration_ms: run.duration_ms + durationMs
|
|
11164
|
+
duration_ms: run.duration_ms + durationMs,
|
|
11165
|
+
// Отметка «не измерено» копится тем же единственным updateRun, что и деньги: разъехаться
|
|
11166
|
+
// «цену дописали, а признак неполноты потеряли» эти величины не должны — отчёт читает их
|
|
11167
|
+
// ПАРОЙ, и ноль при cost_unmeasured=0 значит «бесплатно», а при >0 — «не знаю».
|
|
11168
|
+
cost_unmeasured: run.cost_unmeasured + unmeasured
|
|
10974
11169
|
});
|
|
10975
11170
|
}
|
|
10976
11171
|
for (const full of processed) {
|
|
@@ -10998,8 +11193,8 @@ function makeCostSweeper(deps) {
|
|
|
10998
11193
|
}
|
|
10999
11194
|
|
|
11000
11195
|
// src/runOutcome.ts
|
|
11001
|
-
import { readdirSync as
|
|
11002
|
-
import { join as
|
|
11196
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync16 } from "fs";
|
|
11197
|
+
import { join as join19 } from "path";
|
|
11003
11198
|
init_executors();
|
|
11004
11199
|
function endedItself(outcome) {
|
|
11005
11200
|
return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
|
|
@@ -11013,7 +11208,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
|
11013
11208
|
if (!dir) return null;
|
|
11014
11209
|
let files;
|
|
11015
11210
|
try {
|
|
11016
|
-
files =
|
|
11211
|
+
files = readdirSync11(dir);
|
|
11017
11212
|
} catch {
|
|
11018
11213
|
return null;
|
|
11019
11214
|
}
|
|
@@ -11026,7 +11221,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
|
11026
11221
|
}
|
|
11027
11222
|
if (!latest) return null;
|
|
11028
11223
|
try {
|
|
11029
|
-
return
|
|
11224
|
+
return readFileSync16(join19(dir, latest.name), "utf8");
|
|
11030
11225
|
} catch {
|
|
11031
11226
|
return null;
|
|
11032
11227
|
}
|
|
@@ -11059,20 +11254,20 @@ init_src();
|
|
|
11059
11254
|
init_git();
|
|
11060
11255
|
|
|
11061
11256
|
// src/deps.ts
|
|
11062
|
-
import { existsSync as existsSync14, readdirSync as
|
|
11063
|
-
import { join as
|
|
11257
|
+
import { existsSync as existsSync14, readdirSync as readdirSync13, readFileSync as readFileSync18 } from "fs";
|
|
11258
|
+
import { join as join23 } from "path";
|
|
11064
11259
|
import { execa } from "execa";
|
|
11065
11260
|
|
|
11066
11261
|
// src/prepareLock.ts
|
|
11067
11262
|
import { createHash } from "crypto";
|
|
11068
|
-
import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as
|
|
11069
|
-
import { join as
|
|
11263
|
+
import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync11, writeSync } from "fs";
|
|
11264
|
+
import { join as join21 } from "path";
|
|
11070
11265
|
import { tmpdir } from "os";
|
|
11071
11266
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
11072
11267
|
var POLL_MS = 300;
|
|
11073
11268
|
var STALE_MS = 20 * 6e4;
|
|
11074
11269
|
function machineLockPath(key, dir = tmpdir()) {
|
|
11075
|
-
return
|
|
11270
|
+
return join21(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
11076
11271
|
}
|
|
11077
11272
|
function defaultTryCreate(path, content) {
|
|
11078
11273
|
try {
|
|
@@ -11089,7 +11284,7 @@ function defaultTryCreate(path, content) {
|
|
|
11089
11284
|
}
|
|
11090
11285
|
function defaultAgeMs(path) {
|
|
11091
11286
|
try {
|
|
11092
|
-
return Date.now() -
|
|
11287
|
+
return Date.now() - statSync11(path).mtimeMs;
|
|
11093
11288
|
} catch {
|
|
11094
11289
|
return null;
|
|
11095
11290
|
}
|
|
@@ -11133,8 +11328,8 @@ init_config();
|
|
|
11133
11328
|
init_config();
|
|
11134
11329
|
import { createHash as createHash2 } from "crypto";
|
|
11135
11330
|
import { cp, link, lstat, mkdir as mkdir2, readdir, readlink, rename, rm as rm2, stat, symlink, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
11136
|
-
import { existsSync as existsSync13, readFileSync as
|
|
11137
|
-
import { join as
|
|
11331
|
+
import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
|
|
11332
|
+
import { join as join22 } from "path";
|
|
11138
11333
|
function fingerprint(parts) {
|
|
11139
11334
|
const h = createHash2("sha256");
|
|
11140
11335
|
for (const p of parts) {
|
|
@@ -11148,8 +11343,8 @@ async function cloneTree(src, dst) {
|
|
|
11148
11343
|
const entries = await readdir(src, { withFileTypes: true });
|
|
11149
11344
|
await Promise.all(
|
|
11150
11345
|
entries.map(async (e) => {
|
|
11151
|
-
const from =
|
|
11152
|
-
const to =
|
|
11346
|
+
const from = join22(src, e.name);
|
|
11347
|
+
const to = join22(dst, e.name);
|
|
11153
11348
|
if (e.isDirectory()) return cloneTree(from, to);
|
|
11154
11349
|
if (e.isSymbolicLink()) {
|
|
11155
11350
|
return symlink(await readlink(from), to).catch(() => cp(from, to, { recursive: true, verbatimSymlinks: true }));
|
|
@@ -11162,11 +11357,11 @@ function cacheRoot(io) {
|
|
|
11162
11357
|
return io.root ?? ENV_CACHE_DIR;
|
|
11163
11358
|
}
|
|
11164
11359
|
function snapshotDir(io, key) {
|
|
11165
|
-
return
|
|
11360
|
+
return join22(cacheRoot(io), key);
|
|
11166
11361
|
}
|
|
11167
11362
|
function readManifest(dir) {
|
|
11168
11363
|
try {
|
|
11169
|
-
const parsed = JSON.parse(
|
|
11364
|
+
const parsed = JSON.parse(readFileSync17(join22(dir, "manifest.json"), "utf8"));
|
|
11170
11365
|
if (!parsed || typeof parsed !== "object") return null;
|
|
11171
11366
|
const m = parsed;
|
|
11172
11367
|
if (typeof m.key !== "string" || !Array.isArray(m.dirs)) return null;
|
|
@@ -11179,7 +11374,7 @@ function readManifest(dir) {
|
|
|
11179
11374
|
}
|
|
11180
11375
|
function hasEnvSnapshot(key, io = {}) {
|
|
11181
11376
|
const exists = io.exists ?? existsSync13;
|
|
11182
|
-
return exists(
|
|
11377
|
+
return exists(join22(snapshotDir(io, key), "manifest.json"));
|
|
11183
11378
|
}
|
|
11184
11379
|
async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
11185
11380
|
const exists = io.exists ?? existsSync13;
|
|
@@ -11191,13 +11386,13 @@ async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
|
11191
11386
|
try {
|
|
11192
11387
|
for (let i = 0; i < manifest.dirs.length; i++) {
|
|
11193
11388
|
const rel = manifest.dirs[i] ?? "";
|
|
11194
|
-
const target =
|
|
11389
|
+
const target = join22(worktree, rel);
|
|
11195
11390
|
if (exists(target)) continue;
|
|
11196
|
-
await clone(
|
|
11391
|
+
await clone(join22(dir, "trees", String(i)), target);
|
|
11197
11392
|
made.push(rel);
|
|
11198
11393
|
}
|
|
11199
11394
|
} catch {
|
|
11200
|
-
for (const rel of made) await rm2(
|
|
11395
|
+
for (const rel of made) await rm2(join22(worktree, rel), { recursive: true, force: true }).catch(() => {
|
|
11201
11396
|
});
|
|
11202
11397
|
return null;
|
|
11203
11398
|
}
|
|
@@ -11213,16 +11408,16 @@ async function saveEnvSnapshot(worktree, key, dirs, io = {}) {
|
|
|
11213
11408
|
const exists = io.exists ?? existsSync13;
|
|
11214
11409
|
const clone = io.clone ?? cloneTree;
|
|
11215
11410
|
if (hasEnvSnapshot(key, io)) return true;
|
|
11216
|
-
const present = dirs.filter((rel) => exists(
|
|
11411
|
+
const present = dirs.filter((rel) => exists(join22(worktree, rel)));
|
|
11217
11412
|
if (!present.length) return false;
|
|
11218
11413
|
const root = cacheRoot(io);
|
|
11219
|
-
const tmp =
|
|
11414
|
+
const tmp = join22(root, `.tmp-${key}-${process.pid}`);
|
|
11220
11415
|
try {
|
|
11221
11416
|
await rm2(tmp, { recursive: true, force: true });
|
|
11222
|
-
await mkdir2(
|
|
11223
|
-
for (let i = 0; i < present.length; i++) await clone(
|
|
11417
|
+
await mkdir2(join22(tmp, "trees"), { recursive: true });
|
|
11418
|
+
for (let i = 0; i < present.length; i++) await clone(join22(worktree, present[i] ?? ""), join22(tmp, "trees", String(i)));
|
|
11224
11419
|
const manifest = { key, dirs: present, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
11225
|
-
await writeFile2(
|
|
11420
|
+
await writeFile2(join22(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
11226
11421
|
await rename(tmp, snapshotDir(io, key));
|
|
11227
11422
|
return true;
|
|
11228
11423
|
} catch {
|
|
@@ -11242,13 +11437,13 @@ async function pruneEnvSnapshots(io = {}) {
|
|
|
11242
11437
|
}
|
|
11243
11438
|
const withTime = [];
|
|
11244
11439
|
for (const name of names) {
|
|
11245
|
-
const s = await stat(
|
|
11440
|
+
const s = await stat(join22(root, name)).catch(() => null);
|
|
11246
11441
|
if (s) withTime.push({ name, mtime: s.mtimeMs });
|
|
11247
11442
|
}
|
|
11248
11443
|
withTime.sort((a, b) => b.mtime - a.mtime);
|
|
11249
11444
|
let removed = 0;
|
|
11250
11445
|
for (const { name } of withTime.slice(Math.max(0, keep))) {
|
|
11251
|
-
await rm2(
|
|
11446
|
+
await rm2(join22(root, name), { recursive: true, force: true }).catch(() => {
|
|
11252
11447
|
});
|
|
11253
11448
|
removed++;
|
|
11254
11449
|
}
|
|
@@ -11264,9 +11459,9 @@ async function sweepEnvCacheTemp(io = {}) {
|
|
|
11264
11459
|
}
|
|
11265
11460
|
let removed = 0;
|
|
11266
11461
|
for (const name of names) {
|
|
11267
|
-
const s = await lstat(
|
|
11462
|
+
const s = await lstat(join22(root, name)).catch(() => null);
|
|
11268
11463
|
if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
|
|
11269
|
-
await rm2(
|
|
11464
|
+
await rm2(join22(root, name), { recursive: true, force: true }).catch(() => {
|
|
11270
11465
|
});
|
|
11271
11466
|
removed++;
|
|
11272
11467
|
}
|
|
@@ -11293,21 +11488,21 @@ async function runCommand(cmd, worktree) {
|
|
|
11293
11488
|
}
|
|
11294
11489
|
function safeReadText(p) {
|
|
11295
11490
|
try {
|
|
11296
|
-
return
|
|
11491
|
+
return readFileSync18(p, "utf8");
|
|
11297
11492
|
} catch {
|
|
11298
11493
|
return null;
|
|
11299
11494
|
}
|
|
11300
11495
|
}
|
|
11301
11496
|
function safeListDirs(p) {
|
|
11302
11497
|
try {
|
|
11303
|
-
return
|
|
11498
|
+
return readdirSync13(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
11304
11499
|
} catch {
|
|
11305
11500
|
return [];
|
|
11306
11501
|
}
|
|
11307
11502
|
}
|
|
11308
11503
|
function safeListFiles(p) {
|
|
11309
11504
|
try {
|
|
11310
|
-
return
|
|
11505
|
+
return readdirSync13(p, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
|
|
11311
11506
|
} catch {
|
|
11312
11507
|
return [];
|
|
11313
11508
|
}
|
|
@@ -11335,19 +11530,19 @@ function overridePlan(override) {
|
|
|
11335
11530
|
function detectPackageManager(worktree, io = {}) {
|
|
11336
11531
|
const exists = io.exists ?? existsSync14;
|
|
11337
11532
|
const readText = io.readText ?? safeReadText;
|
|
11338
|
-
const hasPkgJson = exists(
|
|
11533
|
+
const hasPkgJson = exists(join23(worktree, "package.json"));
|
|
11339
11534
|
if (!hasPkgJson) {
|
|
11340
|
-
if (exists(
|
|
11535
|
+
if (exists(join23(worktree, "deno.json")) || exists(join23(worktree, "deno.jsonc")) || exists(join23(worktree, "deno.lock"))) return "deno";
|
|
11341
11536
|
return null;
|
|
11342
11537
|
}
|
|
11343
|
-
const pkg = readJson(readText,
|
|
11538
|
+
const pkg = readJson(readText, join23(worktree, "package.json"));
|
|
11344
11539
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
11345
11540
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
11346
|
-
if (exists(
|
|
11347
|
-
if (exists(
|
|
11348
|
-
if (exists(
|
|
11349
|
-
if (exists(
|
|
11350
|
-
if (exists(
|
|
11541
|
+
if (exists(join23(worktree, "bun.lock")) || exists(join23(worktree, "bun.lockb"))) return "bun";
|
|
11542
|
+
if (exists(join23(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
11543
|
+
if (exists(join23(worktree, "yarn.lock"))) return "yarn";
|
|
11544
|
+
if (exists(join23(worktree, "package-lock.json"))) return "npm";
|
|
11545
|
+
if (exists(join23(worktree, "deno.lock"))) return "deno";
|
|
11351
11546
|
return "npm";
|
|
11352
11547
|
}
|
|
11353
11548
|
function jsInstallCommand(pm) {
|
|
@@ -11411,7 +11606,7 @@ function detectPlan(worktree, io) {
|
|
|
11411
11606
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
11412
11607
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
11413
11608
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
11414
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
11609
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join23(worktree, "node_modules"), source: "detect" }
|
|
11415
11610
|
];
|
|
11416
11611
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
11417
11612
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -11420,12 +11615,12 @@ function detectPlan(worktree, io) {
|
|
|
11420
11615
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
11421
11616
|
}
|
|
11422
11617
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
11423
|
-
const hit = marker.files.some((f) => exists(
|
|
11618
|
+
const hit = marker.files.some((f) => exists(join23(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
11424
11619
|
if (!hit) continue;
|
|
11425
11620
|
let ecosystem = marker.ecosystem;
|
|
11426
11621
|
let command = marker.command;
|
|
11427
11622
|
for (const refine of marker.refine ?? []) {
|
|
11428
|
-
if (!exists(
|
|
11623
|
+
if (!exists(join23(worktree, refine.file))) continue;
|
|
11429
11624
|
ecosystem = refine.ecosystem;
|
|
11430
11625
|
command = refine.command;
|
|
11431
11626
|
break;
|
|
@@ -11482,12 +11677,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
11482
11677
|
function ciWorkflowPlan(worktree, io) {
|
|
11483
11678
|
const readText = io.readText ?? safeReadText;
|
|
11484
11679
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
11485
|
-
const dir =
|
|
11680
|
+
const dir = join23(worktree, ".github", "workflows");
|
|
11486
11681
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
11487
11682
|
const seen = /* @__PURE__ */ new Set();
|
|
11488
11683
|
const commands = [];
|
|
11489
11684
|
for (const name of files.sort()) {
|
|
11490
|
-
const yaml = readText(
|
|
11685
|
+
const yaml = readText(join23(dir, name));
|
|
11491
11686
|
if (yaml === null) continue;
|
|
11492
11687
|
for (const raw of workflowRunLines(yaml)) {
|
|
11493
11688
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -11502,7 +11697,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
11502
11697
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
11503
11698
|
}
|
|
11504
11699
|
function workspaceGlobs(worktree, readText) {
|
|
11505
|
-
const yaml = readText(
|
|
11700
|
+
const yaml = readText(join23(worktree, "pnpm-workspace.yaml"));
|
|
11506
11701
|
if (yaml !== null) {
|
|
11507
11702
|
const globs = [];
|
|
11508
11703
|
let inPackages = false;
|
|
@@ -11522,7 +11717,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
11522
11717
|
}
|
|
11523
11718
|
if (globs.length) return globs;
|
|
11524
11719
|
}
|
|
11525
|
-
const pkg = readJson(readText,
|
|
11720
|
+
const pkg = readJson(readText, join23(worktree, "package.json"));
|
|
11526
11721
|
const ws = pkg?.workspaces;
|
|
11527
11722
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
11528
11723
|
return list.filter((g) => typeof g === "string");
|
|
@@ -11533,14 +11728,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
11533
11728
|
if (glob.startsWith("!")) continue;
|
|
11534
11729
|
const star = glob.indexOf("*");
|
|
11535
11730
|
if (star === -1) {
|
|
11536
|
-
const dir =
|
|
11537
|
-
if (io.exists(
|
|
11731
|
+
const dir = join23(worktree, glob);
|
|
11732
|
+
if (io.exists(join23(dir, "package.json"))) dirs.add(dir);
|
|
11538
11733
|
continue;
|
|
11539
11734
|
}
|
|
11540
|
-
const base3 =
|
|
11735
|
+
const base3 = join23(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
11541
11736
|
for (const name of io.listDirs(base3)) {
|
|
11542
|
-
const dir =
|
|
11543
|
-
if (io.exists(
|
|
11737
|
+
const dir = join23(base3, name);
|
|
11738
|
+
if (io.exists(join23(dir, "package.json"))) dirs.add(dir);
|
|
11544
11739
|
}
|
|
11545
11740
|
}
|
|
11546
11741
|
return [...dirs];
|
|
@@ -11568,11 +11763,11 @@ function workspaceBuildTargets(worktree, io = {}) {
|
|
|
11568
11763
|
if (!globs.length) return [];
|
|
11569
11764
|
const pkgs = [];
|
|
11570
11765
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
11571
|
-
const pkg = readJson(readText,
|
|
11766
|
+
const pkg = readJson(readText, join23(dir, "package.json"));
|
|
11572
11767
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
11573
11768
|
const scripts = pkg.scripts ?? {};
|
|
11574
11769
|
const entries = entryPaths(pkg);
|
|
11575
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
11770
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join23(dir, e)));
|
|
11576
11771
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
11577
11772
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
11578
11773
|
}
|
|
@@ -11600,7 +11795,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
11600
11795
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
11601
11796
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
11602
11797
|
for (const file of [".env", ".env.example"]) {
|
|
11603
|
-
const raw = readText(
|
|
11798
|
+
const raw = readText(join23(worktree, file));
|
|
11604
11799
|
if (raw === null) continue;
|
|
11605
11800
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
11606
11801
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -11620,7 +11815,7 @@ function prismaPackages(worktree, io) {
|
|
|
11620
11815
|
const readText = io.readText ?? safeReadText;
|
|
11621
11816
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
11622
11817
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
11623
|
-
return dirs.filter((dir) => exists(
|
|
11818
|
+
return dirs.filter((dir) => exists(join23(dir, "prisma", "schema.prisma")) && listDirs(join23(dir, "prisma", "migrations")).length > 0);
|
|
11624
11819
|
}
|
|
11625
11820
|
function jsExecBin(pm, command) {
|
|
11626
11821
|
switch (pm) {
|
|
@@ -11657,18 +11852,18 @@ function envCachePlan(worktree, io = {}) {
|
|
|
11657
11852
|
if (!pm) return null;
|
|
11658
11853
|
const parts = [`pm:${pm}`];
|
|
11659
11854
|
for (const name of ENV_DEFINING_FILES) {
|
|
11660
|
-
const raw = readText(
|
|
11855
|
+
const raw = readText(join23(worktree, name));
|
|
11661
11856
|
if (raw !== null) parts.push(`${name}
|
|
11662
11857
|
${raw}`);
|
|
11663
11858
|
}
|
|
11664
11859
|
const pkgDirs = workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs });
|
|
11665
11860
|
const rels = pkgDirs.map((dir) => dir.slice(worktree.length + 1)).sort();
|
|
11666
11861
|
for (const rel of ["", ...rels]) {
|
|
11667
|
-
const raw = readText(
|
|
11862
|
+
const raw = readText(join23(worktree, rel, "package.json"));
|
|
11668
11863
|
if (raw !== null) parts.push(`${rel}/package.json
|
|
11669
11864
|
${raw}`);
|
|
11670
11865
|
}
|
|
11671
|
-
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) =>
|
|
11866
|
+
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join23(rel, "node_modules"))] };
|
|
11672
11867
|
}
|
|
11673
11868
|
function planPrepare(worktree, io = {}) {
|
|
11674
11869
|
const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
|
|
@@ -11693,7 +11888,7 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
11693
11888
|
restored: [],
|
|
11694
11889
|
cached: false
|
|
11695
11890
|
};
|
|
11696
|
-
if (cachePlan && !exists(
|
|
11891
|
+
if (cachePlan && !exists(join23(worktree, "node_modules"))) {
|
|
11697
11892
|
report.restored = await restoreEnvSnapshot(worktree, cachePlan.key, cacheIO) ?? [];
|
|
11698
11893
|
}
|
|
11699
11894
|
const plan = planPrepare(worktree, io);
|
|
@@ -11880,6 +12075,48 @@ function pruneDefers(episodes, live) {
|
|
|
11880
12075
|
for (const key of episodes.keys()) if (!live.has(key)) episodes.delete(key);
|
|
11881
12076
|
}
|
|
11882
12077
|
|
|
12078
|
+
// src/spawnRetry.ts
|
|
12079
|
+
var numEnv4 = (name, def) => {
|
|
12080
|
+
const v = Number(process.env[name]);
|
|
12081
|
+
return Number.isFinite(v) && v > 0 ? v : def;
|
|
12082
|
+
};
|
|
12083
|
+
var DEFAULT_SPAWN_RETRY_CONFIG = {
|
|
12084
|
+
baseMs: numEnv4("SWARM_SPAWN_RETRY_BASE_MS", 3e4),
|
|
12085
|
+
maxMs: numEnv4("SWARM_SPAWN_RETRY_MAX_MS", 12e4)
|
|
12086
|
+
};
|
|
12087
|
+
function spawnRetryBackoffMs(attempts, cfg = DEFAULT_SPAWN_RETRY_CONFIG) {
|
|
12088
|
+
const n = Math.max(0, Math.trunc(attempts));
|
|
12089
|
+
const grown = n >= 30 ? cfg.maxMs : cfg.baseMs * 2 ** n;
|
|
12090
|
+
return Math.min(cfg.maxMs, grown);
|
|
12091
|
+
}
|
|
12092
|
+
function noteSpawnWaiting(state, runId, now) {
|
|
12093
|
+
const prev = state.get(runId);
|
|
12094
|
+
if (prev) return prev;
|
|
12095
|
+
const entry = { since: now, attempts: 0, lastAt: now };
|
|
12096
|
+
state.set(runId, entry);
|
|
12097
|
+
return entry;
|
|
12098
|
+
}
|
|
12099
|
+
function spawnRetryDue(state, runId, now, cfg = DEFAULT_SPAWN_RETRY_CONFIG) {
|
|
12100
|
+
const entry = state.get(runId);
|
|
12101
|
+
if (!entry) return false;
|
|
12102
|
+
return now - entry.lastAt >= spawnRetryBackoffMs(entry.attempts, cfg);
|
|
12103
|
+
}
|
|
12104
|
+
function markSpawnRetry(state, runId, now) {
|
|
12105
|
+
const entry = noteSpawnWaiting(state, runId, now);
|
|
12106
|
+
entry.attempts += 1;
|
|
12107
|
+
entry.lastAt = now;
|
|
12108
|
+
return entry.attempts;
|
|
12109
|
+
}
|
|
12110
|
+
function clearSpawnRetry(state, runId) {
|
|
12111
|
+
state.delete(runId);
|
|
12112
|
+
}
|
|
12113
|
+
function holdSpawnRetries(state, now) {
|
|
12114
|
+
for (const entry of state.values()) entry.lastAt = now;
|
|
12115
|
+
}
|
|
12116
|
+
function pruneSpawnRetries(state, live) {
|
|
12117
|
+
for (const id of [...state.keys()]) if (!live.has(id)) state.delete(id);
|
|
12118
|
+
}
|
|
12119
|
+
|
|
11883
12120
|
// src/spawnAttempts.ts
|
|
11884
12121
|
init_src();
|
|
11885
12122
|
var PRE_START_ATTEMPT_LIMIT = 3;
|
|
@@ -11982,7 +12219,7 @@ function parkReasonUnretriable(reason) {
|
|
|
11982
12219
|
// package.json
|
|
11983
12220
|
var package_default = {
|
|
11984
12221
|
name: "@taskless-app/swarm",
|
|
11985
|
-
version: "0.6.
|
|
12222
|
+
version: "0.6.39",
|
|
11986
12223
|
type: "module",
|
|
11987
12224
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11988
12225
|
keywords: [
|
|
@@ -12033,7 +12270,7 @@ var package_default = {
|
|
|
12033
12270
|
execa: "^10.0.0",
|
|
12034
12271
|
zod: "3.25.76"
|
|
12035
12272
|
},
|
|
12036
|
-
gitHead: "
|
|
12273
|
+
gitHead: "0c0b1bc0ee05add0d2b5b5d503a05ed7b7834dd8"
|
|
12037
12274
|
};
|
|
12038
12275
|
|
|
12039
12276
|
// src/version.ts
|
|
@@ -12637,6 +12874,58 @@ ${out.slice(-CI_SUMMARY_LOG_CHARS)}` : out);
|
|
|
12637
12874
|
|
|
12638
12875
|
${logs.join("\n---\n")}` : header;
|
|
12639
12876
|
}
|
|
12877
|
+
var CI_INFRA_MARKERS = [
|
|
12878
|
+
/ENOTEMPTY/,
|
|
12879
|
+
/ENOENT: process.cwd/,
|
|
12880
|
+
/working directory was likely removed/,
|
|
12881
|
+
// Любой ENOENT, назвавший путь установки pnpm. Четвёртая грань той же гонки (02.09, 19:14,
|
|
12882
|
+
// PR #1074): «ENOENT: no such file or directory, chmod '…/setup-pnpm/…/pnpm/bin/pnpx.cjs'» —
|
|
12883
|
+
// установщик распаковал pnpm и пошёл выставлять права, а файла уже нет. Окно гонки тут не
|
|
12884
|
+
// «две джобы стартуют вместе», а миллисекунды посреди работы одного установщика.
|
|
12885
|
+
/ENOENT[^\n]*setup-pnpm/,
|
|
12886
|
+
/Cannot find module '[^']*setup-pnpm/,
|
|
12887
|
+
/Cannot find module '[^']*node_modules\/pnpm\/bin\/pnpm\.cjs'/,
|
|
12888
|
+
/self-installer exits with code/,
|
|
12889
|
+
// Сетевые отказы раннера. Тот же класс: чинить нечего, помогает перезапуск. Держатся
|
|
12890
|
+
// здесь потому, что ИМЕННО ЭТИ строки уже перечислены в `INFRA_MARKERS`
|
|
12891
|
+
// (.github/scripts/typecheck-verdict.sh) — там они не гасят прогон. Пока список был
|
|
12892
|
+
// только там, `ci.yml` вёл себя верно, а демон всё равно будил дрона и жёг попытку
|
|
12893
|
+
// CI_WAKE_LIMIT: разошедшиеся списки давали ровно ту потерю, ради которой заведена
|
|
12894
|
+
// CI-112. Расхождение теперь ловится тестом «списки маркеров сходятся с bash», а не
|
|
12895
|
+
// обещанием держать их одинаковыми.
|
|
12896
|
+
/EACCES/,
|
|
12897
|
+
/ETIMEDOUT/,
|
|
12898
|
+
/ECONNRESET/
|
|
12899
|
+
// Вердикта самой джобы (`::error title=Джоба typecheck не смогла запуститься…`,
|
|
12900
|
+
// .github/scripts/typecheck-verdict.sh) тут намеренно НЕТ, хотя соблазн есть: шаг вердикта
|
|
12901
|
+
// завершается успешно, а `gh run view --log-failed` отдаёт логи только УПАВШИХ шагов — до
|
|
12902
|
+
// роя эта строка не доезжает. Сырые следы из лога упавшего шага доезжают, по ним и судим.
|
|
12903
|
+
// Списки маркеров у скрипта и здесь держим одинаковыми руками: они судят один лог, но
|
|
12904
|
+
// общего кода у bash в `.github/` и пакета, уезжающего в npm, быть не может.
|
|
12905
|
+
];
|
|
12906
|
+
var CI_CODE_FAILURE_MARKERS = [
|
|
12907
|
+
/error TS\d/,
|
|
12908
|
+
/AssertionError/,
|
|
12909
|
+
/Test Files\s+\d+ failed/,
|
|
12910
|
+
/Tests\s+\d+ failed/,
|
|
12911
|
+
/ERR_PNPM_OUTDATED_LOCKFILE/,
|
|
12912
|
+
// Vitest-символы. `×` (U+00D7) сюда НЕ берём: он встречается в обычном тексте логов
|
|
12913
|
+
// (размеры вида 1920×1080) и делал бы «есть дефект кода» из любого скриншот-прогона.
|
|
12914
|
+
/✕|✗/,
|
|
12915
|
+
// Playwright: у него свои символы и своя сводка — ни `Test Files`, ни `✕` он не печатает.
|
|
12916
|
+
// Без этих двух строк упавший e2e рядом с инфра-падением соседней джобы уезжал в
|
|
12917
|
+
// «это окружение», и дрона не будили вовсе (логи ВСЕХ упавших джоб прогона приезжают
|
|
12918
|
+
// одним куском: `gh run view <run-id> --log-failed`, см. ciFailureSummary).
|
|
12919
|
+
/✘/,
|
|
12920
|
+
// НЕ якорим на начало строки: `gh run view --log-failed` печатает каждую строку с
|
|
12921
|
+
// префиксом «джоба<TAB>шаг<TAB>время», и якорь `^\s*` не совпал бы никогда.
|
|
12922
|
+
/\b\d+ failed\b/
|
|
12923
|
+
];
|
|
12924
|
+
function isInfraCiFailure(summary) {
|
|
12925
|
+
if (!summary) return false;
|
|
12926
|
+
if (CI_CODE_FAILURE_MARKERS.some((re) => re.test(summary))) return false;
|
|
12927
|
+
return CI_INFRA_MARKERS.some((re) => re.test(summary));
|
|
12928
|
+
}
|
|
12640
12929
|
|
|
12641
12930
|
// src/droneServer.ts
|
|
12642
12931
|
init_droneChannel();
|
|
@@ -12649,8 +12938,8 @@ import { z } from "zod";
|
|
|
12649
12938
|
init_git();
|
|
12650
12939
|
|
|
12651
12940
|
// src/diff.ts
|
|
12652
|
-
import { statSync as
|
|
12653
|
-
import { join as
|
|
12941
|
+
import { statSync as statSync12 } from "fs";
|
|
12942
|
+
import { join as join28 } from "path";
|
|
12654
12943
|
import { $ as $4 } from "execa";
|
|
12655
12944
|
var MAX_FILES = 200;
|
|
12656
12945
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -12793,7 +13082,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
12793
13082
|
}
|
|
12794
13083
|
function mtime(path) {
|
|
12795
13084
|
try {
|
|
12796
|
-
return
|
|
13085
|
+
return statSync12(path).mtimeMs;
|
|
12797
13086
|
} catch {
|
|
12798
13087
|
return 0;
|
|
12799
13088
|
}
|
|
@@ -12818,7 +13107,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
12818
13107
|
files.push(pf);
|
|
12819
13108
|
}
|
|
12820
13109
|
}
|
|
12821
|
-
files.sort((a, b) => mtime(
|
|
13110
|
+
files.sort((a, b) => mtime(join28(worktree, b.path)) - mtime(join28(worktree, a.path)));
|
|
12822
13111
|
const truncatedFiles = files.length > MAX_FILES;
|
|
12823
13112
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
12824
13113
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -14898,20 +15187,20 @@ function makeMergeGateParts(deps) {
|
|
|
14898
15187
|
|
|
14899
15188
|
// src/envGate.ts
|
|
14900
15189
|
import { existsSync as existsSync17 } from "fs";
|
|
14901
|
-
import { join as
|
|
15190
|
+
import { join as join30 } from "path";
|
|
14902
15191
|
import { createConnection } from "net";
|
|
14903
15192
|
import { execa as execa2 } from "execa";
|
|
14904
15193
|
|
|
14905
15194
|
// src/platform.ts
|
|
14906
|
-
import { existsSync as existsSync16, readFileSync as
|
|
14907
|
-
import { delimiter, join as
|
|
15195
|
+
import { existsSync as existsSync16, readFileSync as readFileSync22 } from "fs";
|
|
15196
|
+
import { delimiter, join as join29 } from "path";
|
|
14908
15197
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
14909
15198
|
if (name.includes("/") || name.includes("\\")) return existsSync16(name) ? name : null;
|
|
14910
15199
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
14911
15200
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
14912
15201
|
for (const dir of dirs) {
|
|
14913
15202
|
for (const ext of exts) {
|
|
14914
|
-
const candidate =
|
|
15203
|
+
const candidate = join29(dir, name + ext);
|
|
14915
15204
|
if (existsSync16(candidate)) return candidate;
|
|
14916
15205
|
}
|
|
14917
15206
|
}
|
|
@@ -14996,7 +15285,7 @@ var COMPOSE_MARKERS = [
|
|
|
14996
15285
|
];
|
|
14997
15286
|
function detectRequirements(repo, io = {}) {
|
|
14998
15287
|
const exists = io.exists ?? existsSync17;
|
|
14999
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
15288
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join30(repo, f)));
|
|
15000
15289
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
15001
15290
|
}
|
|
15002
15291
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -16739,14 +17028,14 @@ function closeWebServer(server) {
|
|
|
16739
17028
|
|
|
16740
17029
|
// src/hostCommands.ts
|
|
16741
17030
|
init_src();
|
|
16742
|
-
import { readFileSync as
|
|
16743
|
-
import { join as
|
|
17031
|
+
import { readFileSync as readFileSync23, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
|
|
17032
|
+
import { join as join31 } from "path";
|
|
16744
17033
|
function hostCommandStatePath(home) {
|
|
16745
|
-
return
|
|
17034
|
+
return join31(home, "host-command.json");
|
|
16746
17035
|
}
|
|
16747
17036
|
function readState(path) {
|
|
16748
17037
|
try {
|
|
16749
|
-
return JSON.parse(
|
|
17038
|
+
return JSON.parse(readFileSync23(path, "utf8"));
|
|
16750
17039
|
} catch {
|
|
16751
17040
|
return {};
|
|
16752
17041
|
}
|
|
@@ -16917,7 +17206,7 @@ async function doRestart(cmd, opts, note) {
|
|
|
16917
17206
|
// src/selfUpdate.ts
|
|
16918
17207
|
init_git();
|
|
16919
17208
|
import { existsSync as existsSync18 } from "fs";
|
|
16920
|
-
import { join as
|
|
17209
|
+
import { join as join32 } from "path";
|
|
16921
17210
|
import { $ as $5 } from "execa";
|
|
16922
17211
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
16923
17212
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -16959,7 +17248,7 @@ async function runSelfUpdate(io) {
|
|
|
16959
17248
|
};
|
|
16960
17249
|
}
|
|
16961
17250
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
16962
|
-
if (!exists(
|
|
17251
|
+
if (!exists(join32(repo, "pnpm-lock.yaml"))) {
|
|
16963
17252
|
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" };
|
|
16964
17253
|
}
|
|
16965
17254
|
const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -17118,19 +17407,19 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
17118
17407
|
}
|
|
17119
17408
|
|
|
17120
17409
|
// src/anomalyState.ts
|
|
17121
|
-
import { existsSync as existsSync19, readFileSync as
|
|
17122
|
-
import { join as
|
|
17410
|
+
import { existsSync as existsSync19, readFileSync as readFileSync24, renameSync as renameSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
17411
|
+
import { join as join33 } from "path";
|
|
17123
17412
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
17124
17413
|
var MAX_RESOLVED = 200;
|
|
17125
17414
|
function emptyState() {
|
|
17126
17415
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
17127
17416
|
}
|
|
17128
17417
|
function anomalyStatePath(home) {
|
|
17129
|
-
return
|
|
17418
|
+
return join33(home, "anomaly-state.json");
|
|
17130
17419
|
}
|
|
17131
17420
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
17132
17421
|
const exists = io.exists ?? existsSync19;
|
|
17133
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
17422
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync24(p, "utf8"));
|
|
17134
17423
|
const path = anomalyStatePath(home);
|
|
17135
17424
|
if (!exists(path)) return emptyState();
|
|
17136
17425
|
try {
|
|
@@ -17642,6 +17931,7 @@ var CI_POLL_LIMIT_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u
|
|
|
17642
17931
|
var CI_DENIED_LIMIT = Number(process.env.SWARM_CI_DENIED_LIMIT) || 3;
|
|
17643
17932
|
var CI_DENIED_MARK = "\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C (403)";
|
|
17644
17933
|
var CI_DENIED_STOP_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: \u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 (403)";
|
|
17934
|
+
var CI_INFRA_MARK = "\u0443\u043F\u0430\u043B \u043D\u0435 \u043A\u043E\u0434, \u0430 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0434\u0436\u043E\u0431\u044B";
|
|
17645
17935
|
var CI_WAIT_LIMIT_MS = Number(process.env.SWARM_CI_WAIT_LIMIT_MS) || 45 * 60 * 1e3;
|
|
17646
17936
|
var CI_POLL_LIMIT = Math.max(
|
|
17647
17937
|
Number(process.env.SWARM_CI_POLL_LIMIT) || 90,
|
|
@@ -18326,10 +18616,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
18326
18616
|
}
|
|
18327
18617
|
return parts.join("\n");
|
|
18328
18618
|
}
|
|
18329
|
-
var HERO_CHARTER_PATH =
|
|
18619
|
+
var HERO_CHARTER_PATH = join34(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
18330
18620
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
18331
18621
|
const name = run.name || `#${run.id}`;
|
|
18332
|
-
const charter =
|
|
18622
|
+
const charter = readFileSync25(HERO_CHARTER_PATH, "utf8");
|
|
18333
18623
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
18334
18624
|
|
|
18335
18625
|
${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).`;
|
|
@@ -18757,9 +19047,9 @@ function repoHasCi(deps, run) {
|
|
|
18757
19047
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
18758
19048
|
for (const base3 of [worktreePath, repo]) {
|
|
18759
19049
|
if (!base3) continue;
|
|
18760
|
-
const dir =
|
|
19050
|
+
const dir = join34(base3, ".github", "workflows");
|
|
18761
19051
|
try {
|
|
18762
|
-
if (
|
|
19052
|
+
if (readdirSync14(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
18763
19053
|
} catch {
|
|
18764
19054
|
}
|
|
18765
19055
|
}
|
|
@@ -18850,6 +19140,21 @@ async function wakeAuthorOnRedCi(deps, run, now, ci, worktreeExists, hasTranscri
|
|
|
18850
19140
|
const wt = run.worktree_path;
|
|
18851
19141
|
if (!wt || !worktreeExists(wt) || !hasTranscript(wt)) return false;
|
|
18852
19142
|
const summary = await deps.ci.failureSummary(ci.failing, ci.failingRunIds).catch(() => null) ?? `\u274C CI \u0443\u043F\u0430\u043B: ${ci.failing.join(", ") || "(\u0441\u043C. gh pr checks)"}`;
|
|
19143
|
+
if (isInfraCiFailure(summary)) {
|
|
19144
|
+
const seen = ciStreak(deps.db, run.id, CI_INFRA_MARK).count;
|
|
19145
|
+
deps.db.addEvent(
|
|
19146
|
+
run.id,
|
|
19147
|
+
"ci_checked",
|
|
19148
|
+
`CI fail (${ci.failing.join(", ") || "?"}) \u2014 ${CI_INFRA_MARK}; \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u0431\u0443\u0436\u0443, \u0447\u0438\u043D\u0438\u0442\u0441\u044F \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C \u0434\u0436\u043E\u0431 [${CI_WAIT_POLL_MARK}]`
|
|
19149
|
+
);
|
|
19150
|
+
if (seen === 0) {
|
|
19151
|
+
narrate(
|
|
19152
|
+
deps,
|
|
19153
|
+
`CI \u0443 ${droneLabel(run)} \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u043D\u0435 \u043F\u043E \u043A\u043E\u0434\u0443 \u2014 \u0434\u0436\u043E\u0431\u0430 \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u0430 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\u0441\u044F (\u0440\u0430\u043D\u043D\u0435\u0440\u044B). \u0414\u0440\u043E\u043D\u0430 \u043D\u0435 \u0431\u0443\u0436\u0443: \u0447\u0438\u043D\u0438\u0442\u044C \u0435\u043C\u0443 \u0442\u0430\u043C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u0435\u043D \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0443\u043F\u0430\u0432\u0448\u0438\u0445 \u0434\u0436\u043E\u0431 (gh run rerun --failed).`
|
|
19154
|
+
);
|
|
19155
|
+
}
|
|
19156
|
+
return true;
|
|
19157
|
+
}
|
|
18853
19158
|
try {
|
|
18854
19159
|
await deps.respawn(run, "continue", buildCiWakeNudge(summary));
|
|
18855
19160
|
deps.db.addEvent(run.id, "ci_wake", `CI \u0443\u043F\u0430\u043B (${ci.failing.join(", ") || "?"}) \u2014 \u0431\u0443\u0436\u0443 \u0434\u0440\u043E\u043D\u0430 \u0441\u043E \u0441\u0436\u0430\u0442\u044B\u043C summary`);
|
|
@@ -19638,9 +19943,22 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19638
19943
|
const needsRelief = deps.reliefNeeded ? deps.reliefNeeded() : false;
|
|
19639
19944
|
const resourcesOk2 = deps.resourcesOk();
|
|
19640
19945
|
state.reliefSince = needsRelief ? state.reliefSince ?? now : null;
|
|
19946
|
+
if (state.reliefSince == null) state.soleWorkerNoted = false;
|
|
19641
19947
|
if (!resourcesOk2 || needsRelief) state.overloadedAt = now;
|
|
19642
19948
|
if (needsRelief && deps.kill && pauseDue(state.reliefSince, state.lastPauseAt ?? null, now, cfg)) {
|
|
19643
|
-
const
|
|
19949
|
+
const running = deps.db.listRuns("running");
|
|
19950
|
+
if (reliefWouldStrangleSoleWorker(running)) {
|
|
19951
|
+
const alone = loadBearingRuns(running)[0];
|
|
19952
|
+
if (alone && !state.soleWorkerNoted) {
|
|
19953
|
+
state.soleWorkerNoted = true;
|
|
19954
|
+
const why = reliefSoleWorkerReason(deps.reliefDecisionText?.() ?? null);
|
|
19955
|
+
deps.db.addEvent(alone.id, "note", why);
|
|
19956
|
+
narrate(deps, `${droneLabel(alone)} \u043E\u0434\u0438\u043D \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u2014 \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0435\u0433\u043E \u043D\u0435 \u0441\u043D\u0438\u043C\u0430\u0435\u0442. ${why}`);
|
|
19957
|
+
console.warn(`[overmind] \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430: ${why}`);
|
|
19958
|
+
}
|
|
19959
|
+
return;
|
|
19960
|
+
}
|
|
19961
|
+
const candidate = pickPauseCandidate(running, new Set(heavyRunsInFlight(deps).map((r) => r.id)));
|
|
19644
19962
|
if (candidate) {
|
|
19645
19963
|
const decisionText = deps.reliefDecisionText?.() ?? null;
|
|
19646
19964
|
let stepsSinceResume = null;
|
|
@@ -19777,6 +20095,43 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
19777
20095
|
return;
|
|
19778
20096
|
}
|
|
19779
20097
|
}
|
|
20098
|
+
function hostNotAcceptingReason(deps, droneCap, stormActive) {
|
|
20099
|
+
if (stormActive) return "\u0440\u043E\u0439 \u0432 \u0448\u0442\u043E\u0440\u043C\u043E\u0432\u043E\u043C \u0440\u0435\u0436\u0438\u043C\u0435";
|
|
20100
|
+
if (deps.control?.paused) return "\u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435";
|
|
20101
|
+
if (deps.resourcesOk && !deps.resourcesOk()) return deps.gateReason?.() ?? "\u0440\u0435\u0441\u0443\u0440\u0441-\u0433\u0435\u0439\u0442 \u0437\u0430\u043A\u0440\u044B\u0442";
|
|
20102
|
+
const active = countActiveDrones(deps.db);
|
|
20103
|
+
if (active >= droneCap) return `\u0441\u043B\u043E\u0442\u043E\u0432 \u043D\u0435\u0442 (\u0434\u0440\u043E\u043D\u043E\u0432 ${active} \u0438\u0437 ${droneCap})`;
|
|
20104
|
+
return null;
|
|
20105
|
+
}
|
|
20106
|
+
async function retryDeferredSpawns(deps, now, droneCap, stormActive) {
|
|
20107
|
+
const state = deps.spawnRetry ??= /* @__PURE__ */ new Map();
|
|
20108
|
+
const cfg = deps.spawnRetryConfig ? { ...DEFAULT_SPAWN_RETRY_CONFIG, ...deps.spawnRetryConfig } : DEFAULT_SPAWN_RETRY_CONFIG;
|
|
20109
|
+
const waiting = orderQueue(deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0));
|
|
20110
|
+
pruneSpawnRetries(state, new Set(waiting.map((r) => r.id)));
|
|
20111
|
+
if (!waiting.length) return;
|
|
20112
|
+
for (const run of waiting) noteSpawnWaiting(state, run.id, now);
|
|
20113
|
+
const closed = hostNotAcceptingReason(deps, droneCap, stormActive);
|
|
20114
|
+
if (closed) {
|
|
20115
|
+
holdSpawnRetries(state, now);
|
|
20116
|
+
return;
|
|
20117
|
+
}
|
|
20118
|
+
const relief = deps.relief ?? (deps.relief = {});
|
|
20119
|
+
const reliefCfg = relief.config ? resolveReliefConfig(relief.config) : DEFAULT_RELIEF_CONFIG;
|
|
20120
|
+
if (!rampDue(relief.lastRampAt ?? null, now, reliefCfg)) return;
|
|
20121
|
+
for (const run of waiting) {
|
|
20122
|
+
if (!spawnRetryDue(state, run.id, now, cfg)) continue;
|
|
20123
|
+
const entry = state.get(run.id);
|
|
20124
|
+
const attempt = markSpawnRetry(state, run.id, now);
|
|
20125
|
+
console.error(
|
|
20126
|
+
`[overmind] \u043F\u043E\u0432\u0442\u043E\u0440 \u043E\u0442\u043B\u043E\u0436\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043F\u0430\u0432\u043D\u0430 \u2116${attempt}: ${droneLabel(run)} (${run.task_slug}) \u0436\u0434\u0451\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 ${humanMinutes(now - entry.since)}, \u0445\u043E\u0441\u0442 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u2014 \u043F\u0440\u043E\u0431\u0443\u044E \u0441\u043D\u043E\u0432\u0430 (\u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u043E\u0442\u0441\u0442\u0443\u043F ${Math.round(spawnRetryBackoffMs(attempt, cfg) / 1e3)}\u0441, TASWM-549)`
|
|
20127
|
+
);
|
|
20128
|
+
const outcome = await trySpawnQueuedRun(deps, run);
|
|
20129
|
+
if (outcome !== "skipped") clearSpawnRetry(state, run.id);
|
|
20130
|
+
if (outcome === "closed") continue;
|
|
20131
|
+
if (outcome !== "skipped") relief.lastRampAt = now;
|
|
20132
|
+
return;
|
|
20133
|
+
}
|
|
20134
|
+
}
|
|
19780
20135
|
async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
19781
20136
|
const state = deps.relief?.cost;
|
|
19782
20137
|
if (!state) return [];
|
|
@@ -20162,6 +20517,11 @@ async function tickOnce(deps) {
|
|
|
20162
20517
|
if (!stormActive) {
|
|
20163
20518
|
await resumeWatchdogParked(deps, droneCap);
|
|
20164
20519
|
}
|
|
20520
|
+
try {
|
|
20521
|
+
await retryDeferredSpawns(deps, now, droneCap, stormActive);
|
|
20522
|
+
} catch (e) {
|
|
20523
|
+
console.error("[overmind] \u043F\u043E\u0432\u0442\u043E\u0440 \u043E\u0442\u043B\u043E\u0436\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043F\u0430\u0432\u043D\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
|
|
20524
|
+
}
|
|
20165
20525
|
for (let run of deps.db.listRuns("review")) {
|
|
20166
20526
|
try {
|
|
20167
20527
|
if (run.resolves_run_id != null) {
|
|
@@ -20830,7 +21190,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
20830
21190
|
model: reviewRun.model,
|
|
20831
21191
|
review_model: reviewRun.review_model
|
|
20832
21192
|
});
|
|
20833
|
-
const wt =
|
|
21193
|
+
const wt = join34(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
20834
21194
|
try {
|
|
20835
21195
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20836
21196
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -20909,7 +21269,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
20909
21269
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
20910
21270
|
workspace_id: reviewRun.workspace_id
|
|
20911
21271
|
});
|
|
20912
|
-
const wt =
|
|
21272
|
+
const wt = join34(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
20913
21273
|
try {
|
|
20914
21274
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20915
21275
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -20990,7 +21350,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
20990
21350
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
20991
21351
|
workspace_id: verifiedRun.workspace_id
|
|
20992
21352
|
});
|
|
20993
|
-
const wt =
|
|
21353
|
+
const wt = join34(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
20994
21354
|
try {
|
|
20995
21355
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20996
21356
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
@@ -21142,7 +21502,7 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
21142
21502
|
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`);
|
|
21143
21503
|
}
|
|
21144
21504
|
}
|
|
21145
|
-
const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW,
|
|
21505
|
+
const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir7(), repoPaths);
|
|
21146
21506
|
const repoMap = emptyRepoMapState(scanRootsList);
|
|
21147
21507
|
if (SWARM_BACKEND !== "client") {
|
|
21148
21508
|
const { SwarmDb: SwarmDb2 } = await loadSqlite();
|
|
@@ -21236,7 +21596,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
21236
21596
|
exists: (d) => existsSync20(d),
|
|
21237
21597
|
isNonEmptyDir: (d) => {
|
|
21238
21598
|
try {
|
|
21239
|
-
return
|
|
21599
|
+
return readdirSync14(d).length > 0;
|
|
21240
21600
|
} catch {
|
|
21241
21601
|
return false;
|
|
21242
21602
|
}
|
|
@@ -22489,6 +22849,7 @@ export {
|
|
|
22489
22849
|
CI_DENIED_LIMIT,
|
|
22490
22850
|
CI_DENIED_MARK,
|
|
22491
22851
|
CI_DENIED_STOP_MARK,
|
|
22852
|
+
CI_INFRA_MARK,
|
|
22492
22853
|
CI_NO_CHECKS_MARK,
|
|
22493
22854
|
CI_POLL_LIMIT,
|
|
22494
22855
|
CI_POLL_LIMIT_MARK,
|