@taskless-app/swarm 0.6.31 → 0.6.33
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 +82 -17
- package/dist/overmind.js +1082 -505
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -1447,7 +1447,7 @@ function buildZoneModel(entries, opts = {}) {
|
|
|
1447
1447
|
if (!m) labelVotes.set(z2, m = /* @__PURE__ */ new Map());
|
|
1448
1448
|
bump(m, manual);
|
|
1449
1449
|
}
|
|
1450
|
-
const
|
|
1450
|
+
const rename2 = /* @__PURE__ */ new Map();
|
|
1451
1451
|
const takenNames = /* @__PURE__ */ new Set([SPINE_ZONE]);
|
|
1452
1452
|
const clustersByEvidence = [...labelVotes.entries()].sort((a, b) => {
|
|
1453
1453
|
const sum = (m) => [...m.values()].reduce((x, y) => x + y, 0);
|
|
@@ -1456,11 +1456,11 @@ function buildZoneModel(entries, opts = {}) {
|
|
|
1456
1456
|
for (const [zone, votes] of clustersByEvidence) {
|
|
1457
1457
|
const top = topKey(votes);
|
|
1458
1458
|
if (!top || top.share < 0.6 || takenNames.has(top.key) || top.key === zone) continue;
|
|
1459
|
-
|
|
1459
|
+
rename2.set(zone, top.key);
|
|
1460
1460
|
takenNames.add(top.key);
|
|
1461
1461
|
}
|
|
1462
|
-
const renamed = (z2) =>
|
|
1463
|
-
if (
|
|
1462
|
+
const renamed = (z2) => rename2.get(z2) ?? z2;
|
|
1463
|
+
if (rename2.size) {
|
|
1464
1464
|
for (const [f, z2] of fileZone) fileZone.set(f, renamed(z2));
|
|
1465
1465
|
for (const [s, z2] of taskZone) taskZone.set(s, renamed(z2));
|
|
1466
1466
|
}
|
|
@@ -1566,8 +1566,8 @@ function predictFiles(model, opts) {
|
|
|
1566
1566
|
}
|
|
1567
1567
|
const zone = normalizeZoneName(opts.zone) ?? (opts.text ? suggestZonesFromText(model, opts.text)[0]?.zone ?? null : null);
|
|
1568
1568
|
if (zone) {
|
|
1569
|
-
const
|
|
1570
|
-
if (
|
|
1569
|
+
const stat2 = model.zones.find((z2) => z2.zone === zone);
|
|
1570
|
+
if (stat2) stat2.files.slice(0, limit).forEach((f, i) => put(f, Math.max(0.12, 0.45 - i * 0.02), `\u0437\u043E\u043D\u0430 ${zone}`));
|
|
1571
1571
|
}
|
|
1572
1572
|
return [...best.values()].sort((a, b) => b.weight - a.weight || a.path.localeCompare(b.path)).slice(0, limit);
|
|
1573
1573
|
}
|
|
@@ -2231,10 +2231,10 @@ function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWAR
|
|
|
2231
2231
|
const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
|
|
2232
2232
|
for (const file of ["auth.json", "config.toml"]) {
|
|
2233
2233
|
const target = join5(real, file);
|
|
2234
|
-
const
|
|
2235
|
-
if (!existsSync4(target) || existsSync4(
|
|
2234
|
+
const link2 = join5(home, file);
|
|
2235
|
+
if (!existsSync4(target) || existsSync4(link2)) continue;
|
|
2236
2236
|
try {
|
|
2237
|
-
symlinkSync(target,
|
|
2237
|
+
symlinkSync(target, link2);
|
|
2238
2238
|
} catch {
|
|
2239
2239
|
}
|
|
2240
2240
|
}
|
|
@@ -2433,6 +2433,9 @@ __export(config_exports, {
|
|
|
2433
2433
|
DRONE_HOME: () => DRONE_HOME,
|
|
2434
2434
|
DRONE_HOME_ISOLATION: () => DRONE_HOME_ISOLATION,
|
|
2435
2435
|
DRONE_ISOLATION: () => DRONE_ISOLATION,
|
|
2436
|
+
ENV_CACHE_DIR: () => ENV_CACHE_DIR,
|
|
2437
|
+
ENV_CACHE_ENABLED: () => ENV_CACHE_ENABLED,
|
|
2438
|
+
ENV_CACHE_KEEP: () => ENV_CACHE_KEEP,
|
|
2436
2439
|
EXECUTOR: () => EXECUTOR,
|
|
2437
2440
|
HEARTBEAT_PATH: () => HEARTBEAT_PATH,
|
|
2438
2441
|
HEARTBEAT_STALE_MS: () => HEARTBEAT_STALE_MS,
|
|
@@ -2602,7 +2605,7 @@ function reviewCutoffAt() {
|
|
|
2602
2605
|
}
|
|
2603
2606
|
return at;
|
|
2604
2607
|
}
|
|
2605
|
-
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_REST_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2608
|
+
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, ENV_CACHE_DIR, ENV_CACHE_ENABLED, ENV_CACHE_KEEP, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_REST_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
2606
2609
|
var init_config = __esm({
|
|
2607
2610
|
"src/config.ts"() {
|
|
2608
2611
|
"use strict";
|
|
@@ -2628,6 +2631,9 @@ var init_config = __esm({
|
|
|
2628
2631
|
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join6(SWARM_HOME, "outbox.jsonl");
|
|
2629
2632
|
SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
|
|
2630
2633
|
SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
|
|
2634
|
+
ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR || join6(SWARM_HOME, "env-cache");
|
|
2635
|
+
ENV_CACHE_ENABLED = process.env.SWARM_ENV_CACHE !== "0";
|
|
2636
|
+
ENV_CACHE_KEEP = Math.trunc(envNum("SWARM_ENV_CACHE_KEEP", 4));
|
|
2631
2637
|
SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
|
|
2632
2638
|
SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
|
|
2633
2639
|
SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
|
|
@@ -3327,6 +3333,118 @@ var init_logNoise = __esm({
|
|
|
3327
3333
|
}
|
|
3328
3334
|
});
|
|
3329
3335
|
|
|
3336
|
+
// src/netTimeout.ts
|
|
3337
|
+
import { basename as basename2 } from "path";
|
|
3338
|
+
function isTimedOut(outcome) {
|
|
3339
|
+
return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
|
|
3340
|
+
}
|
|
3341
|
+
function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
|
|
3342
|
+
const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
|
|
3343
|
+
if (!isTimedOut(outcome)) {
|
|
3344
|
+
timeoutNoise.forget(noiseKey(scope, call));
|
|
3345
|
+
return false;
|
|
3346
|
+
}
|
|
3347
|
+
const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
|
|
3348
|
+
if (say) warn(say);
|
|
3349
|
+
return true;
|
|
3350
|
+
}
|
|
3351
|
+
function noiseKey(scope, call) {
|
|
3352
|
+
return `timeout:${scope}:${call}`;
|
|
3353
|
+
}
|
|
3354
|
+
function callFailureWord(f) {
|
|
3355
|
+
switch (f.reason) {
|
|
3356
|
+
case "timeout":
|
|
3357
|
+
return "\u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 \u043E\u0442\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F (\u043F\u0440\u0435\u0440\u0432\u0430\u043D)";
|
|
3358
|
+
case "unreadable":
|
|
3359
|
+
return "\u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043D\u043E \u0432\u044B\u0432\u043E\u0434 \u043D\u0435 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u043D";
|
|
3360
|
+
case "crashed":
|
|
3361
|
+
return "\u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F";
|
|
3362
|
+
default:
|
|
3363
|
+
return "\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u044B\u0432\u043E\u0434\u0430";
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
function classifyCallFailure(outcome) {
|
|
3367
|
+
if (isTimedOut(outcome)) return { reason: "timeout", detail: firstLineOf(errText(outcome)) };
|
|
3368
|
+
const code = outcome?.code;
|
|
3369
|
+
if (code === "ENOENT") return { reason: "crashed", detail: firstLineOf(errText(outcome)) };
|
|
3370
|
+
return { reason: "empty", detail: firstLineOf(errText(outcome)) };
|
|
3371
|
+
}
|
|
3372
|
+
function errText(outcome) {
|
|
3373
|
+
const o = outcome;
|
|
3374
|
+
for (const v of [o?.stderr, o?.shortMessage, o?.message]) if (typeof v === "string" && v.trim()) return v;
|
|
3375
|
+
return "";
|
|
3376
|
+
}
|
|
3377
|
+
function firstLineOf(s) {
|
|
3378
|
+
return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
|
|
3379
|
+
}
|
|
3380
|
+
function noteCallFailure(scope, call, outcome, warn = console.warn) {
|
|
3381
|
+
const f = classifyCallFailure(outcome);
|
|
3382
|
+
if (f.reason === "timeout") return f;
|
|
3383
|
+
const line = `[${scope}] \u2716 \u0432\u044B\u0437\u043E\u0432 ${callFailureWord(f)}: ${call}${f.detail ? ` \u2014 ${f.detail}` : ""} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
|
|
3384
|
+
const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
|
|
3385
|
+
if (say) warn(say);
|
|
3386
|
+
return f;
|
|
3387
|
+
}
|
|
3388
|
+
function humanMs(ms) {
|
|
3389
|
+
return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
|
|
3390
|
+
}
|
|
3391
|
+
function destroyPipes(child) {
|
|
3392
|
+
for (const pipe of [child.stdout, child.stderr, child.stdin]) {
|
|
3393
|
+
try {
|
|
3394
|
+
pipe?.destroy?.();
|
|
3395
|
+
} catch {
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
|
|
3400
|
+
let timer;
|
|
3401
|
+
const capped = new Promise((_, reject) => {
|
|
3402
|
+
timer = setTimeout(() => {
|
|
3403
|
+
child.kill("SIGKILL");
|
|
3404
|
+
destroyPipes(child);
|
|
3405
|
+
reject(new CallTimedOutError(call, timeoutMs));
|
|
3406
|
+
}, timeoutMs + HARD_CAP_GRACE_MS);
|
|
3407
|
+
timer.unref?.();
|
|
3408
|
+
});
|
|
3409
|
+
try {
|
|
3410
|
+
const res = await Promise.race([child, capped]);
|
|
3411
|
+
noteTimedOut(scope, call, timeoutMs, res, warn);
|
|
3412
|
+
return res;
|
|
3413
|
+
} catch (e) {
|
|
3414
|
+
noteTimedOut(scope, call, timeoutMs, e, warn);
|
|
3415
|
+
throw e;
|
|
3416
|
+
} finally {
|
|
3417
|
+
if (timer) clearTimeout(timer);
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
function callLabel(cmd, args, head = 4) {
|
|
3421
|
+
const shown = args.slice(0, head).join(" ");
|
|
3422
|
+
return `${cmd} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
|
|
3423
|
+
}
|
|
3424
|
+
function gitCallLabel(repo, args) {
|
|
3425
|
+
return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
|
|
3426
|
+
}
|
|
3427
|
+
var GH_CALL_TIMEOUT_MS, GIT_NET_TIMEOUT_MS, GIT_LOCAL_TIMEOUT_MS, GIT_HEAVY_NET_TIMEOUT_MS, HARD_CAP_GRACE_MS, CallTimedOutError, timeoutNoise;
|
|
3428
|
+
var init_netTimeout = __esm({
|
|
3429
|
+
"src/netTimeout.ts"() {
|
|
3430
|
+
"use strict";
|
|
3431
|
+
init_logNoise();
|
|
3432
|
+
GH_CALL_TIMEOUT_MS = 15e3;
|
|
3433
|
+
GIT_NET_TIMEOUT_MS = 15e3;
|
|
3434
|
+
GIT_LOCAL_TIMEOUT_MS = 5e3;
|
|
3435
|
+
GIT_HEAVY_NET_TIMEOUT_MS = 6e4;
|
|
3436
|
+
HARD_CAP_GRACE_MS = 2e3;
|
|
3437
|
+
CallTimedOutError = class extends Error {
|
|
3438
|
+
timedOut = true;
|
|
3439
|
+
constructor(call, timeoutMs) {
|
|
3440
|
+
super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
|
|
3441
|
+
this.name = "CallTimedOutError";
|
|
3442
|
+
}
|
|
3443
|
+
};
|
|
3444
|
+
timeoutNoise = makeRepeatFilter();
|
|
3445
|
+
}
|
|
3446
|
+
});
|
|
3447
|
+
|
|
3330
3448
|
// src/procList.ts
|
|
3331
3449
|
function parsePsRows(out) {
|
|
3332
3450
|
const rows = [];
|
|
@@ -3645,88 +3763,10 @@ var init_procCleanup = __esm({
|
|
|
3645
3763
|
}
|
|
3646
3764
|
});
|
|
3647
3765
|
|
|
3648
|
-
// src/netTimeout.ts
|
|
3649
|
-
import { basename as basename2 } from "path";
|
|
3650
|
-
function isTimedOut(outcome) {
|
|
3651
|
-
return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
|
|
3652
|
-
}
|
|
3653
|
-
function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
|
|
3654
|
-
const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
|
|
3655
|
-
if (!isTimedOut(outcome)) {
|
|
3656
|
-
timeoutNoise.forget(noiseKey(scope, call));
|
|
3657
|
-
return false;
|
|
3658
|
-
}
|
|
3659
|
-
const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
|
|
3660
|
-
if (say) warn(say);
|
|
3661
|
-
return true;
|
|
3662
|
-
}
|
|
3663
|
-
function noiseKey(scope, call) {
|
|
3664
|
-
return `timeout:${scope}:${call}`;
|
|
3665
|
-
}
|
|
3666
|
-
function humanMs(ms) {
|
|
3667
|
-
return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
|
|
3668
|
-
}
|
|
3669
|
-
function destroyPipes(child) {
|
|
3670
|
-
for (const pipe of [child.stdout, child.stderr, child.stdin]) {
|
|
3671
|
-
try {
|
|
3672
|
-
pipe?.destroy?.();
|
|
3673
|
-
} catch {
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
|
|
3678
|
-
let timer;
|
|
3679
|
-
const capped = new Promise((_, reject) => {
|
|
3680
|
-
timer = setTimeout(() => {
|
|
3681
|
-
child.kill("SIGKILL");
|
|
3682
|
-
destroyPipes(child);
|
|
3683
|
-
reject(new CallTimedOutError(call, timeoutMs));
|
|
3684
|
-
}, timeoutMs + HARD_CAP_GRACE_MS);
|
|
3685
|
-
timer.unref?.();
|
|
3686
|
-
});
|
|
3687
|
-
try {
|
|
3688
|
-
const res = await Promise.race([child, capped]);
|
|
3689
|
-
noteTimedOut(scope, call, timeoutMs, res, warn);
|
|
3690
|
-
return res;
|
|
3691
|
-
} catch (e) {
|
|
3692
|
-
noteTimedOut(scope, call, timeoutMs, e, warn);
|
|
3693
|
-
throw e;
|
|
3694
|
-
} finally {
|
|
3695
|
-
if (timer) clearTimeout(timer);
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
|
-
function callLabel(cmd, args, head = 4) {
|
|
3699
|
-
const shown = args.slice(0, head).join(" ");
|
|
3700
|
-
return `${cmd} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
|
|
3701
|
-
}
|
|
3702
|
-
function gitCallLabel(repo, args) {
|
|
3703
|
-
return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
|
|
3704
|
-
}
|
|
3705
|
-
var GH_CALL_TIMEOUT_MS, GIT_NET_TIMEOUT_MS, GIT_LOCAL_TIMEOUT_MS, GIT_HEAVY_NET_TIMEOUT_MS, HARD_CAP_GRACE_MS, CallTimedOutError, timeoutNoise;
|
|
3706
|
-
var init_netTimeout = __esm({
|
|
3707
|
-
"src/netTimeout.ts"() {
|
|
3708
|
-
"use strict";
|
|
3709
|
-
init_logNoise();
|
|
3710
|
-
GH_CALL_TIMEOUT_MS = 15e3;
|
|
3711
|
-
GIT_NET_TIMEOUT_MS = 15e3;
|
|
3712
|
-
GIT_LOCAL_TIMEOUT_MS = 5e3;
|
|
3713
|
-
GIT_HEAVY_NET_TIMEOUT_MS = 6e4;
|
|
3714
|
-
HARD_CAP_GRACE_MS = 2e3;
|
|
3715
|
-
CallTimedOutError = class extends Error {
|
|
3716
|
-
timedOut = true;
|
|
3717
|
-
constructor(call, timeoutMs) {
|
|
3718
|
-
super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
|
|
3719
|
-
this.name = "CallTimedOutError";
|
|
3720
|
-
}
|
|
3721
|
-
};
|
|
3722
|
-
timeoutNoise = makeRepeatFilter();
|
|
3723
|
-
}
|
|
3724
|
-
});
|
|
3725
|
-
|
|
3726
3766
|
// src/git.ts
|
|
3727
|
-
import { existsSync as
|
|
3767
|
+
import { existsSync as existsSync12, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
3728
3768
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3729
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3769
|
+
import { isAbsolute as isAbsolute2, join as join19, sep as sep2 } from "path";
|
|
3730
3770
|
import { $ as $2 } from "execa";
|
|
3731
3771
|
function gitNet(repo, args, timeoutMs) {
|
|
3732
3772
|
return withHardTimeout(
|
|
@@ -3749,7 +3789,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
3749
3789
|
}
|
|
3750
3790
|
function isLinkedWorktree(dir) {
|
|
3751
3791
|
try {
|
|
3752
|
-
return statSync8(
|
|
3792
|
+
return statSync8(join19(dir, ".git")).isFile();
|
|
3753
3793
|
} catch {
|
|
3754
3794
|
return false;
|
|
3755
3795
|
}
|
|
@@ -3761,7 +3801,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
3761
3801
|
}
|
|
3762
3802
|
}
|
|
3763
3803
|
async function ensureWorktreeDirGone(worktree) {
|
|
3764
|
-
if (!
|
|
3804
|
+
if (!existsSync12(worktree)) return false;
|
|
3765
3805
|
if (!isLinkedWorktree(worktree)) {
|
|
3766
3806
|
console.error(
|
|
3767
3807
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -3776,7 +3816,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
3776
3816
|
);
|
|
3777
3817
|
return true;
|
|
3778
3818
|
}
|
|
3779
|
-
if (
|
|
3819
|
+
if (existsSync12(worktree)) {
|
|
3780
3820
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
3781
3821
|
}
|
|
3782
3822
|
return true;
|
|
@@ -3824,7 +3864,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3824
3864
|
}
|
|
3825
3865
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
3826
3866
|
if (busyAt) {
|
|
3827
|
-
const exists =
|
|
3867
|
+
const exists = existsSync12(busyAt);
|
|
3828
3868
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
3829
3869
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
3830
3870
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -3859,8 +3899,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3859
3899
|
try {
|
|
3860
3900
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3861
3901
|
if (!commonDir) return false;
|
|
3862
|
-
const infoDir =
|
|
3863
|
-
const excludeFile =
|
|
3902
|
+
const infoDir = join19(commonDir, "info");
|
|
3903
|
+
const excludeFile = join19(infoDir, "exclude");
|
|
3864
3904
|
let current = "";
|
|
3865
3905
|
try {
|
|
3866
3906
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3880,7 +3920,7 @@ async function gitPath(worktree, flag) {
|
|
|
3880
3920
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3881
3921
|
const raw = out.stdout.trim();
|
|
3882
3922
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3883
|
-
return isAbsolute2(raw) ? raw :
|
|
3923
|
+
return isAbsolute2(raw) ? raw : join19(worktree, raw);
|
|
3884
3924
|
}
|
|
3885
3925
|
async function ensureWorktreeHygiene(worktree) {
|
|
3886
3926
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3890,12 +3930,12 @@ async function installGuardHooks(worktree) {
|
|
|
3890
3930
|
try {
|
|
3891
3931
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3892
3932
|
if (!gitDir) return false;
|
|
3893
|
-
const hooksDir =
|
|
3933
|
+
const hooksDir = join19(gitDir, "swarm-hooks");
|
|
3894
3934
|
const prev = await previousHooksPath(worktree);
|
|
3895
3935
|
await mkdir(hooksDir, { recursive: true });
|
|
3896
3936
|
for (const hook of GUARD_HOOKS) {
|
|
3897
|
-
await writeFile(
|
|
3898
|
-
await chmod(
|
|
3937
|
+
await writeFile(join19(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
3938
|
+
await chmod(join19(hooksDir, hook), 493);
|
|
3899
3939
|
}
|
|
3900
3940
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3901
3941
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3914,7 +3954,7 @@ async function previousHooksPath(worktree) {
|
|
|
3914
3954
|
if (out.exitCode === 0 && value) return value;
|
|
3915
3955
|
}
|
|
3916
3956
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3917
|
-
return commonDir ?
|
|
3957
|
+
return commonDir ? join19(commonDir, "hooks") : null;
|
|
3918
3958
|
}
|
|
3919
3959
|
async function removeWorktree(repo, path) {
|
|
3920
3960
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3939,7 +3979,7 @@ async function worktreeDirty(path) {
|
|
|
3939
3979
|
async function repairMovedWorktrees(targetDir) {
|
|
3940
3980
|
const subdirs = (dir) => {
|
|
3941
3981
|
try {
|
|
3942
|
-
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) =>
|
|
3982
|
+
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join19(dir, d.name));
|
|
3943
3983
|
} catch {
|
|
3944
3984
|
return [];
|
|
3945
3985
|
}
|
|
@@ -4056,6 +4096,7 @@ var init_git = __esm({
|
|
|
4056
4096
|
var droneChannel_exports = {};
|
|
4057
4097
|
__export(droneChannel_exports, {
|
|
4058
4098
|
ChannelUnavailableError: () => ChannelUnavailableError,
|
|
4099
|
+
PATCHABLE: () => PATCHABLE,
|
|
4059
4100
|
channelDroneStore: () => channelDroneStore,
|
|
4060
4101
|
closeDroneChannel: () => closeDroneChannel,
|
|
4061
4102
|
loadChannelSecret: () => loadChannelSecret,
|
|
@@ -4068,18 +4109,18 @@ __export(droneChannel_exports, {
|
|
|
4068
4109
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
4069
4110
|
});
|
|
4070
4111
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
4071
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as
|
|
4112
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
4072
4113
|
import { dirname as dirname4 } from "path";
|
|
4073
4114
|
import { serve } from "@hono/node-server";
|
|
4074
4115
|
function loadChannelSecret(path) {
|
|
4075
4116
|
try {
|
|
4076
|
-
const s =
|
|
4117
|
+
const s = readFileSync18(path, "utf8").trim();
|
|
4077
4118
|
if (s.length >= 32) return s;
|
|
4078
4119
|
} catch {
|
|
4079
4120
|
}
|
|
4080
4121
|
const secret = randomBytes(32).toString("hex");
|
|
4081
4122
|
mkdirSync9(dirname4(path), { recursive: true });
|
|
4082
|
-
|
|
4123
|
+
writeFileSync12(path, secret, { mode: 384 });
|
|
4083
4124
|
try {
|
|
4084
4125
|
chmodSync2(path, 384);
|
|
4085
4126
|
} catch {
|
|
@@ -4139,6 +4180,16 @@ function invalidPatchValue(field, v) {
|
|
|
4139
4180
|
return typeof v === "number" && Number.isInteger(v) && v >= 0 ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u0446\u0438\u043A\u043B \u0440\u0435\u0432\u044C\u044E: ${String(v)}`;
|
|
4140
4181
|
case "pr_url":
|
|
4141
4182
|
return v === null || typeof v === "string" ? null : "pr_url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439";
|
|
4183
|
+
// TASWM-532: имя ветки уходит в аргументы git у ревьюера и резолвера, поэтому проверяем
|
|
4184
|
+
// форму, а не только тип: пустая строка и пробелы внутри — это не ref, а сбой чтения.
|
|
4185
|
+
case "branch":
|
|
4186
|
+
return typeof v === "string" && v.length > 0 && v.length <= BRANCH_LEN_MAX && !BAD_IN_BRANCH.test(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0438\u043C\u044F \u0432\u0435\u0442\u043A\u0438: ${String(v).slice(0, 80)}`;
|
|
4187
|
+
case "stop_reason":
|
|
4188
|
+
return v === null || typeof v === "string" && v.length <= STOP_REASON_LEN_MAX ? null : "stop_reason \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u043E\u0439 (\u0438\u043B\u0438 null) \u0432 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445 \u043F\u043E\u0442\u043E\u043B\u043A\u0430 \u0434\u043B\u0438\u043D\u044B";
|
|
4189
|
+
// Словарь кодов возврата один на весь продукт (swarm-core) — проверяем его же функцией,
|
|
4190
|
+
// как verify_verdict выше, а не повторяем литералы ещё одним списком.
|
|
4191
|
+
case "return_reason":
|
|
4192
|
+
return v === null || isReturnReason(v) ? null : `\u043D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0439 \u043A\u043E\u0434 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430: ${String(v)}`;
|
|
4142
4193
|
case "changed_paths": {
|
|
4143
4194
|
if (!Array.isArray(v)) return "changed_paths \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043C\u0430\u0441\u0441\u0438\u0432\u043E\u043C \u043F\u0443\u0442\u0435\u0439";
|
|
4144
4195
|
if (v.length > CHANGED_PATHS_MAX) return `changed_paths: \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E \u043F\u0443\u0442\u0435\u0439 (${v.length} > ${CHANGED_PATHS_MAX})`;
|
|
@@ -4202,10 +4253,18 @@ function makeChannelHandler(deps) {
|
|
|
4202
4253
|
if (id == null) return bad(403, "\u043F\u0440\u043E\u0433\u043E\u043D \u0432\u043D\u0435 \u0441\u043A\u043E\u0443\u043F\u0430 \u043A\u0430\u043D\u0430\u043B\u0430");
|
|
4203
4254
|
const raw = p.patch ?? {};
|
|
4204
4255
|
const patch = {};
|
|
4256
|
+
const refuse = (status, why) => {
|
|
4257
|
+
log(`[\u043A\u0430\u043D\u0430\u043B] \u043F\u0430\u0442\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${id} \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D (${status}): ${why}`);
|
|
4258
|
+
try {
|
|
4259
|
+
deps.store.addEvent(id, "step", `\u043A\u0430\u043D\u0430\u043B \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u043F\u0430\u0442\u0447 \u0434\u0440\u043E\u043D\u0430 (${status}): ${why}`, null);
|
|
4260
|
+
} catch {
|
|
4261
|
+
}
|
|
4262
|
+
return bad(status, why);
|
|
4263
|
+
};
|
|
4205
4264
|
for (const [k, v] of Object.entries(raw)) {
|
|
4206
|
-
if (!PATCHABLE.has(k)) return
|
|
4265
|
+
if (!PATCHABLE.has(k)) return refuse(403, `\u043F\u043E\u043B\u0435 \xAB${k}\xBB \u043A\u0430\u043D\u0430\u043B\u0443 \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442\u0441\u044F`);
|
|
4207
4266
|
const why = invalidPatchValue(k, v);
|
|
4208
|
-
if (why) return
|
|
4267
|
+
if (why) return refuse(400, why);
|
|
4209
4268
|
patch[k] = v;
|
|
4210
4269
|
}
|
|
4211
4270
|
deps.store.updateRun(id, patch);
|
|
@@ -4398,7 +4457,7 @@ function channelDroneStore(opts) {
|
|
|
4398
4457
|
openQuestionsFor: async () => (await call("openQuestionsFor")).questions
|
|
4399
4458
|
};
|
|
4400
4459
|
}
|
|
4401
|
-
var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
|
|
4460
|
+
var LOCAL_CLI_KEY, PATCHABLE, CHANGED_PATHS_MAX, CHANGED_PATH_LEN_MAX, BRANCH_LEN_MAX, STOP_REASON_LEN_MAX, BAD_IN_BRANCH, EVENT_KIND_SET, ChannelUnavailableError, BACKOFF_MS;
|
|
4402
4461
|
var init_droneChannel = __esm({
|
|
4403
4462
|
"src/droneChannel.ts"() {
|
|
4404
4463
|
"use strict";
|
|
@@ -4418,10 +4477,24 @@ var init_droneChannel = __esm({
|
|
|
4418
4477
|
// worktree), а не модель — со стороны канала это такой же самоотчёт прогона о себе, как
|
|
4419
4478
|
// pr_url. Потолки на размер — ниже, в invalidPatchValue: список идёт в гейт раздачи, и
|
|
4420
4479
|
// сорвавшийся дрон не должен уметь заявить занятым весь репозиторий.
|
|
4421
|
-
"changed_paths"
|
|
4480
|
+
"changed_paths",
|
|
4481
|
+
// TASWM-532: ФАКТИЧЕСКАЯ ветка сданного PR. Её подмешивает в патч сдачи не модель, а наш код
|
|
4482
|
+
// (droneServer.done → branchFromPrPatch по ответу `gh pr view --json headRefName`) — тот же
|
|
4483
|
+
// довод, что у changed_paths строкой выше. «Ветка и так известна из прогона» — неправда:
|
|
4484
|
+
// поле пишется на спавне из слага задачи, а дрон мог продолжать ЧУЖОЙ PR, и по этому полю
|
|
4485
|
+
// ревьюер строит worktree `origin/<branch>` (TASWM-356).
|
|
4486
|
+
"branch",
|
|
4487
|
+
// TASWM-532: возврат задачи автору (`return_task`) пишет их одним патчем со статусом
|
|
4488
|
+
// "returned" — проза для ленты задачи и код причины для распределения по бэклогу
|
|
4489
|
+
// (TASWM-505). Без них 403 ложился на возврат ровно так же, как на сдачу.
|
|
4490
|
+
"stop_reason",
|
|
4491
|
+
"return_reason"
|
|
4422
4492
|
]);
|
|
4423
4493
|
CHANGED_PATHS_MAX = 1e3;
|
|
4424
4494
|
CHANGED_PATH_LEN_MAX = 1024;
|
|
4495
|
+
BRANCH_LEN_MAX = 255;
|
|
4496
|
+
STOP_REASON_LEN_MAX = 2e3;
|
|
4497
|
+
BAD_IN_BRANCH = /[\s~^:?*[\\\p{Cc}]/u;
|
|
4425
4498
|
EVENT_KIND_SET = new Set(EVENT_KINDS);
|
|
4426
4499
|
ChannelUnavailableError = class extends Error {
|
|
4427
4500
|
constructor(downMs, message) {
|
|
@@ -4436,8 +4509,8 @@ var init_droneChannel = __esm({
|
|
|
4436
4509
|
});
|
|
4437
4510
|
|
|
4438
4511
|
// src/attachments.ts
|
|
4439
|
-
import { mkdir as
|
|
4440
|
-
import { join as
|
|
4512
|
+
import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4513
|
+
import { join as join23 } from "path";
|
|
4441
4514
|
function attachmentsFromDoc(doc, origin) {
|
|
4442
4515
|
const out = [];
|
|
4443
4516
|
const walk2 = (node) => {
|
|
@@ -4522,13 +4595,13 @@ async function materializeAttachments(opts) {
|
|
|
4522
4595
|
const maxTotal = opts.maxTotalBytes ?? ATTACH_MAX_TOTAL_BYTES;
|
|
4523
4596
|
const maxFile = opts.maxFileBytes ?? ATTACH_MAX_FILE_BYTES;
|
|
4524
4597
|
const io = opts.io ?? {
|
|
4525
|
-
removeDir: async (dir2) =>
|
|
4526
|
-
mkdir: async (dir2) => void await
|
|
4527
|
-
writeFile: async (path, bytes) =>
|
|
4598
|
+
removeDir: async (dir2) => rm3(dir2, { recursive: true, force: true }),
|
|
4599
|
+
mkdir: async (dir2) => void await mkdir3(dir2, { recursive: true }),
|
|
4600
|
+
writeFile: async (path, bytes) => writeFile3(path, bytes)
|
|
4528
4601
|
};
|
|
4529
4602
|
const result = { files: [], skipped: [] };
|
|
4530
4603
|
if (opts.refs.length === 0) return result;
|
|
4531
|
-
const dir =
|
|
4604
|
+
const dir = join23(opts.worktree, ATTACHMENTS_DIR);
|
|
4532
4605
|
await io.removeDir(dir);
|
|
4533
4606
|
let dirReady = false;
|
|
4534
4607
|
let total = 0;
|
|
@@ -4553,7 +4626,7 @@ async function materializeAttachments(opts) {
|
|
|
4553
4626
|
try {
|
|
4554
4627
|
file = await opts.fetchFile(ref.url);
|
|
4555
4628
|
} catch (e) {
|
|
4556
|
-
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${
|
|
4629
|
+
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C (${errText2(e)})`);
|
|
4557
4630
|
continue;
|
|
4558
4631
|
}
|
|
4559
4632
|
if (!file) {
|
|
@@ -4574,9 +4647,9 @@ async function materializeAttachments(opts) {
|
|
|
4574
4647
|
await io.mkdir(dir);
|
|
4575
4648
|
dirReady = true;
|
|
4576
4649
|
}
|
|
4577
|
-
await io.writeFile(
|
|
4650
|
+
await io.writeFile(join23(dir, name), file.bytes);
|
|
4578
4651
|
} catch (e) {
|
|
4579
|
-
result.skipped.push(`${label2} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${
|
|
4652
|
+
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)})`);
|
|
4580
4653
|
continue;
|
|
4581
4654
|
}
|
|
4582
4655
|
total += file.bytes.byteLength;
|
|
@@ -4630,7 +4703,7 @@ async function prepareTaskAttachments(opts) {
|
|
|
4630
4703
|
}
|
|
4631
4704
|
return renderAttachmentsBlock(result);
|
|
4632
4705
|
} catch (e) {
|
|
4633
|
-
opts.onEvent?.(`\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0432\u044B\u043B\u043E\u0436\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${
|
|
4706
|
+
opts.onEvent?.(`\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0432\u044B\u043B\u043E\u0436\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${errText2(e)} \u2014 \u0434\u0440\u043E\u043D \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u0431\u0435\u0437 \u043D\u0438\u0445`);
|
|
4634
4707
|
return void 0;
|
|
4635
4708
|
}
|
|
4636
4709
|
}
|
|
@@ -4653,7 +4726,7 @@ function mb(bytes) {
|
|
|
4653
4726
|
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} \u041C\u0411`;
|
|
4654
4727
|
return `${bytes === 0 ? 0 : Math.max(1, Math.round(bytes / 1024))} \u041A\u0411`;
|
|
4655
4728
|
}
|
|
4656
|
-
function
|
|
4729
|
+
function errText2(e) {
|
|
4657
4730
|
return e instanceof Error ? e.message : String(e);
|
|
4658
4731
|
}
|
|
4659
4732
|
var ATTACHMENTS_DIR, ATTACH_MAX_FILES, ATTACH_MAX_TOTAL_BYTES, ATTACH_MAX_FILE_BYTES, IMAGE_EXT, DOC_EXT, TEXT_EXT, KNOWN_EXT, TRANSLIT, EXT_BY_MIME;
|
|
@@ -4727,7 +4800,7 @@ var init_attachments = __esm({
|
|
|
4727
4800
|
|
|
4728
4801
|
// src/shotUpload.ts
|
|
4729
4802
|
import { readFile as fsReadFile } from "fs/promises";
|
|
4730
|
-
import { basename as basename4, isAbsolute as isAbsolute3, join as
|
|
4803
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join24 } from "path";
|
|
4731
4804
|
function shotMimetype(path) {
|
|
4732
4805
|
const dot = path.lastIndexOf(".");
|
|
4733
4806
|
if (dot < 0) return null;
|
|
@@ -4753,7 +4826,7 @@ async function readShots(paths, opts) {
|
|
|
4753
4826
|
}
|
|
4754
4827
|
let bytes;
|
|
4755
4828
|
try {
|
|
4756
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
4829
|
+
bytes = await read(isAbsolute3(path) ? path : join24(opts.worktree, path));
|
|
4757
4830
|
} catch (e) {
|
|
4758
4831
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
4759
4832
|
continue;
|
|
@@ -4833,12 +4906,12 @@ var init_shotUpload = __esm({
|
|
|
4833
4906
|
});
|
|
4834
4907
|
|
|
4835
4908
|
// src/oauth.ts
|
|
4836
|
-
import { createHash as
|
|
4837
|
-
import { readFileSync as
|
|
4838
|
-
import { join as
|
|
4909
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
4910
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13, unlinkSync as unlinkSync4 } from "fs";
|
|
4911
|
+
import { join as join25 } from "path";
|
|
4839
4912
|
function makePkce() {
|
|
4840
4913
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4841
|
-
const challenge =
|
|
4914
|
+
const challenge = createHash3("sha256").update(verifier).digest("base64url");
|
|
4842
4915
|
return { verifier, challenge };
|
|
4843
4916
|
}
|
|
4844
4917
|
async function registerClient(args, fetchFn = fetch) {
|
|
@@ -4919,13 +4992,13 @@ function toStore(clientId, t, now) {
|
|
|
4919
4992
|
}
|
|
4920
4993
|
function loadStore() {
|
|
4921
4994
|
try {
|
|
4922
|
-
return JSON.parse(
|
|
4995
|
+
return JSON.parse(readFileSync19(STORE_PATH2, "utf8"));
|
|
4923
4996
|
} catch {
|
|
4924
4997
|
return null;
|
|
4925
4998
|
}
|
|
4926
4999
|
}
|
|
4927
5000
|
function saveStore(s) {
|
|
4928
|
-
|
|
5001
|
+
writeFileSync13(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
4929
5002
|
}
|
|
4930
5003
|
function clearStore() {
|
|
4931
5004
|
try {
|
|
@@ -4935,7 +5008,7 @@ function clearStore() {
|
|
|
4935
5008
|
}
|
|
4936
5009
|
function loadClientId() {
|
|
4937
5010
|
try {
|
|
4938
|
-
return JSON.parse(
|
|
5011
|
+
return JSON.parse(readFileSync19(CLIENT_PATH, "utf8")).clientId;
|
|
4939
5012
|
} catch {
|
|
4940
5013
|
return null;
|
|
4941
5014
|
}
|
|
@@ -4944,7 +5017,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
4944
5017
|
const existing = loadClientId();
|
|
4945
5018
|
if (existing) return existing;
|
|
4946
5019
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
4947
|
-
|
|
5020
|
+
writeFileSync13(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
4948
5021
|
return clientId;
|
|
4949
5022
|
}
|
|
4950
5023
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -4970,8 +5043,8 @@ var init_oauth = __esm({
|
|
|
4970
5043
|
AUTHORIZE = "/api/oauth/authorize";
|
|
4971
5044
|
TOKEN = "/api/oauth/token";
|
|
4972
5045
|
SCOPE = "openid profile email taskless:full";
|
|
4973
|
-
STORE_PATH2 =
|
|
4974
|
-
CLIENT_PATH =
|
|
5046
|
+
STORE_PATH2 = join25(SWARM_HOME, "oauth.json");
|
|
5047
|
+
CLIENT_PATH = join25(SWARM_HOME, "oauth-client.json");
|
|
4975
5048
|
base2 = (api) => api.replace(/\/$/, "");
|
|
4976
5049
|
}
|
|
4977
5050
|
});
|
|
@@ -5017,11 +5090,11 @@ var init_authState = __esm({
|
|
|
5017
5090
|
});
|
|
5018
5091
|
|
|
5019
5092
|
// src/workspace.ts
|
|
5020
|
-
import { readFileSync as
|
|
5021
|
-
import { join as
|
|
5093
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync as unlinkSync5 } from "fs";
|
|
5094
|
+
import { join as join26 } from "path";
|
|
5022
5095
|
function loadWorkspace() {
|
|
5023
5096
|
try {
|
|
5024
|
-
const w = JSON.parse(
|
|
5097
|
+
const w = JSON.parse(readFileSync20(WORKSPACE_PATH, "utf8"));
|
|
5025
5098
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
5026
5099
|
} catch {
|
|
5027
5100
|
return null;
|
|
@@ -5035,7 +5108,7 @@ var init_workspace = __esm({
|
|
|
5035
5108
|
"src/workspace.ts"() {
|
|
5036
5109
|
"use strict";
|
|
5037
5110
|
init_config();
|
|
5038
|
-
WORKSPACE_PATH =
|
|
5111
|
+
WORKSPACE_PATH = join26(SWARM_HOME, "workspace.json");
|
|
5039
5112
|
}
|
|
5040
5113
|
});
|
|
5041
5114
|
|
|
@@ -5616,7 +5689,7 @@ __export(db_exports, {
|
|
|
5616
5689
|
});
|
|
5617
5690
|
import { createRequire as createRequire2 } from "module";
|
|
5618
5691
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5619
|
-
import { existsSync as
|
|
5692
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5620
5693
|
import { resolve as resolve3 } from "path";
|
|
5621
5694
|
function rowToQuestion(row) {
|
|
5622
5695
|
return {
|
|
@@ -6239,7 +6312,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
6239
6312
|
const repoN = normalizeRepo(repo);
|
|
6240
6313
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
6241
6314
|
if (already) return { imported: false, runs: already.run_count };
|
|
6242
|
-
if (!
|
|
6315
|
+
if (!existsSync15(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
6243
6316
|
const src = new this.databaseCtor(abs);
|
|
6244
6317
|
try {
|
|
6245
6318
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -6608,10 +6681,10 @@ var init_droneStore = __esm({
|
|
|
6608
6681
|
|
|
6609
6682
|
// src/overmind.ts
|
|
6610
6683
|
import { homedir as homedir6 } from "os";
|
|
6611
|
-
import { existsSync as
|
|
6612
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
6684
|
+
import { existsSync as existsSync20, readFileSync as readFileSync24, readdirSync as readdirSync13, renameSync as renameSync12 } from "fs";
|
|
6685
|
+
import { basename as basename6, dirname as dirname5, join as join33 } from "path";
|
|
6613
6686
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
6614
|
-
import { spawn as
|
|
6687
|
+
import { spawn as spawn4 } from "child_process";
|
|
6615
6688
|
|
|
6616
6689
|
// src/restart.ts
|
|
6617
6690
|
init_src();
|
|
@@ -6654,11 +6727,11 @@ function savePauseState(home, state, io = {}) {
|
|
|
6654
6727
|
remove(path);
|
|
6655
6728
|
return;
|
|
6656
6729
|
}
|
|
6657
|
-
const
|
|
6730
|
+
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
6658
6731
|
writeFileSync(`${p}.tmp`, data);
|
|
6659
6732
|
renameSync(`${p}.tmp`, p);
|
|
6660
6733
|
});
|
|
6661
|
-
|
|
6734
|
+
writeFile4(path, JSON.stringify(state, null, 2));
|
|
6662
6735
|
}
|
|
6663
6736
|
function makePauseState(source, reason, now = Date.now()) {
|
|
6664
6737
|
return {
|
|
@@ -9415,6 +9488,26 @@ function rotateDaemonLog(path, opts, io = realIo) {
|
|
|
9415
9488
|
return "rotated";
|
|
9416
9489
|
}
|
|
9417
9490
|
|
|
9491
|
+
// src/daemonLaunch.ts
|
|
9492
|
+
import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync8 } from "fs";
|
|
9493
|
+
import { spawn } from "child_process";
|
|
9494
|
+
var realRespawnIo = {
|
|
9495
|
+
isTty: () => process.stdout.isTTY === true,
|
|
9496
|
+
open: (path) => openSync2(path, "a")
|
|
9497
|
+
};
|
|
9498
|
+
function respawnStdio(logPath, io = realRespawnIo) {
|
|
9499
|
+
if (io.isTty()) return "inherit";
|
|
9500
|
+
try {
|
|
9501
|
+
const fd = io.open(logPath);
|
|
9502
|
+
return ["ignore", fd, fd];
|
|
9503
|
+
} catch {
|
|
9504
|
+
return "inherit";
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
|
|
9508
|
+
// src/overmind.ts
|
|
9509
|
+
init_netTimeout();
|
|
9510
|
+
|
|
9418
9511
|
// src/logStamp.ts
|
|
9419
9512
|
import { format } from "util";
|
|
9420
9513
|
function logStamp(at) {
|
|
@@ -9496,13 +9589,13 @@ async function buildRepoRegistry(paths) {
|
|
|
9496
9589
|
}
|
|
9497
9590
|
|
|
9498
9591
|
// src/repoMemory.ts
|
|
9499
|
-
import { readFileSync as
|
|
9592
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
9500
9593
|
import { join as join10 } from "path";
|
|
9501
9594
|
var FILE = "repos.json";
|
|
9502
9595
|
function loadRememberedRepos(home) {
|
|
9503
9596
|
let raw;
|
|
9504
9597
|
try {
|
|
9505
|
-
raw =
|
|
9598
|
+
raw = readFileSync9(join10(home, FILE), "utf8");
|
|
9506
9599
|
} catch {
|
|
9507
9600
|
return [];
|
|
9508
9601
|
}
|
|
@@ -9532,11 +9625,11 @@ init_src();
|
|
|
9532
9625
|
init_config();
|
|
9533
9626
|
init_linkFailure();
|
|
9534
9627
|
import { homedir as homedir4 } from "os";
|
|
9535
|
-
import { readFileSync as
|
|
9628
|
+
import { readFileSync as readFileSync11, renameSync as renameSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
9536
9629
|
import { isAbsolute, join as join12, resolve as resolve2 } from "path";
|
|
9537
9630
|
|
|
9538
9631
|
// src/repoScan.ts
|
|
9539
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
9632
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
9540
9633
|
import { join as join11 } from "path";
|
|
9541
9634
|
var SKIP = /* @__PURE__ */ new Set([
|
|
9542
9635
|
"node_modules",
|
|
@@ -9567,7 +9660,7 @@ function parseOriginFromGitConfig(text) {
|
|
|
9567
9660
|
}
|
|
9568
9661
|
function originOf(dir) {
|
|
9569
9662
|
try {
|
|
9570
|
-
return parseOriginFromGitConfig(
|
|
9663
|
+
return parseOriginFromGitConfig(readFileSync10(join11(dir, ".git", "config"), "utf8"));
|
|
9571
9664
|
} catch {
|
|
9572
9665
|
return null;
|
|
9573
9666
|
}
|
|
@@ -9852,7 +9945,7 @@ function repoMapRows(state) {
|
|
|
9852
9945
|
}
|
|
9853
9946
|
function loadRepoMapCache(path) {
|
|
9854
9947
|
try {
|
|
9855
|
-
const raw = JSON.parse(
|
|
9948
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
9856
9949
|
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
9857
9950
|
return {
|
|
9858
9951
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
@@ -9875,7 +9968,7 @@ function writeRepoMapCache(path, entries, scannedAt = stamp()) {
|
|
|
9875
9968
|
|
|
9876
9969
|
// src/hostIdentity.ts
|
|
9877
9970
|
import { hostname } from "os";
|
|
9878
|
-
import { existsSync as existsSync9, readFileSync as
|
|
9971
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
9879
9972
|
import { join as join13 } from "path";
|
|
9880
9973
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9881
9974
|
function hostIdentityPath(home) {
|
|
@@ -9883,8 +9976,8 @@ function hostIdentityPath(home) {
|
|
|
9883
9976
|
}
|
|
9884
9977
|
function loadHostIdentity(deps) {
|
|
9885
9978
|
const exists = deps.exists ?? existsSync9;
|
|
9886
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
9887
|
-
const
|
|
9979
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
9980
|
+
const writeFile4 = deps.writeFile ?? ((p, data) => {
|
|
9888
9981
|
writeFileSync10(`${p}.tmp`, data);
|
|
9889
9982
|
renameSync7(`${p}.tmp`, p);
|
|
9890
9983
|
});
|
|
@@ -9904,7 +9997,7 @@ function loadHostIdentity(deps) {
|
|
|
9904
9997
|
}
|
|
9905
9998
|
const hostId = newId();
|
|
9906
9999
|
try {
|
|
9907
|
-
|
|
10000
|
+
writeFile4(path, JSON.stringify({ hostId, hostName, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
9908
10001
|
} catch {
|
|
9909
10002
|
console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C ${path} \u2014 hostId \u0436\u0438\u0432\u0451\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043E \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430`);
|
|
9910
10003
|
}
|
|
@@ -9928,7 +10021,13 @@ var DEFAULT_RELIEF_CONFIG = {
|
|
|
9928
10021
|
function resolveReliefConfig(cfg) {
|
|
9929
10022
|
return { ...DEFAULT_RELIEF_CONFIG, ...cfg ?? {} };
|
|
9930
10023
|
}
|
|
9931
|
-
var RELIEF_PAUSE_REASON = `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM
|
|
10024
|
+
var RELIEF_PAUSE_REASON = `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM), \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043F\u043E\u0441\u043B\u0435 \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0438 (TASWM-151)`;
|
|
10025
|
+
function reliefPauseReason(canContinue) {
|
|
10026
|
+
return canContinue ? `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 --continue: worktree \u0438 \u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435)` : `${RELIEF_PAUSE_MARK}: \u0445\u043E\u0441\u0442 \u043F\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043D \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D (SIGTERM; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C \u2014 worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B, \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u0441\u0442\u0430\u0440\u0442\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E)`;
|
|
10027
|
+
}
|
|
10028
|
+
function isReliefPause(stopReason) {
|
|
10029
|
+
return !!stopReason && stopReason.startsWith(RELIEF_PAUSE_MARK);
|
|
10030
|
+
}
|
|
9932
10031
|
function reliefResumeReason(mode) {
|
|
9933
10032
|
return mode === "continue" ? `${RELIEF_PAUSE_MARK} \u043E\u0442\u043F\u0443\u0441\u0442\u0438\u043B\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 (--continue: \u0440\u0430\u0431\u043E\u0442\u0430 \u0432 origin + \u0441\u0435\u0441\u0441\u0438\u044F \u0446\u0435\u043B\u044B)` : `${RELIEF_PAUSE_MARK} \u043E\u0442\u043F\u0443\u0441\u0442\u0438\u043B\u0430 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443, \u0441\u0442\u0430\u0440\u0442 \u0437\u0430\u043D\u043E\u0432\u043E (\u0441\u0435\u0441\u0441\u0438\u044F/worktree \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B)`;
|
|
9934
10033
|
}
|
|
@@ -9952,6 +10051,115 @@ function pickResumeCandidate(pausedForRelief) {
|
|
|
9952
10051
|
return candidates.reduce((oldest, r) => r.updated_at < oldest.updated_at ? r : oldest);
|
|
9953
10052
|
}
|
|
9954
10053
|
|
|
10054
|
+
// src/reliefLoop.ts
|
|
10055
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
10056
|
+
import { join as join14 } from "path";
|
|
10057
|
+
var numEnv3 = (name, def) => {
|
|
10058
|
+
const v = Number(process.env[name]);
|
|
10059
|
+
return Number.isFinite(v) && v > 0 ? v : def;
|
|
10060
|
+
};
|
|
10061
|
+
var DEFAULT_RELIEF_LOOP_CONFIG = {
|
|
10062
|
+
limit: numEnv3("SWARM_RELIEF_LOOP_LIMIT", 3),
|
|
10063
|
+
backoffMaxMs: numEnv3("SWARM_RELIEF_BACKOFF_MAX_MS", 10 * 6e4),
|
|
10064
|
+
seriesTtlMs: numEnv3("SWARM_RELIEF_LOOP_TTL_MS", 6 * 60 * 6e4)
|
|
10065
|
+
};
|
|
10066
|
+
function resolveReliefLoopConfig(cfg) {
|
|
10067
|
+
return { ...DEFAULT_RELIEF_LOOP_CONFIG, ...cfg ?? {} };
|
|
10068
|
+
}
|
|
10069
|
+
function reliefLoopKey(repo, slug) {
|
|
10070
|
+
return `${repo}#${slug}`;
|
|
10071
|
+
}
|
|
10072
|
+
function recordReliefLoopPause(state, key, now, delivered, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10073
|
+
const prev = state[key];
|
|
10074
|
+
const continues = !!prev && !delivered && now - prev.lastPauseAt <= cfg.seriesTtlMs;
|
|
10075
|
+
const entry = continues ? { pauses: prev.pauses + 1, lastPauseAt: now, ...prev.escalated ? { escalated: true } : {} } : { pauses: 1, lastPauseAt: now };
|
|
10076
|
+
state[key] = entry;
|
|
10077
|
+
return entry;
|
|
10078
|
+
}
|
|
10079
|
+
function clearReliefLoop(state, key) {
|
|
10080
|
+
delete state[key];
|
|
10081
|
+
}
|
|
10082
|
+
function reliefBackoffMs(pauses, baseMs, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10083
|
+
if (pauses <= 1) return baseMs;
|
|
10084
|
+
const grown = baseMs * 2 ** Math.min(pauses - 1, 30);
|
|
10085
|
+
return Math.min(grown, cfg.backoffMaxMs);
|
|
10086
|
+
}
|
|
10087
|
+
function reliefLoopTripped(entry, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10088
|
+
return !!entry && entry.pauses >= cfg.limit;
|
|
10089
|
+
}
|
|
10090
|
+
function reliefLoopStatePath(home) {
|
|
10091
|
+
return join14(home, "relief-loop.json");
|
|
10092
|
+
}
|
|
10093
|
+
function loadReliefLoopState(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10094
|
+
const exists = io.exists ?? existsSync10;
|
|
10095
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync13(p, "utf8"));
|
|
10096
|
+
const path = reliefLoopStatePath(home);
|
|
10097
|
+
if (!exists(path)) return {};
|
|
10098
|
+
try {
|
|
10099
|
+
const raw = JSON.parse(readFile2(path));
|
|
10100
|
+
const out = {};
|
|
10101
|
+
for (const [key, value] of Object.entries(raw ?? {})) {
|
|
10102
|
+
const v = value;
|
|
10103
|
+
const pauses = Number(v?.pauses);
|
|
10104
|
+
const lastPauseAt = Number(v?.lastPauseAt);
|
|
10105
|
+
if (!Number.isFinite(pauses) || pauses <= 0 || !Number.isFinite(lastPauseAt)) continue;
|
|
10106
|
+
if (now - lastPauseAt > cfg.seriesTtlMs) continue;
|
|
10107
|
+
out[key] = { pauses, lastPauseAt, ...v?.escalated ? { escalated: true } : {} };
|
|
10108
|
+
}
|
|
10109
|
+
return out;
|
|
10110
|
+
} catch {
|
|
10111
|
+
return {};
|
|
10112
|
+
}
|
|
10113
|
+
}
|
|
10114
|
+
function saveReliefLoopState(home, state, io = {}) {
|
|
10115
|
+
const path = reliefLoopStatePath(home);
|
|
10116
|
+
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
10117
|
+
writeFileSync11(`${p}.tmp`, data);
|
|
10118
|
+
renameSync8(`${p}.tmp`, p);
|
|
10119
|
+
});
|
|
10120
|
+
writeFile4(path, JSON.stringify(state, null, 2));
|
|
10121
|
+
}
|
|
10122
|
+
var ReliefLoopStore = class {
|
|
10123
|
+
constructor(home, now = Date.now(), io = {}, cfg = DEFAULT_RELIEF_LOOP_CONFIG) {
|
|
10124
|
+
this.home = home;
|
|
10125
|
+
this.io = io;
|
|
10126
|
+
this.cfg = cfg;
|
|
10127
|
+
this.state = loadReliefLoopState(home, now, io, cfg);
|
|
10128
|
+
}
|
|
10129
|
+
home;
|
|
10130
|
+
io;
|
|
10131
|
+
cfg;
|
|
10132
|
+
state;
|
|
10133
|
+
get(key) {
|
|
10134
|
+
return this.state[key];
|
|
10135
|
+
}
|
|
10136
|
+
/** Учесть снятие и сразу сохранить. Возвращает запись после учёта. */
|
|
10137
|
+
recordPause(key, now, delivered) {
|
|
10138
|
+
const entry = recordReliefLoopPause(this.state, key, now, delivered, this.cfg);
|
|
10139
|
+
this.flush();
|
|
10140
|
+
return entry;
|
|
10141
|
+
}
|
|
10142
|
+
/** Пометить, что человека уже позвали — чтобы не звать на каждом тике. */
|
|
10143
|
+
markEscalated(key) {
|
|
10144
|
+
const entry = this.state[key];
|
|
10145
|
+
if (!entry || entry.escalated) return;
|
|
10146
|
+
entry.escalated = true;
|
|
10147
|
+
this.flush();
|
|
10148
|
+
}
|
|
10149
|
+
clear(key) {
|
|
10150
|
+
if (!(key in this.state)) return;
|
|
10151
|
+
clearReliefLoop(this.state, key);
|
|
10152
|
+
this.flush();
|
|
10153
|
+
}
|
|
10154
|
+
flush() {
|
|
10155
|
+
try {
|
|
10156
|
+
saveReliefLoopState(this.home, this.state, this.io);
|
|
10157
|
+
} catch (e) {
|
|
10158
|
+
console.warn("[overmind] \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043F\u0435\u0442\u043B\u0438 \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0438 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C:", e);
|
|
10159
|
+
}
|
|
10160
|
+
}
|
|
10161
|
+
};
|
|
10162
|
+
|
|
9955
10163
|
// src/reliefCost.ts
|
|
9956
10164
|
function median(xs) {
|
|
9957
10165
|
const s = [...xs].sort((a, b) => a - b);
|
|
@@ -10067,16 +10275,16 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
10067
10275
|
}
|
|
10068
10276
|
|
|
10069
10277
|
// src/launcher.ts
|
|
10070
|
-
import { spawn } from "child_process";
|
|
10071
|
-
import { closeSync as
|
|
10278
|
+
import { spawn as spawn2 } from "child_process";
|
|
10279
|
+
import { closeSync as closeSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
|
|
10072
10280
|
import { createRequire } from "module";
|
|
10073
10281
|
import { homedir as homedir5 } from "os";
|
|
10074
|
-
import { dirname as dirname3, join as
|
|
10282
|
+
import { dirname as dirname3, join as join16 } from "path";
|
|
10075
10283
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10076
10284
|
|
|
10077
10285
|
// src/droneHome.ts
|
|
10078
10286
|
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
10079
|
-
import { join as
|
|
10287
|
+
import { join as join15, relative, sep } from "path";
|
|
10080
10288
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
10081
10289
|
function droneHomeDeny(env = process.env) {
|
|
10082
10290
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -10100,20 +10308,20 @@ function buildDroneHome(opts) {
|
|
|
10100
10308
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
10101
10309
|
for (const existing of readdirSync7(droneHome)) {
|
|
10102
10310
|
if (wanted.has(existing)) continue;
|
|
10103
|
-
const p =
|
|
10311
|
+
const p = join15(droneHome, existing);
|
|
10104
10312
|
try {
|
|
10105
10313
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
10106
10314
|
} catch {
|
|
10107
10315
|
}
|
|
10108
10316
|
}
|
|
10109
10317
|
for (const name of wanted) {
|
|
10110
|
-
const
|
|
10318
|
+
const link2 = join15(droneHome, name);
|
|
10111
10319
|
try {
|
|
10112
|
-
lstatSync(
|
|
10320
|
+
lstatSync(link2);
|
|
10113
10321
|
continue;
|
|
10114
10322
|
} catch {
|
|
10115
10323
|
}
|
|
10116
|
-
const target =
|
|
10324
|
+
const target = join15(realHome, name);
|
|
10117
10325
|
let isDir;
|
|
10118
10326
|
try {
|
|
10119
10327
|
isDir = statSync7(target).isDirectory();
|
|
@@ -10121,7 +10329,7 @@ function buildDroneHome(opts) {
|
|
|
10121
10329
|
continue;
|
|
10122
10330
|
}
|
|
10123
10331
|
try {
|
|
10124
|
-
symlinkSync2(target,
|
|
10332
|
+
symlinkSync2(target, link2, isDir ? "junction" : "file");
|
|
10125
10333
|
} catch (e) {
|
|
10126
10334
|
if (e.code !== "EEXIST") throw e;
|
|
10127
10335
|
}
|
|
@@ -10134,7 +10342,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
10134
10342
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
10135
10343
|
];
|
|
10136
10344
|
function realPathEnvPins(realHome) {
|
|
10137
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
10345
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join15(realHome, ...segments)]));
|
|
10138
10346
|
}
|
|
10139
10347
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
10140
10348
|
return [
|
|
@@ -10246,7 +10454,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
10246
10454
|
const env = { ...base3 };
|
|
10247
10455
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
10248
10456
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
10249
|
-
env.TURBO_CACHE_DIR =
|
|
10457
|
+
env.TURBO_CACHE_DIR = join16(worktree, ".turbo", "cache");
|
|
10250
10458
|
}
|
|
10251
10459
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
10252
10460
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -10274,13 +10482,13 @@ var DAEMON_STATE_ENV = [
|
|
|
10274
10482
|
"TASKLESS_TOKEN"
|
|
10275
10483
|
];
|
|
10276
10484
|
function bundledRoleDir(kind) {
|
|
10277
|
-
return
|
|
10485
|
+
return join16(dirname3(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
10278
10486
|
}
|
|
10279
10487
|
function roleDir(worktree, kind) {
|
|
10280
|
-
return
|
|
10488
|
+
return join16(worktree, ".claude", "roles", kind);
|
|
10281
10489
|
}
|
|
10282
10490
|
function roleDirs(worktree, kind) {
|
|
10283
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
10491
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync11(dir));
|
|
10284
10492
|
}
|
|
10285
10493
|
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
10286
10494
|
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
@@ -10296,21 +10504,21 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
10296
10504
|
const m = /^(\d+)\.json/.exec(f);
|
|
10297
10505
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
10298
10506
|
}
|
|
10299
|
-
return
|
|
10507
|
+
return join16(dir, `${next}.json`);
|
|
10300
10508
|
}
|
|
10301
10509
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
10302
10510
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
10303
10511
|
const path = nextRunLogPath(runLogsDir, runLogKeys);
|
|
10304
10512
|
let out;
|
|
10305
10513
|
try {
|
|
10306
|
-
out =
|
|
10514
|
+
out = openSync3(path, "wx");
|
|
10307
10515
|
} catch (e) {
|
|
10308
10516
|
if (e.code !== "EEXIST") throw e;
|
|
10309
10517
|
continue;
|
|
10310
10518
|
}
|
|
10311
10519
|
let err = null;
|
|
10312
10520
|
try {
|
|
10313
|
-
err =
|
|
10521
|
+
err = openSync3(`${path.slice(0, -".json".length)}.err.log`, "w");
|
|
10314
10522
|
} catch {
|
|
10315
10523
|
err = null;
|
|
10316
10524
|
}
|
|
@@ -10379,9 +10587,9 @@ function makeLauncher(cfg) {
|
|
|
10379
10587
|
}
|
|
10380
10588
|
const realHome = cfg.droneIsolation?.realHome ?? homedir5();
|
|
10381
10589
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
10382
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
10590
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync11(join16(cfg.droneIsolation.swarmHome, "token"))) {
|
|
10383
10591
|
console.error(
|
|
10384
|
-
`[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 ${
|
|
10592
|
+
`[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 ${join16(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.`
|
|
10385
10593
|
);
|
|
10386
10594
|
}
|
|
10387
10595
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10419,7 +10627,7 @@ function makeLauncher(cfg) {
|
|
|
10419
10627
|
errFd = fds.err ?? void 0;
|
|
10420
10628
|
stdio = ["ignore", outFd, errFd ?? "ignore"];
|
|
10421
10629
|
}
|
|
10422
|
-
const child =
|
|
10630
|
+
const child = spawn2(cfg.bin ?? plan.bin, plan.args, {
|
|
10423
10631
|
cwd: worktree,
|
|
10424
10632
|
detached: true,
|
|
10425
10633
|
stdio,
|
|
@@ -10430,8 +10638,8 @@ function makeLauncher(cfg) {
|
|
|
10430
10638
|
// они про его собственное состояние, и перебивать их ройным нечем.
|
|
10431
10639
|
env: { ...droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null), ...plan.env }
|
|
10432
10640
|
});
|
|
10433
|
-
if (outFd !== void 0)
|
|
10434
|
-
if (errFd !== void 0)
|
|
10641
|
+
if (outFd !== void 0) closeSync3(outFd);
|
|
10642
|
+
if (errFd !== void 0) closeSync3(errFd);
|
|
10435
10643
|
child.unref();
|
|
10436
10644
|
if (child.pid == null) throw new Error("\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430 (\u043D\u0435\u0442 pid)");
|
|
10437
10645
|
return child.pid;
|
|
@@ -10442,8 +10650,8 @@ function makeLauncher(cfg) {
|
|
|
10442
10650
|
init_procCleanup();
|
|
10443
10651
|
|
|
10444
10652
|
// src/cost.ts
|
|
10445
|
-
import { readFileSync as
|
|
10446
|
-
import { join as
|
|
10653
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync9, renameSync as renameSync9 } from "fs";
|
|
10654
|
+
import { join as join17 } from "path";
|
|
10447
10655
|
init_executors();
|
|
10448
10656
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
10449
10657
|
function unprocessedLogFiles(dir) {
|
|
@@ -10465,10 +10673,10 @@ function accountRunCost(deps, run) {
|
|
|
10465
10673
|
let durationMs = 0;
|
|
10466
10674
|
const processed = [];
|
|
10467
10675
|
for (const f of files) {
|
|
10468
|
-
const full =
|
|
10676
|
+
const full = join17(dir, f);
|
|
10469
10677
|
let raw = null;
|
|
10470
10678
|
try {
|
|
10471
|
-
raw =
|
|
10679
|
+
raw = readFileSync14(full, "utf8");
|
|
10472
10680
|
} catch {
|
|
10473
10681
|
continue;
|
|
10474
10682
|
}
|
|
@@ -10494,7 +10702,7 @@ function accountRunCost(deps, run) {
|
|
|
10494
10702
|
}
|
|
10495
10703
|
for (const full of processed) {
|
|
10496
10704
|
try {
|
|
10497
|
-
|
|
10705
|
+
renameSync9(full, `${full}.done`);
|
|
10498
10706
|
} catch {
|
|
10499
10707
|
}
|
|
10500
10708
|
}
|
|
@@ -10517,8 +10725,8 @@ function makeCostSweeper(deps) {
|
|
|
10517
10725
|
}
|
|
10518
10726
|
|
|
10519
10727
|
// src/runOutcome.ts
|
|
10520
|
-
import { readdirSync as readdirSync10, readFileSync as
|
|
10521
|
-
import { join as
|
|
10728
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync15 } from "fs";
|
|
10729
|
+
import { join as join18 } from "path";
|
|
10522
10730
|
init_executors();
|
|
10523
10731
|
function endedItself(outcome) {
|
|
10524
10732
|
return outcome === "graceful" || outcome === "budget" || outcome === "aborted";
|
|
@@ -10545,7 +10753,7 @@ function readLatestResultRaw(runLogsDir, runLogKeys) {
|
|
|
10545
10753
|
}
|
|
10546
10754
|
if (!latest) return null;
|
|
10547
10755
|
try {
|
|
10548
|
-
return
|
|
10756
|
+
return readFileSync15(join18(dir, latest.name), "utf8");
|
|
10549
10757
|
} catch {
|
|
10550
10758
|
return null;
|
|
10551
10759
|
}
|
|
@@ -10578,28 +10786,28 @@ init_src();
|
|
|
10578
10786
|
init_git();
|
|
10579
10787
|
|
|
10580
10788
|
// src/deps.ts
|
|
10581
|
-
import { existsSync as
|
|
10582
|
-
import { join as
|
|
10789
|
+
import { existsSync as existsSync14, readdirSync as readdirSync12, readFileSync as readFileSync17 } from "fs";
|
|
10790
|
+
import { join as join22 } from "path";
|
|
10583
10791
|
import { execa } from "execa";
|
|
10584
10792
|
|
|
10585
10793
|
// src/prepareLock.ts
|
|
10586
10794
|
import { createHash } from "crypto";
|
|
10587
|
-
import { closeSync as
|
|
10588
|
-
import { join as
|
|
10795
|
+
import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
|
|
10796
|
+
import { join as join20 } from "path";
|
|
10589
10797
|
import { tmpdir } from "os";
|
|
10590
10798
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
10591
10799
|
var POLL_MS = 300;
|
|
10592
10800
|
var STALE_MS = 20 * 6e4;
|
|
10593
10801
|
function machineLockPath(key, dir = tmpdir()) {
|
|
10594
|
-
return
|
|
10802
|
+
return join20(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
10595
10803
|
}
|
|
10596
10804
|
function defaultTryCreate(path, content) {
|
|
10597
10805
|
try {
|
|
10598
|
-
const fd =
|
|
10806
|
+
const fd = openSync4(path, "wx");
|
|
10599
10807
|
try {
|
|
10600
10808
|
writeSync(fd, content);
|
|
10601
10809
|
} finally {
|
|
10602
|
-
|
|
10810
|
+
closeSync4(fd);
|
|
10603
10811
|
}
|
|
10604
10812
|
return true;
|
|
10605
10813
|
} catch {
|
|
@@ -10645,6 +10853,153 @@ async function withMachineLock(key, fn, io = {}) {
|
|
|
10645
10853
|
}
|
|
10646
10854
|
}
|
|
10647
10855
|
|
|
10856
|
+
// src/deps.ts
|
|
10857
|
+
init_config();
|
|
10858
|
+
|
|
10859
|
+
// src/envCache.ts
|
|
10860
|
+
init_config();
|
|
10861
|
+
import { createHash as createHash2 } from "crypto";
|
|
10862
|
+
import { cp, link, lstat, mkdir as mkdir2, readdir, readlink, rename, rm as rm2, stat, symlink, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
10863
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
10864
|
+
import { join as join21 } from "path";
|
|
10865
|
+
function fingerprint(parts) {
|
|
10866
|
+
const h = createHash2("sha256");
|
|
10867
|
+
for (const p of parts) {
|
|
10868
|
+
h.update(p);
|
|
10869
|
+
h.update("\0");
|
|
10870
|
+
}
|
|
10871
|
+
return h.digest("hex").slice(0, 32);
|
|
10872
|
+
}
|
|
10873
|
+
async function cloneTree(src, dst) {
|
|
10874
|
+
await mkdir2(dst, { recursive: true });
|
|
10875
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
10876
|
+
await Promise.all(
|
|
10877
|
+
entries.map(async (e) => {
|
|
10878
|
+
const from = join21(src, e.name);
|
|
10879
|
+
const to = join21(dst, e.name);
|
|
10880
|
+
if (e.isDirectory()) return cloneTree(from, to);
|
|
10881
|
+
if (e.isSymbolicLink()) {
|
|
10882
|
+
return symlink(await readlink(from), to).catch(() => cp(from, to, { recursive: true, verbatimSymlinks: true }));
|
|
10883
|
+
}
|
|
10884
|
+
return link(from, to).catch(() => cp(from, to));
|
|
10885
|
+
})
|
|
10886
|
+
);
|
|
10887
|
+
}
|
|
10888
|
+
function cacheRoot(io) {
|
|
10889
|
+
return io.root ?? ENV_CACHE_DIR;
|
|
10890
|
+
}
|
|
10891
|
+
function snapshotDir(io, key) {
|
|
10892
|
+
return join21(cacheRoot(io), key);
|
|
10893
|
+
}
|
|
10894
|
+
function readManifest(dir) {
|
|
10895
|
+
try {
|
|
10896
|
+
const parsed = JSON.parse(readFileSync16(join21(dir, "manifest.json"), "utf8"));
|
|
10897
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
10898
|
+
const m = parsed;
|
|
10899
|
+
if (typeof m.key !== "string" || !Array.isArray(m.dirs)) return null;
|
|
10900
|
+
const dirs = m.dirs.filter((d) => typeof d === "string" && d.length > 0 && !d.startsWith("/") && !d.includes(".."));
|
|
10901
|
+
if (!dirs.length) return null;
|
|
10902
|
+
return { key: m.key, dirs, createdAt: typeof m.createdAt === "string" ? m.createdAt : "" };
|
|
10903
|
+
} catch {
|
|
10904
|
+
return null;
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10907
|
+
function hasEnvSnapshot(key, io = {}) {
|
|
10908
|
+
const exists = io.exists ?? existsSync13;
|
|
10909
|
+
return exists(join21(snapshotDir(io, key), "manifest.json"));
|
|
10910
|
+
}
|
|
10911
|
+
async function restoreEnvSnapshot(worktree, key, io = {}) {
|
|
10912
|
+
const exists = io.exists ?? existsSync13;
|
|
10913
|
+
const clone = io.clone ?? cloneTree;
|
|
10914
|
+
const dir = snapshotDir(io, key);
|
|
10915
|
+
const manifest = readManifest(dir);
|
|
10916
|
+
if (!manifest) return null;
|
|
10917
|
+
const made = [];
|
|
10918
|
+
try {
|
|
10919
|
+
for (let i = 0; i < manifest.dirs.length; i++) {
|
|
10920
|
+
const rel = manifest.dirs[i] ?? "";
|
|
10921
|
+
const target = join21(worktree, rel);
|
|
10922
|
+
if (exists(target)) continue;
|
|
10923
|
+
await clone(join21(dir, "trees", String(i)), target);
|
|
10924
|
+
made.push(rel);
|
|
10925
|
+
}
|
|
10926
|
+
} catch {
|
|
10927
|
+
for (const rel of made) await rm2(join21(worktree, rel), { recursive: true, force: true }).catch(() => {
|
|
10928
|
+
});
|
|
10929
|
+
return null;
|
|
10930
|
+
}
|
|
10931
|
+
await touch(dir);
|
|
10932
|
+
return made;
|
|
10933
|
+
}
|
|
10934
|
+
async function touch(dir) {
|
|
10935
|
+
const now = /* @__PURE__ */ new Date();
|
|
10936
|
+
await utimes(dir, now, now).catch(() => {
|
|
10937
|
+
});
|
|
10938
|
+
}
|
|
10939
|
+
async function saveEnvSnapshot(worktree, key, dirs, io = {}) {
|
|
10940
|
+
const exists = io.exists ?? existsSync13;
|
|
10941
|
+
const clone = io.clone ?? cloneTree;
|
|
10942
|
+
if (hasEnvSnapshot(key, io)) return true;
|
|
10943
|
+
const present = dirs.filter((rel) => exists(join21(worktree, rel)));
|
|
10944
|
+
if (!present.length) return false;
|
|
10945
|
+
const root = cacheRoot(io);
|
|
10946
|
+
const tmp = join21(root, `.tmp-${key}-${process.pid}`);
|
|
10947
|
+
try {
|
|
10948
|
+
await rm2(tmp, { recursive: true, force: true });
|
|
10949
|
+
await mkdir2(join21(tmp, "trees"), { recursive: true });
|
|
10950
|
+
for (let i = 0; i < present.length; i++) await clone(join21(worktree, present[i] ?? ""), join21(tmp, "trees", String(i)));
|
|
10951
|
+
const manifest = { key, dirs: present, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10952
|
+
await writeFile2(join21(tmp, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
10953
|
+
await rename(tmp, snapshotDir(io, key));
|
|
10954
|
+
return true;
|
|
10955
|
+
} catch {
|
|
10956
|
+
await rm2(tmp, { recursive: true, force: true }).catch(() => {
|
|
10957
|
+
});
|
|
10958
|
+
return hasEnvSnapshot(key, io);
|
|
10959
|
+
}
|
|
10960
|
+
}
|
|
10961
|
+
async function pruneEnvSnapshots(io = {}) {
|
|
10962
|
+
const root = cacheRoot(io);
|
|
10963
|
+
const keep = io.keep ?? ENV_CACHE_KEEP;
|
|
10964
|
+
let names;
|
|
10965
|
+
try {
|
|
10966
|
+
names = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
10967
|
+
} catch {
|
|
10968
|
+
return 0;
|
|
10969
|
+
}
|
|
10970
|
+
const withTime = [];
|
|
10971
|
+
for (const name of names) {
|
|
10972
|
+
const s = await stat(join21(root, name)).catch(() => null);
|
|
10973
|
+
if (s) withTime.push({ name, mtime: s.mtimeMs });
|
|
10974
|
+
}
|
|
10975
|
+
withTime.sort((a, b) => b.mtime - a.mtime);
|
|
10976
|
+
let removed = 0;
|
|
10977
|
+
for (const { name } of withTime.slice(Math.max(0, keep))) {
|
|
10978
|
+
await rm2(join21(root, name), { recursive: true, force: true }).catch(() => {
|
|
10979
|
+
});
|
|
10980
|
+
removed++;
|
|
10981
|
+
}
|
|
10982
|
+
return removed;
|
|
10983
|
+
}
|
|
10984
|
+
async function sweepEnvCacheTemp(io = {}) {
|
|
10985
|
+
const root = cacheRoot(io);
|
|
10986
|
+
let names;
|
|
10987
|
+
try {
|
|
10988
|
+
names = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory() && e.name.startsWith(".tmp-")).map((e) => e.name);
|
|
10989
|
+
} catch {
|
|
10990
|
+
return 0;
|
|
10991
|
+
}
|
|
10992
|
+
let removed = 0;
|
|
10993
|
+
for (const name of names) {
|
|
10994
|
+
const s = await lstat(join21(root, name)).catch(() => null);
|
|
10995
|
+
if (!s || Date.now() - s.mtimeMs < 60 * 6e4) continue;
|
|
10996
|
+
await rm2(join21(root, name), { recursive: true, force: true }).catch(() => {
|
|
10997
|
+
});
|
|
10998
|
+
removed++;
|
|
10999
|
+
}
|
|
11000
|
+
return removed;
|
|
11001
|
+
}
|
|
11002
|
+
|
|
10648
11003
|
// src/deps.ts
|
|
10649
11004
|
var PREPARE_TIMEOUT_MS = 15 * 6e4;
|
|
10650
11005
|
var MAX_PLAN_COMMANDS = 12;
|
|
@@ -10665,7 +11020,7 @@ async function runCommand(cmd, worktree) {
|
|
|
10665
11020
|
}
|
|
10666
11021
|
function safeReadText(p) {
|
|
10667
11022
|
try {
|
|
10668
|
-
return
|
|
11023
|
+
return readFileSync17(p, "utf8");
|
|
10669
11024
|
} catch {
|
|
10670
11025
|
return null;
|
|
10671
11026
|
}
|
|
@@ -10705,21 +11060,21 @@ function overridePlan(override) {
|
|
|
10705
11060
|
};
|
|
10706
11061
|
}
|
|
10707
11062
|
function detectPackageManager(worktree, io = {}) {
|
|
10708
|
-
const exists = io.exists ??
|
|
11063
|
+
const exists = io.exists ?? existsSync14;
|
|
10709
11064
|
const readText = io.readText ?? safeReadText;
|
|
10710
|
-
const hasPkgJson = exists(
|
|
11065
|
+
const hasPkgJson = exists(join22(worktree, "package.json"));
|
|
10711
11066
|
if (!hasPkgJson) {
|
|
10712
|
-
if (exists(
|
|
11067
|
+
if (exists(join22(worktree, "deno.json")) || exists(join22(worktree, "deno.jsonc")) || exists(join22(worktree, "deno.lock"))) return "deno";
|
|
10713
11068
|
return null;
|
|
10714
11069
|
}
|
|
10715
|
-
const pkg = readJson(readText,
|
|
11070
|
+
const pkg = readJson(readText, join22(worktree, "package.json"));
|
|
10716
11071
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
10717
11072
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
10718
|
-
if (exists(
|
|
10719
|
-
if (exists(
|
|
10720
|
-
if (exists(
|
|
10721
|
-
if (exists(
|
|
10722
|
-
if (exists(
|
|
11073
|
+
if (exists(join22(worktree, "bun.lock")) || exists(join22(worktree, "bun.lockb"))) return "bun";
|
|
11074
|
+
if (exists(join22(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
11075
|
+
if (exists(join22(worktree, "yarn.lock"))) return "yarn";
|
|
11076
|
+
if (exists(join22(worktree, "package-lock.json"))) return "npm";
|
|
11077
|
+
if (exists(join22(worktree, "deno.lock"))) return "deno";
|
|
10723
11078
|
return "npm";
|
|
10724
11079
|
}
|
|
10725
11080
|
function jsInstallCommand(pm) {
|
|
@@ -10775,7 +11130,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
10775
11130
|
// конфигурация — дело сборки, не подготовки
|
|
10776
11131
|
];
|
|
10777
11132
|
function detectPlan(worktree, io) {
|
|
10778
|
-
const exists = io.exists ??
|
|
11133
|
+
const exists = io.exists ?? existsSync14;
|
|
10779
11134
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
10780
11135
|
const pm = detectPackageManager(worktree, io);
|
|
10781
11136
|
if (pm) {
|
|
@@ -10783,7 +11138,7 @@ function detectPlan(worktree, io) {
|
|
|
10783
11138
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
10784
11139
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
10785
11140
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
10786
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
11141
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join22(worktree, "node_modules"), source: "detect" }
|
|
10787
11142
|
];
|
|
10788
11143
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
10789
11144
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -10792,12 +11147,12 @@ function detectPlan(worktree, io) {
|
|
|
10792
11147
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
10793
11148
|
}
|
|
10794
11149
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
10795
|
-
const hit = marker.files.some((f) => exists(
|
|
11150
|
+
const hit = marker.files.some((f) => exists(join22(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
10796
11151
|
if (!hit) continue;
|
|
10797
11152
|
let ecosystem = marker.ecosystem;
|
|
10798
11153
|
let command = marker.command;
|
|
10799
11154
|
for (const refine of marker.refine ?? []) {
|
|
10800
|
-
if (!exists(
|
|
11155
|
+
if (!exists(join22(worktree, refine.file))) continue;
|
|
10801
11156
|
ecosystem = refine.ecosystem;
|
|
10802
11157
|
command = refine.command;
|
|
10803
11158
|
break;
|
|
@@ -10854,12 +11209,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
10854
11209
|
function ciWorkflowPlan(worktree, io) {
|
|
10855
11210
|
const readText = io.readText ?? safeReadText;
|
|
10856
11211
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
10857
|
-
const dir =
|
|
11212
|
+
const dir = join22(worktree, ".github", "workflows");
|
|
10858
11213
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
10859
11214
|
const seen = /* @__PURE__ */ new Set();
|
|
10860
11215
|
const commands = [];
|
|
10861
11216
|
for (const name of files.sort()) {
|
|
10862
|
-
const yaml = readText(
|
|
11217
|
+
const yaml = readText(join22(dir, name));
|
|
10863
11218
|
if (yaml === null) continue;
|
|
10864
11219
|
for (const raw of workflowRunLines(yaml)) {
|
|
10865
11220
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -10874,7 +11229,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
10874
11229
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
10875
11230
|
}
|
|
10876
11231
|
function workspaceGlobs(worktree, readText) {
|
|
10877
|
-
const yaml = readText(
|
|
11232
|
+
const yaml = readText(join22(worktree, "pnpm-workspace.yaml"));
|
|
10878
11233
|
if (yaml !== null) {
|
|
10879
11234
|
const globs = [];
|
|
10880
11235
|
let inPackages = false;
|
|
@@ -10894,7 +11249,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
10894
11249
|
}
|
|
10895
11250
|
if (globs.length) return globs;
|
|
10896
11251
|
}
|
|
10897
|
-
const pkg = readJson(readText,
|
|
11252
|
+
const pkg = readJson(readText, join22(worktree, "package.json"));
|
|
10898
11253
|
const ws = pkg?.workspaces;
|
|
10899
11254
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
10900
11255
|
return list.filter((g) => typeof g === "string");
|
|
@@ -10905,14 +11260,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
10905
11260
|
if (glob.startsWith("!")) continue;
|
|
10906
11261
|
const star = glob.indexOf("*");
|
|
10907
11262
|
if (star === -1) {
|
|
10908
|
-
const dir =
|
|
10909
|
-
if (io.exists(
|
|
11263
|
+
const dir = join22(worktree, glob);
|
|
11264
|
+
if (io.exists(join22(dir, "package.json"))) dirs.add(dir);
|
|
10910
11265
|
continue;
|
|
10911
11266
|
}
|
|
10912
|
-
const base3 =
|
|
11267
|
+
const base3 = join22(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
10913
11268
|
for (const name of io.listDirs(base3)) {
|
|
10914
|
-
const dir =
|
|
10915
|
-
if (io.exists(
|
|
11269
|
+
const dir = join22(base3, name);
|
|
11270
|
+
if (io.exists(join22(dir, "package.json"))) dirs.add(dir);
|
|
10916
11271
|
}
|
|
10917
11272
|
}
|
|
10918
11273
|
return [...dirs];
|
|
@@ -10933,18 +11288,18 @@ function entryPaths(pkg) {
|
|
|
10933
11288
|
return out;
|
|
10934
11289
|
}
|
|
10935
11290
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
10936
|
-
const exists = io.exists ??
|
|
11291
|
+
const exists = io.exists ?? existsSync14;
|
|
10937
11292
|
const readText = io.readText ?? safeReadText;
|
|
10938
11293
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
10939
11294
|
const globs = workspaceGlobs(worktree, readText);
|
|
10940
11295
|
if (!globs.length) return [];
|
|
10941
11296
|
const pkgs = [];
|
|
10942
11297
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
10943
|
-
const pkg = readJson(readText,
|
|
11298
|
+
const pkg = readJson(readText, join22(dir, "package.json"));
|
|
10944
11299
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
10945
11300
|
const scripts = pkg.scripts ?? {};
|
|
10946
11301
|
const entries = entryPaths(pkg);
|
|
10947
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
11302
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join22(dir, e)));
|
|
10948
11303
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
10949
11304
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
10950
11305
|
}
|
|
@@ -10972,7 +11327,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
10972
11327
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
10973
11328
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
10974
11329
|
for (const file of [".env", ".env.example"]) {
|
|
10975
|
-
const raw = readText(
|
|
11330
|
+
const raw = readText(join22(worktree, file));
|
|
10976
11331
|
if (raw === null) continue;
|
|
10977
11332
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
10978
11333
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -10988,11 +11343,11 @@ function isLocalDatabase(url) {
|
|
|
10988
11343
|
}
|
|
10989
11344
|
}
|
|
10990
11345
|
function prismaPackages(worktree, io) {
|
|
10991
|
-
const exists = io.exists ??
|
|
11346
|
+
const exists = io.exists ?? existsSync14;
|
|
10992
11347
|
const readText = io.readText ?? safeReadText;
|
|
10993
11348
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
10994
11349
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
10995
|
-
return dirs.filter((dir) => exists(
|
|
11350
|
+
return dirs.filter((dir) => exists(join22(dir, "prisma", "schema.prisma")) && listDirs(join22(dir, "prisma", "migrations")).length > 0);
|
|
10996
11351
|
}
|
|
10997
11352
|
function jsExecBin(pm, command) {
|
|
10998
11353
|
switch (pm) {
|
|
@@ -11019,26 +11374,59 @@ function migrateCommands(worktree, pm, io) {
|
|
|
11019
11374
|
source: "detect"
|
|
11020
11375
|
}));
|
|
11021
11376
|
}
|
|
11377
|
+
var ENV_DEFINING_FILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock", "pnpm-workspace.yaml"];
|
|
11378
|
+
function envCachePlan(worktree, io = {}) {
|
|
11379
|
+
if (!ENV_CACHE_ENABLED) return null;
|
|
11380
|
+
const exists = io.exists ?? existsSync14;
|
|
11381
|
+
const readText = io.readText ?? safeReadText;
|
|
11382
|
+
const listDirs = io.listDirs ?? safeListDirs;
|
|
11383
|
+
const pm = detectPackageManager(worktree, io);
|
|
11384
|
+
if (!pm) return null;
|
|
11385
|
+
const parts = [`pm:${pm}`];
|
|
11386
|
+
for (const name of ENV_DEFINING_FILES) {
|
|
11387
|
+
const raw = readText(join22(worktree, name));
|
|
11388
|
+
if (raw !== null) parts.push(`${name}
|
|
11389
|
+
${raw}`);
|
|
11390
|
+
}
|
|
11391
|
+
const pkgDirs = workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs });
|
|
11392
|
+
const rels = pkgDirs.map((dir) => dir.slice(worktree.length + 1)).sort();
|
|
11393
|
+
for (const rel of ["", ...rels]) {
|
|
11394
|
+
const raw = readText(join22(worktree, rel, "package.json"));
|
|
11395
|
+
if (raw !== null) parts.push(`${rel}/package.json
|
|
11396
|
+
${raw}`);
|
|
11397
|
+
}
|
|
11398
|
+
return { key: fingerprint(parts), dirs: ["node_modules", ...rels.map((rel) => join22(rel, "node_modules"))] };
|
|
11399
|
+
}
|
|
11022
11400
|
function planPrepare(worktree, io = {}) {
|
|
11023
11401
|
const plan = overridePlan(io.override) ?? detectPlan(worktree, io) ?? ciWorkflowPlan(worktree, io);
|
|
11024
11402
|
if (!plan) return { source: "none", ecosystem: null, commands: [] };
|
|
11025
11403
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
11026
11404
|
}
|
|
11027
11405
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
11028
|
-
const exists = io.exists ??
|
|
11406
|
+
const exists = io.exists ?? existsSync14;
|
|
11029
11407
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
11030
11408
|
const approve = io.approve ?? (() => true);
|
|
11031
|
-
const
|
|
11409
|
+
const cachePlan = envCachePlan(worktree, io);
|
|
11410
|
+
const cacheIO = io.envCache ?? {};
|
|
11032
11411
|
const report = {
|
|
11033
|
-
ecosystem:
|
|
11034
|
-
source:
|
|
11035
|
-
outcome:
|
|
11412
|
+
ecosystem: null,
|
|
11413
|
+
source: "none",
|
|
11414
|
+
outcome: "not-detected",
|
|
11036
11415
|
ran: [],
|
|
11037
11416
|
failed: [],
|
|
11038
11417
|
skipped: [],
|
|
11039
11418
|
blocked: [],
|
|
11040
|
-
warned: []
|
|
11419
|
+
warned: [],
|
|
11420
|
+
restored: [],
|
|
11421
|
+
cached: false
|
|
11041
11422
|
};
|
|
11423
|
+
if (cachePlan && !exists(join22(worktree, "node_modules"))) {
|
|
11424
|
+
report.restored = await restoreEnvSnapshot(worktree, cachePlan.key, cacheIO) ?? [];
|
|
11425
|
+
}
|
|
11426
|
+
const plan = planPrepare(worktree, io);
|
|
11427
|
+
report.ecosystem = plan.ecosystem;
|
|
11428
|
+
report.source = plan.source;
|
|
11429
|
+
report.outcome = plan.source === "none" ? "not-detected" : "nothing-to-do";
|
|
11042
11430
|
for (const cmd of plan.commands) {
|
|
11043
11431
|
if (cmd.skipIfExists && exists(cmd.skipIfExists)) {
|
|
11044
11432
|
report.skipped.push(cmd.label);
|
|
@@ -11060,11 +11448,20 @@ async function ensureWorktreeDeps(worktree, io = {}) {
|
|
|
11060
11448
|
break;
|
|
11061
11449
|
}
|
|
11062
11450
|
}
|
|
11451
|
+
if (cachePlan && !report.failed.length && !report.blocked.length) {
|
|
11452
|
+
report.cached = await saveEnvSnapshot(worktree, cachePlan.key, cachePlan.dirs, cacheIO);
|
|
11453
|
+
if (report.cached) {
|
|
11454
|
+
await pruneEnvSnapshots(cacheIO);
|
|
11455
|
+
await sweepEnvCacheTemp(cacheIO);
|
|
11456
|
+
}
|
|
11457
|
+
}
|
|
11063
11458
|
return report;
|
|
11064
11459
|
}
|
|
11065
11460
|
function depsEvents(report) {
|
|
11066
11461
|
const events = [];
|
|
11067
11462
|
const where = report.source === "none" ? "" : ` (${[report.ecosystem, `\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A: ${sourceLabel(report.source)}`].filter(Boolean).join(", ")})`;
|
|
11463
|
+
if (report.restored.length)
|
|
11464
|
+
events.push(`\u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0432\u0437\u044F\u0442\u043E \u0438\u0437 \u043C\u0430\u0448\u0438\u043D\u043D\u043E\u0433\u043E \u043A\u044D\u0448\u0430, \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043D\u0435 \u043F\u043E\u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043B\u0430\u0441\u044C: ${report.restored.length} \u043A\u0430\u0442. node_modules \u2014 TASWM-519`);
|
|
11068
11465
|
if (report.ran.length) events.push(`\u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 worktree${where}: ${report.ran.join(", ")} \u2014 TASWM-312`);
|
|
11069
11466
|
if (report.failed.length)
|
|
11070
11467
|
events.push(
|
|
@@ -11293,20 +11690,256 @@ function preStartAttempts(runs, taskSlug, opts = {}) {
|
|
|
11293
11690
|
}
|
|
11294
11691
|
return n;
|
|
11295
11692
|
}
|
|
11296
|
-
function lastPreStartAttempt(runs, taskSlug) {
|
|
11297
|
-
return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort(newestFirst)[0] ?? null;
|
|
11693
|
+
function lastPreStartAttempt(runs, taskSlug) {
|
|
11694
|
+
return runs.filter((r) => r.task_slug === taskSlug && isPreStartFailure(r)).sort(newestFirst)[0] ?? null;
|
|
11695
|
+
}
|
|
11696
|
+
function unresolvedRepoReason(repo, served) {
|
|
11697
|
+
const raw = repo.trim();
|
|
11698
|
+
if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
|
|
11699
|
+
const list = served.length ? served.join(", ") : "\u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E (\u0440\u0435\u0435\u0441\u0442\u0440 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u043F\u0443\u0441\u0442)";
|
|
11700
|
+
return `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 ${raw} \u043D\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u043B\u0441\u044F \u0432 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043F\u0443\u0442\u044C: \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0435\u0433\u043E \u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442. \u041C\u0430\u0448\u0438\u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u0435\u0442: ${list}. \u041A\u043B\u043E\u043D\u0438\u0440\u0443\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u0434\u043E\u0431\u0430\u0432\u044C \u0435\u0433\u043E \u0432 SWARM_REPO_PATHS (\u0438\u043B\u0438 swarm start --repo <\u043F\u0443\u0442\u044C>), \u043B\u0438\u0431\u043E \u043E\u0442\u0434\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u043C\u0430\u0448\u0438\u043D\u0435, \u0433\u0434\u0435 \u043E\u043D \u0435\u0441\u0442\u044C.`;
|
|
11701
|
+
}
|
|
11702
|
+
function parkReason(attempts, reason) {
|
|
11703
|
+
return `${PRE_START_MARK} ${attempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u043F\u043E \u043A\u0440\u0443\u0433\u0443. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
|
|
11704
|
+
}
|
|
11705
|
+
function parkReasonUnretriable(reason) {
|
|
11706
|
+
return `${PRE_START_MARK}, \u0438 ${UNRETRIABLE_PARK_MARK} \u2014 ${SPAWN_PARK_MARK} \u0441\u0440\u0430\u0437\u0443, \u043D\u0435 \u0442\u0440\u0430\u0442\u044F ${PRE_START_ATTEMPT_LIMIT} \u043F\u043E\u043F\u044B\u0442\u043A\u0438. \u0427\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E: ${reason}`;
|
|
11707
|
+
}
|
|
11708
|
+
|
|
11709
|
+
// package.json
|
|
11710
|
+
var package_default = {
|
|
11711
|
+
name: "@taskless-app/swarm",
|
|
11712
|
+
version: "0.6.33",
|
|
11713
|
+
type: "module",
|
|
11714
|
+
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
11715
|
+
keywords: [
|
|
11716
|
+
"ai-agents",
|
|
11717
|
+
"coding-agents",
|
|
11718
|
+
"swarm",
|
|
11719
|
+
"orchestration",
|
|
11720
|
+
"autonomous-agents",
|
|
11721
|
+
"mcp",
|
|
11722
|
+
"taskless",
|
|
11723
|
+
"cli"
|
|
11724
|
+
],
|
|
11725
|
+
license: "SEE LICENSE IN LICENSE",
|
|
11726
|
+
author: "Ilya Smurov <ilya@smurov.com>",
|
|
11727
|
+
homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
|
|
11728
|
+
repository: {
|
|
11729
|
+
type: "git",
|
|
11730
|
+
url: "git+https://github.com/ilyasmurov/taskless.git",
|
|
11731
|
+
directory: "packages/swarm-daemon"
|
|
11732
|
+
},
|
|
11733
|
+
engines: {
|
|
11734
|
+
node: ">=22"
|
|
11735
|
+
},
|
|
11736
|
+
bin: {
|
|
11737
|
+
swarm: "dist/cli.js"
|
|
11738
|
+
},
|
|
11739
|
+
files: [
|
|
11740
|
+
"dist",
|
|
11741
|
+
"docs/HERO.md",
|
|
11742
|
+
"hooks",
|
|
11743
|
+
"roles"
|
|
11744
|
+
],
|
|
11745
|
+
scripts: {
|
|
11746
|
+
overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
|
|
11747
|
+
swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
|
|
11748
|
+
build: "tsup",
|
|
11749
|
+
start: "node dist/overmind.js",
|
|
11750
|
+
prepublishOnly: "pnpm run build",
|
|
11751
|
+
test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
11752
|
+
"test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
11753
|
+
"test:watch": "vitest",
|
|
11754
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
11755
|
+
lint: "echo 'no lint in swarm-daemon yet'"
|
|
11756
|
+
},
|
|
11757
|
+
dependencies: {
|
|
11758
|
+
"@hono/node-server": "^2.0.12",
|
|
11759
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
11760
|
+
execa: "^10.0.0",
|
|
11761
|
+
zod: "3.25.76"
|
|
11762
|
+
},
|
|
11763
|
+
gitHead: "7b17b06b041af953afde46fbd41fc68ebc6dc4e9"
|
|
11764
|
+
};
|
|
11765
|
+
|
|
11766
|
+
// src/version.ts
|
|
11767
|
+
var PACKAGE_VERSION = package_default.version;
|
|
11768
|
+
var MIN_NODE_MAJOR = Number(/(\d+)/.exec(package_default.engines?.node ?? "")?.[1] ?? 0);
|
|
11769
|
+
|
|
11770
|
+
// src/release.ts
|
|
11771
|
+
init_src();
|
|
11772
|
+
init_claudeBin();
|
|
11773
|
+
var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
11774
|
+
var LATEST_TIMEOUT_MS = 4e3;
|
|
11775
|
+
async function fetchLatestVersion(opts = {}) {
|
|
11776
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
11777
|
+
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
11778
|
+
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
11779
|
+
const ctrl = new AbortController();
|
|
11780
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
11781
|
+
try {
|
|
11782
|
+
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
11783
|
+
signal: ctrl.signal,
|
|
11784
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
11785
|
+
});
|
|
11786
|
+
if (!res.ok) return null;
|
|
11787
|
+
const body = await res.json();
|
|
11788
|
+
return typeof body.version === "string" && body.version ? body.version : null;
|
|
11789
|
+
} catch {
|
|
11790
|
+
return null;
|
|
11791
|
+
} finally {
|
|
11792
|
+
clearTimeout(timer);
|
|
11793
|
+
}
|
|
11794
|
+
}
|
|
11795
|
+
function isOutdated(current, latest) {
|
|
11796
|
+
if (!latest || !current) return false;
|
|
11797
|
+
return compareVersionDirs(latest, current) > 0;
|
|
11798
|
+
}
|
|
11799
|
+
function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
11800
|
+
if (!isOutdated(current, latest)) return null;
|
|
11801
|
+
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
11802
|
+
}
|
|
11803
|
+
|
|
11804
|
+
// src/daemonVersion.ts
|
|
11805
|
+
function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
|
|
11806
|
+
return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
|
|
11807
|
+
}
|
|
11808
|
+
var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
|
|
11809
|
+
async function checkDaemonVersion(start, opts = {}) {
|
|
11810
|
+
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
11811
|
+
const fetchLatest = opts.latest ?? (() => fetchLatestVersion());
|
|
11812
|
+
const latestVersion = await fetchLatest().catch(() => null);
|
|
11813
|
+
return {
|
|
11814
|
+
startedAt: start.startedAt,
|
|
11815
|
+
version: start.version,
|
|
11816
|
+
latestVersion,
|
|
11817
|
+
stale: isOutdated(start.version, latestVersion),
|
|
11818
|
+
checkedAt: now().toISOString()
|
|
11819
|
+
};
|
|
11820
|
+
}
|
|
11821
|
+
|
|
11822
|
+
// src/escalations.ts
|
|
11823
|
+
var RESOLVE_ESCALATION_MARK = "\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D";
|
|
11824
|
+
var REVIEW_ESCALATION_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C\u0441\u044F";
|
|
11825
|
+
var REVIEW_UNRECOVERABLE_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u044C";
|
|
11826
|
+
function reviewUnrecoverableMark(branch) {
|
|
11827
|
+
return `${REVIEW_UNRECOVERABLE_MARK} (\u0432\u0435\u0442\u043A\u0430 ${branch})`;
|
|
11298
11828
|
}
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11829
|
+
var REVIEW_CYCLE_ESCALATION_MARK = "\u0440\u0435-\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u043E\u0441\u044C";
|
|
11830
|
+
var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u0441\u044C";
|
|
11831
|
+
var CHANGES_WAKE_ESCALATION_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C";
|
|
11832
|
+
var REVIEW_BUDGET_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u043E\u0441\u044C \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442";
|
|
11833
|
+
function reviewBudgetMark(cycle) {
|
|
11834
|
+
return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
|
|
11304
11835
|
}
|
|
11305
|
-
|
|
11306
|
-
|
|
11836
|
+
var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
|
|
11837
|
+
var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
|
|
11838
|
+
var CI_WAIT_ESCALATION_MARK = "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 CI \u0437\u0430\u0442\u044F\u043D\u0443\u043B\u043E\u0441\u044C";
|
|
11839
|
+
var RELIEF_LOOP_ESCALATION_MARK = "\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u0430\u0441\u044C";
|
|
11840
|
+
var ESCALATIONS = [
|
|
11841
|
+
{
|
|
11842
|
+
mark: SELF_REFERENCE_MARK,
|
|
11843
|
+
// Надзора тут нет намеренно: у self-reference свой канал доставки (watchdog-нотификация
|
|
11844
|
+
// на месте записи), и вход в NEEDS_HUMAN_MARKS задвоил бы сигнал. Ровно на этом маркере
|
|
11845
|
+
// wakeRun и запирал дверь: под self-reference попадает ЛЮБАЯ задача роя, трогающая
|
|
11846
|
+
// packages/swarm-daemon или packages/swarm-core, — вердикта не будет никогда.
|
|
11847
|
+
sides: ["reviewer", "author"],
|
|
11848
|
+
why: "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430: \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E, \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442",
|
|
11849
|
+
stopReason: () => `PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 (${DAEMON_CODE_PATHS.join(", ")}) \u2014 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E (TASWM-270): \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0438\u0439 \u0434\u0435\u043C\u043E\u043D \u043A\u0440\u0443\u0442\u0438\u0442\u0441\u044F \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u043C \u043A\u043E\u0434\u0435 \u0438 \u0441\u0432\u043E\u044E \u0436\u0435 \u043F\u0440\u0430\u0432\u043A\u0443 \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
|
|
11850
|
+
},
|
|
11851
|
+
{
|
|
11852
|
+
mark: REVIEW_ESCALATION_MARK,
|
|
11853
|
+
sides: ["reviewer", "author", "supervision"],
|
|
11854
|
+
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E \u0440\u043E\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442",
|
|
11855
|
+
stopReason: (_run, limits) => `\u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B (${limits.reviewSpawn}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
|
|
11856
|
+
},
|
|
11857
|
+
{
|
|
11858
|
+
mark: REVIEW_CYCLE_ESCALATION_MARK,
|
|
11859
|
+
sides: ["reviewer", "author", "supervision"],
|
|
11860
|
+
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0430\u0432\u0442\u043E\u0441\u043F\u0430\u0432\u043D\u043E\u0432 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442",
|
|
11861
|
+
stopReason: (_run, limits) => `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (${limits.reviewCycle}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`
|
|
11862
|
+
},
|
|
11863
|
+
{
|
|
11864
|
+
mark: REVIEW_BUDGET_MARK,
|
|
11865
|
+
sides: ["reviewer", "author", "supervision"],
|
|
11866
|
+
why: "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u043F\u043E\u043F\u044B\u0442\u043A\u0430 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435",
|
|
11867
|
+
scoped: (run) => reviewBudgetMark(run.review_cycle ?? 0),
|
|
11868
|
+
stopReason: () => "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
11869
|
+
},
|
|
11870
|
+
{
|
|
11871
|
+
mark: REVIEW_UNRECOVERABLE_MARK,
|
|
11872
|
+
sides: ["reviewer", "author", "supervision"],
|
|
11873
|
+
why: "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0441\u0434\u0435\u043B\u0430\u0435\u0442 \u0442\u043E\u0442 \u0436\u0435 git-\u0432\u044B\u0437\u043E\u0432 \u0441 \u0442\u0435\u043C \u0436\u0435 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u043C",
|
|
11874
|
+
scoped: (run) => reviewUnrecoverableMark(run.branch || run.task_slug),
|
|
11875
|
+
stopReason: () => "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
11876
|
+
},
|
|
11877
|
+
{
|
|
11878
|
+
mark: CHANGES_WAKE_ESCALATION_MARK,
|
|
11879
|
+
// Проверяющего этот маркер не касается: вердикт-то есть, не поднимается АВТОР.
|
|
11880
|
+
sides: ["author", "supervision"],
|
|
11881
|
+
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0431\u0435\u0441\u043F\u043B\u043E\u0434\u043D\u044B\u0445 \u0444\u0438\u043A\u0441-\u043F\u043E\u0431\u0443\u0434\u043E\u043A \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0430\u0432\u0442\u043E\u0440\u0430 \u0440\u043E\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0431\u0443\u0434\u0438\u0442",
|
|
11882
|
+
scoped: (run) => `${CHANGES_WAKE_ESCALATION_MARK} (\u043A\u0440\u0443\u0433 ${run.review_cycle ?? 0})`
|
|
11883
|
+
},
|
|
11884
|
+
{
|
|
11885
|
+
mark: RESOLVE_ESCALATION_MARK,
|
|
11886
|
+
sides: ["author", "supervision"],
|
|
11887
|
+
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0430\u0432\u0442\u043E-\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440\u043E\u0432 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0434\u0430\u043B\u044C\u0448\u0435 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u0440\u0430\u0437\u0432\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
11888
|
+
},
|
|
11889
|
+
{
|
|
11890
|
+
mark: CI_WAKE_ESCALATION_MARK,
|
|
11891
|
+
sides: ["author", "supervision"],
|
|
11892
|
+
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u0431\u0443\u0434\u043E\u043A \u043F\u043E \u043A\u0440\u0430\u0441\u043D\u043E\u043C\u0443 CI \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0447\u0438\u043D\u0438\u0442\u044C CI \u0441\u0430\u043C"
|
|
11893
|
+
},
|
|
11894
|
+
{
|
|
11895
|
+
mark: VERIFY_ESCALATION_MARK,
|
|
11896
|
+
// Надзора нет: верификация — не ревью, её отсутствие не держит PR (TASWM-344), и в
|
|
11897
|
+
// NEEDS_HUMAN_MARKS этот маркер никогда не входил. Автору дверь открывает: вердикта
|
|
11898
|
+
// верификации у прогона не будет, поднять его может только оператор.
|
|
11899
|
+
sides: ["author"],
|
|
11900
|
+
why: "\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u043F\u043E\u043F\u044B\u0442\u043E\u043A \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0443 PR \u043D\u0435 \u0431\u0443\u0434\u0435\u0442"
|
|
11901
|
+
},
|
|
11902
|
+
{
|
|
11903
|
+
mark: RELIEF_LOOP_ESCALATION_MARK,
|
|
11904
|
+
// Проверяющего это не касается: работы, которую проверять, ещё нет. Автор — да, и это
|
|
11905
|
+
// главное: сторож прогресса (watchdog.decideFreshAction) обязан УВИДЕТЬ, что рой уже
|
|
11906
|
+
// сдался, иначе он поднимет свежего актора в ту же перегрузку — буквально следующий круг
|
|
11907
|
+
// петли, ровно то, что он и делал 27.08. Ручную побудку `swarm wake` этот маркер, как и
|
|
11908
|
+
// остальные author-маркеры, не запрещает — он открывает ей дверь (см. stoppedForHuman).
|
|
11909
|
+
//
|
|
11910
|
+
// Надзора НЕТ намеренно: маркер ложится на прогон в статусе `stalled`, а надзорная труба
|
|
11911
|
+
// (buildEscalationCandidates) ходит только по `listRuns("review")` — запись там была бы
|
|
11912
|
+
// мёртвой. Человека по такому прогону зовёт детектор run_stalled (anomalies.ts), причём
|
|
11913
|
+
// повторный заход по той же задаче он поднимает до уровня «нужен оператор» сам.
|
|
11914
|
+
sides: ["author"],
|
|
11915
|
+
why: "\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043D\u044F\u043B\u0430 \u043F\u0440\u043E\u0433\u043E\u043D N \u0440\u0430\u0437 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0439 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0440\u043E\u0439 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u043B \u0435\u0433\u043E \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C, \u0437\u043E\u0432\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 run_stalled"
|
|
11916
|
+
},
|
|
11917
|
+
{
|
|
11918
|
+
mark: CI_WAIT_ESCALATION_MARK,
|
|
11919
|
+
// Сторон НЕТ — и это решение, а не пропуск. Маркер лежит на прогоне РЕВЬЮЕРА (paused,
|
|
11920
|
+
// AWAITING_CI_REASON), а не на прогоне автора, и человека зовёт сам:
|
|
11921
|
+
// escalateStaleCiWaits пишет аномалию и дёргает watchdog.notify на месте. Вписать его в
|
|
11922
|
+
// author/supervision значило бы задвоить сигнал и поменять поведение — чего задача
|
|
11923
|
+
// прямо просила не делать.
|
|
11924
|
+
sides: [],
|
|
11925
|
+
why: "\u0440\u043E\u0439 \u0417\u041E\u0412\u0401\u0422 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u043D\u043E \u0441\u0430\u043C \u0435\u0449\u0451 \u0436\u0434\u0451\u0442: \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043D\u0435 \u0441\u0434\u0430\u043B\u0441\u044F, \u0430 \u043D\u043E\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044E \u0448\u043B\u0451\u0442 \u0441\u0432\u043E\u0438\u043C \u043A\u0430\u043D\u0430\u043B\u043E\u043C"
|
|
11926
|
+
}
|
|
11927
|
+
];
|
|
11928
|
+
function escalationText(entry, run, side) {
|
|
11929
|
+
return side === "supervision" ? entry.mark : entry.scoped?.(run) ?? entry.mark;
|
|
11307
11930
|
}
|
|
11308
|
-
function
|
|
11309
|
-
return
|
|
11931
|
+
function escalationsFor(side) {
|
|
11932
|
+
return ESCALATIONS.filter((e) => e.sides.includes(side));
|
|
11933
|
+
}
|
|
11934
|
+
function isEscalationText(text, run, side) {
|
|
11935
|
+
return escalationsFor(side).some((e) => text.includes(escalationText(e, run, side)));
|
|
11936
|
+
}
|
|
11937
|
+
function findEscalation(texts, run, side) {
|
|
11938
|
+
for (const entry of escalationsFor(side)) {
|
|
11939
|
+
const needle = escalationText(entry, run, side);
|
|
11940
|
+
if (texts.some((t) => t.includes(needle))) return entry;
|
|
11941
|
+
}
|
|
11942
|
+
return null;
|
|
11310
11943
|
}
|
|
11311
11944
|
|
|
11312
11945
|
// src/worktreeReap.ts
|
|
@@ -11384,7 +12017,12 @@ import { basename as basename3 } from "path";
|
|
|
11384
12017
|
async function runGh(args, timeoutMs, cwd) {
|
|
11385
12018
|
const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
|
|
11386
12019
|
const child = cwd ? $3({ cwd, timeout: timeoutMs })`gh ${args}` : $3({ timeout: timeoutMs })`gh ${args}`;
|
|
11387
|
-
|
|
12020
|
+
try {
|
|
12021
|
+
return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
|
|
12022
|
+
} catch (e) {
|
|
12023
|
+
noteCallFailure("gh", label2, e);
|
|
12024
|
+
throw e;
|
|
12025
|
+
}
|
|
11388
12026
|
}
|
|
11389
12027
|
function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
|
|
11390
12028
|
return (args) => runGh(args, timeoutMs);
|
|
@@ -11598,36 +12236,59 @@ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have
|
|
|
11598
12236
|
function firstLine(s) {
|
|
11599
12237
|
return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
|
|
11600
12238
|
}
|
|
12239
|
+
var GhCallFailedError = class extends Error {
|
|
12240
|
+
failure;
|
|
12241
|
+
constructor(call, failure) {
|
|
12242
|
+
super(`${call}: ${failure.reason}${failure.detail ? ` \u2014 ${failure.detail}` : ""}`);
|
|
12243
|
+
this.name = "GhCallFailedError";
|
|
12244
|
+
this.failure = failure;
|
|
12245
|
+
}
|
|
12246
|
+
};
|
|
11601
12247
|
var realChecksRunner = async (pr) => {
|
|
11602
|
-
const
|
|
11603
|
-
|
|
11604
|
-
"gh",
|
|
11605
|
-
callLabel("gh", ["pr", "checks", pr]),
|
|
11606
|
-
15e3
|
|
11607
|
-
);
|
|
12248
|
+
const label2 = callLabel("gh", ["pr", "checks", pr]);
|
|
12249
|
+
const r = await withHardTimeout($3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`, "gh", label2, 15e3);
|
|
11608
12250
|
const stdout = r.stdout ?? "";
|
|
11609
12251
|
const stderr = r.stderr ?? "";
|
|
12252
|
+
if (isTimedOut(r)) throw new GhCallFailedError(label2, classifyCallFailure(r));
|
|
11610
12253
|
if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
|
|
11611
12254
|
if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
|
|
12255
|
+
if (!stdout.trim()) throw new GhCallFailedError(label2, noteCallFailure("gh", label2, r));
|
|
11612
12256
|
return stdout;
|
|
11613
12257
|
};
|
|
11614
12258
|
async function prCheckRows(pr, run) {
|
|
12259
|
+
let out;
|
|
11615
12260
|
try {
|
|
11616
|
-
|
|
11617
|
-
const rows = JSON.parse(out);
|
|
11618
|
-
if (!Array.isArray(rows)) return null;
|
|
11619
|
-
return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
|
|
12261
|
+
out = await run(pr);
|
|
11620
12262
|
} catch (e) {
|
|
11621
12263
|
if (e instanceof CiAccessDeniedError) return { denied: e.detail };
|
|
11622
|
-
return
|
|
12264
|
+
return { unavailable: e instanceof GhCallFailedError ? e.failure : classifyCallFailure(e) };
|
|
12265
|
+
}
|
|
12266
|
+
let parsed;
|
|
12267
|
+
try {
|
|
12268
|
+
parsed = JSON.parse(out);
|
|
12269
|
+
} catch {
|
|
12270
|
+
return { unavailable: { reason: "unreadable", detail: headOfOutput(out) } };
|
|
11623
12271
|
}
|
|
12272
|
+
if (!Array.isArray(parsed)) return { unavailable: { reason: "unreadable", detail: "\u043E\u0442\u0432\u0435\u0442 gh \u2014 \u043D\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u043A\u043E\u0432" } };
|
|
12273
|
+
return parsed.map((r) => ({
|
|
12274
|
+
name: r.name ?? "?",
|
|
12275
|
+
bucket: (r.bucket ?? "").toLowerCase(),
|
|
12276
|
+
link: r.link ?? ""
|
|
12277
|
+
}));
|
|
12278
|
+
}
|
|
12279
|
+
function headOfOutput(out) {
|
|
12280
|
+
const head = firstLine(out).slice(0, 120);
|
|
12281
|
+
return head === "gh \u0432\u0435\u0440\u043D\u0443\u043B 403" ? "\u043F\u0443\u0441\u0442\u043E" : head;
|
|
11624
12282
|
}
|
|
11625
12283
|
function isDenied(rows) {
|
|
11626
|
-
return
|
|
12284
|
+
return !Array.isArray(rows) && "denied" in rows;
|
|
12285
|
+
}
|
|
12286
|
+
function isUnavailable(rows) {
|
|
12287
|
+
return !Array.isArray(rows) && "unavailable" in rows;
|
|
11627
12288
|
}
|
|
11628
12289
|
async function ghChecks(pr, run = realChecksRunner) {
|
|
11629
12290
|
const rows = await prCheckRows(pr, run);
|
|
11630
|
-
if (
|
|
12291
|
+
if (isDenied(rows) || isUnavailable(rows) || rows.length === 0) return null;
|
|
11631
12292
|
let pass = 0, fail = 0, pending2 = 0;
|
|
11632
12293
|
for (const r of rows) {
|
|
11633
12294
|
if (r.bucket === "pass") pass++;
|
|
@@ -11651,13 +12312,16 @@ async function ghChecks(pr, run = realChecksRunner) {
|
|
|
11651
12312
|
}
|
|
11652
12313
|
return { bucket, text, tone, total, pass, fail, pending: pending2 };
|
|
11653
12314
|
}
|
|
11654
|
-
function runIdFromLink(
|
|
11655
|
-
const m =
|
|
12315
|
+
function runIdFromLink(link2) {
|
|
12316
|
+
const m = link2.match(/\/actions\/runs\/(\d+)/);
|
|
11656
12317
|
return m ? m[1] : null;
|
|
11657
12318
|
}
|
|
11658
12319
|
async function prCiStatus(pr, run = realChecksRunner) {
|
|
11659
12320
|
const rows = await prCheckRows(pr, run);
|
|
11660
|
-
if (
|
|
12321
|
+
if (isUnavailable(rows)) {
|
|
12322
|
+
const f = rows.unavailable;
|
|
12323
|
+
return { bucket: "unavailable", reason: f.reason, failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, ...f.detail ? { detail: f.detail } : {} };
|
|
12324
|
+
}
|
|
11661
12325
|
if (isDenied(rows)) return { bucket: "denied", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0, detail: rows.denied };
|
|
11662
12326
|
if (rows.length === 0) return { bucket: "none", failing: [], failingRunIds: [], total: 0, pass: 0, pending: 0 };
|
|
11663
12327
|
const total = rows.length;
|
|
@@ -11713,7 +12377,7 @@ init_git();
|
|
|
11713
12377
|
|
|
11714
12378
|
// src/diff.ts
|
|
11715
12379
|
import { statSync as statSync10 } from "fs";
|
|
11716
|
-
import { join as
|
|
12380
|
+
import { join as join27 } from "path";
|
|
11717
12381
|
import { $ as $4 } from "execa";
|
|
11718
12382
|
var MAX_FILES = 200;
|
|
11719
12383
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -11881,7 +12545,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
11881
12545
|
files.push(pf);
|
|
11882
12546
|
}
|
|
11883
12547
|
}
|
|
11884
|
-
files.sort((a, b) => mtime(
|
|
12548
|
+
files.sort((a, b) => mtime(join27(worktree, b.path)) - mtime(join27(worktree, a.path)));
|
|
11885
12549
|
const truncatedFiles = files.length > MAX_FILES;
|
|
11886
12550
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
11887
12551
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -12714,7 +13378,7 @@ ${report}${shots.warning}`,
|
|
|
12714
13378
|
const myKind = opts.runKind ?? (await store.myRun().catch(() => null))?.kind;
|
|
12715
13379
|
if (myKind === "hero") {
|
|
12716
13380
|
const taskless = opts.taskless ?? null;
|
|
12717
|
-
const
|
|
13381
|
+
const errText3 = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
12718
13382
|
let heroCache = null;
|
|
12719
13383
|
const heroRun = async () => heroCache ??= await store.myRun();
|
|
12720
13384
|
let epicCache;
|
|
@@ -12732,14 +13396,14 @@ ${report}${shots.warning}`,
|
|
|
12732
13396
|
estimate_hours: z.number().positive().describe("\u043E\u0446\u0435\u043D\u043A\u0430 \u0432 \u0447\u0430\u0441\u0430\u0445 (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E)")
|
|
12733
13397
|
},
|
|
12734
13398
|
guard(async ({ title, description_md, estimate_hours }) => {
|
|
12735
|
-
if (!taskless) return
|
|
13399
|
+
if (!taskless) return errText3("Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C.");
|
|
12736
13400
|
const hero = await heroRun();
|
|
12737
|
-
if (!hero) return
|
|
13401
|
+
if (!hero) return errText3("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0442\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
|
|
12738
13402
|
const ep = await epic(hero.task_slug);
|
|
12739
|
-
if (!ep) return
|
|
12740
|
-
if (!ep.projectId) return
|
|
13403
|
+
if (!ep) return errText3(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u044D\u043F\u0438\u043A ${hero.task_slug} \u0438\u0437 Taskless \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u0430.`);
|
|
13404
|
+
if (!ep.projectId) return errText3(`\u044D\u043F\u0438\u043A ${hero.task_slug} \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (\u043D\u0443\u0436\u0435\u043D projectId).`);
|
|
12741
13405
|
const statusId = await taskless.backlogStatusId();
|
|
12742
|
-
if (!statusId) return
|
|
13406
|
+
if (!statusId) return errText3("\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0441\u0442\u0430\u0442\u0443\u0441 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438 backlog \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430 \u2014 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u044E (\u0440\u0438\u0441\u043A \u043F\u043E\u043F\u0430\u0441\u0442\u044C \u0441\u0440\u0430\u0437\u0443 \u0432 \xAB\u0421\u0434\u0435\u043B\u0430\u0442\u044C\xBB).");
|
|
12743
13407
|
const footer = `
|
|
12744
13408
|
|
|
12745
13409
|
---
|
|
@@ -12753,7 +13417,7 @@ ${report}${shots.warning}`,
|
|
|
12753
13417
|
projectId: ep.projectId
|
|
12754
13418
|
// TASWM-138: наследуем проект эпика — иначе сирота не claimable
|
|
12755
13419
|
});
|
|
12756
|
-
if (!created) return
|
|
13420
|
+
if (!created) return errText3("Taskless \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u043B \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0443 (\u0441\u043C. \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430).");
|
|
12757
13421
|
await store.addEvent(null, "step", `\u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u0441\u043E\u0437\u0434\u0430\u043D\u0430: ${created.slug}`);
|
|
12758
13422
|
return { content: [{ type: "text", text: created.slug }] };
|
|
12759
13423
|
})
|
|
@@ -12767,18 +13431,18 @@ ${report}${shots.warning}`,
|
|
|
12767
13431
|
after: z.array(z.string()).optional().describe("\u0441\u043B\u0430\u0433\u0438 \u0437\u0430\u0434\u0430\u0447, \u043F\u043E\u0441\u043B\u0435 done \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C (\u043E\u043F\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u043E)")
|
|
12768
13432
|
},
|
|
12769
13433
|
guard(async ({ slug, area, after }) => {
|
|
12770
|
-
if (!taskless) return
|
|
13434
|
+
if (!taskless) return errText3("Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (slug-only \u0440\u0435\u0436\u0438\u043C) \u2014 \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D.");
|
|
12771
13435
|
const hero = await heroRun();
|
|
12772
|
-
if (!hero) return
|
|
13436
|
+
if (!hero) return errText3("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
|
|
12773
13437
|
const read = await taskless.getTaskDetailed(slug);
|
|
12774
|
-
if (!read.ok) return
|
|
13438
|
+
if (!read.ok) return errText3(`\u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 ${slug}: ${describeToolFailure(read.failure)}.`);
|
|
12775
13439
|
const task = read.task;
|
|
12776
13440
|
const ep = await epic(hero.task_slug);
|
|
12777
13441
|
if (!ep || task.parentId !== ep.id) {
|
|
12778
|
-
return
|
|
13442
|
+
return errText3(`${slug} \u2014 \u043D\u0435 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0432\u043E\u0435\u0433\u043E \u044D\u043F\u0438\u043A\u0430 ${hero.task_slug}. \u0414\u0438\u0441\u043F\u0430\u0442\u0447\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u0432\u043E\u0438 \u043F\u043E\u0434\u0437\u0430\u0434\u0430\u0447\u0438.`);
|
|
12779
13443
|
}
|
|
12780
13444
|
if (task.statusCategory !== "unstarted") {
|
|
12781
|
-
return
|
|
13445
|
+
return errText3(
|
|
12782
13446
|
`\u0436\u0434\u0438 \u0430\u043F\u043F\u0440\u0443\u0432\u0430 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430: \u043F\u0435\u0440\u0435\u043D\u043E\u0441 \u0432 \xAB\u0421\u0434\u0435\u043B\u0430\u0442\u044C\xBB. ${slug} \u0441\u0435\u0439\u0447\u0430\u0441 \u0432 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \xAB${task.statusCategory ?? "?"}\xBB, \u0434\u0438\u0441\u043F\u0430\u0442\u0447 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F unstarted (\u043E\u0434\u043E\u0431\u0440\u0435\u043D\u043D\u044B\u0445).`
|
|
12783
13447
|
);
|
|
12784
13448
|
}
|
|
@@ -12800,7 +13464,7 @@ ${report}${shots.warning}`,
|
|
|
12800
13464
|
{},
|
|
12801
13465
|
guard(async () => {
|
|
12802
13466
|
const hero = await heroRun();
|
|
12803
|
-
if (!hero) return
|
|
13467
|
+
if (!hero) return errText3("\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0442\u0432\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u2014 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043E\u0442\u0440\u044F\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 \u043D\u0435 \u0441\u043E\u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043F\u043E\u0437\u0436\u0435.");
|
|
12804
13468
|
const squad = await store.squadRuns();
|
|
12805
13469
|
const qByRun = /* @__PURE__ */ new Map();
|
|
12806
13470
|
for (const q of await store.openQuestionsFor()) {
|
|
@@ -12830,9 +13494,9 @@ ${report}${shots.warning}`,
|
|
|
12830
13494
|
},
|
|
12831
13495
|
guard(async ({ question_id, text }) => {
|
|
12832
13496
|
const mine = (await store.openQuestionsFor()).some((q) => q.id === question_id);
|
|
12833
|
-
if (!mine) return
|
|
13497
|
+
if (!mine) return errText3(`\u0432\u043E\u043F\u0440\u043E\u0441 #${question_id} \u043D\u0435 \u0438\u0437 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0442\u0432\u043E\u0435\u0433\u043E \u043E\u0442\u0440\u044F\u0434\u0430 \u2014 \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F.`);
|
|
12834
13498
|
const answered = await store.answer(question_id, text, "hero");
|
|
12835
|
-
if (!answered || answered.status !== "answered") return
|
|
13499
|
+
if (!answered || answered.status !== "answered") return errText3(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 #${question_id}.`);
|
|
12836
13500
|
return { content: [{ type: "text", text: `\u043E\u0442\u0432\u0435\u0442 \u0434\u0440\u043E\u043D\u0443 (\u0432\u043E\u043F\u0440\u043E\u0441 #${question_id}) \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0434\u0440\u043E\u043D \u0440\u0430\u0437\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D` }] };
|
|
12837
13501
|
})
|
|
12838
13502
|
);
|
|
@@ -12906,7 +13570,7 @@ async function runDroneServer(runId) {
|
|
|
12906
13570
|
|
|
12907
13571
|
// src/droneBridge.ts
|
|
12908
13572
|
init_src();
|
|
12909
|
-
import { createHash as
|
|
13573
|
+
import { createHash as createHash4 } from "crypto";
|
|
12910
13574
|
|
|
12911
13575
|
// src/anomalies.ts
|
|
12912
13576
|
init_src();
|
|
@@ -13437,7 +14101,7 @@ function questionKey(runId, text) {
|
|
|
13437
14101
|
return `${runId} ${text.trim().replace(/\s+/g, " ")}`;
|
|
13438
14102
|
}
|
|
13439
14103
|
function noteKey(runId, text) {
|
|
13440
|
-
return `note:${runId}:${
|
|
14104
|
+
return `note:${runId}:${createHash4("sha1").update(questionKey(runId, text)).digest("hex").slice(0, 16)}`;
|
|
13441
14105
|
}
|
|
13442
14106
|
async function syncDroneChannel(opts) {
|
|
13443
14107
|
const result = { imported: [], answered: [], notes: [] };
|
|
@@ -13663,42 +14327,6 @@ init_authState();
|
|
|
13663
14327
|
|
|
13664
14328
|
// src/swarmStatus.ts
|
|
13665
14329
|
init_linkFailure();
|
|
13666
|
-
|
|
13667
|
-
// src/release.ts
|
|
13668
|
-
init_src();
|
|
13669
|
-
init_claudeBin();
|
|
13670
|
-
var SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
13671
|
-
var LATEST_TIMEOUT_MS = 4e3;
|
|
13672
|
-
async function fetchLatestVersion(opts = {}) {
|
|
13673
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
13674
|
-
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
13675
|
-
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
13676
|
-
const ctrl = new AbortController();
|
|
13677
|
-
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
13678
|
-
try {
|
|
13679
|
-
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
13680
|
-
signal: ctrl.signal,
|
|
13681
|
-
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
13682
|
-
});
|
|
13683
|
-
if (!res.ok) return null;
|
|
13684
|
-
const body = await res.json();
|
|
13685
|
-
return typeof body.version === "string" && body.version ? body.version : null;
|
|
13686
|
-
} catch {
|
|
13687
|
-
return null;
|
|
13688
|
-
} finally {
|
|
13689
|
-
clearTimeout(timer);
|
|
13690
|
-
}
|
|
13691
|
-
}
|
|
13692
|
-
function isOutdated(current, latest) {
|
|
13693
|
-
if (!latest || !current) return false;
|
|
13694
|
-
return compareVersionDirs(latest, current) > 0;
|
|
13695
|
-
}
|
|
13696
|
-
function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
13697
|
-
if (!isOutdated(current, latest)) return null;
|
|
13698
|
-
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
13699
|
-
}
|
|
13700
|
-
|
|
13701
|
-
// src/swarmStatus.ts
|
|
13702
14330
|
init_src();
|
|
13703
14331
|
function classifyLinkError(e) {
|
|
13704
14332
|
const { kind, detail } = explainLinkFailure(e);
|
|
@@ -14000,22 +14628,22 @@ function makeMergeGateParts(deps) {
|
|
|
14000
14628
|
}
|
|
14001
14629
|
|
|
14002
14630
|
// src/envGate.ts
|
|
14003
|
-
import { existsSync as
|
|
14004
|
-
import { join as
|
|
14631
|
+
import { existsSync as existsSync17 } from "fs";
|
|
14632
|
+
import { join as join29 } from "path";
|
|
14005
14633
|
import { createConnection } from "net";
|
|
14006
14634
|
import { execa as execa2 } from "execa";
|
|
14007
14635
|
|
|
14008
14636
|
// src/platform.ts
|
|
14009
|
-
import { existsSync as
|
|
14010
|
-
import { delimiter, join as
|
|
14637
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
|
|
14638
|
+
import { delimiter, join as join28 } from "path";
|
|
14011
14639
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
14012
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
14640
|
+
if (name.includes("/") || name.includes("\\")) return existsSync16(name) ? name : null;
|
|
14013
14641
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
14014
14642
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
14015
14643
|
for (const dir of dirs) {
|
|
14016
14644
|
for (const ext of exts) {
|
|
14017
|
-
const candidate =
|
|
14018
|
-
if (
|
|
14645
|
+
const candidate = join28(dir, name + ext);
|
|
14646
|
+
if (existsSync16(candidate)) return candidate;
|
|
14019
14647
|
}
|
|
14020
14648
|
}
|
|
14021
14649
|
return null;
|
|
@@ -14098,8 +14726,8 @@ var COMPOSE_MARKERS = [
|
|
|
14098
14726
|
"compose.yaml"
|
|
14099
14727
|
];
|
|
14100
14728
|
function detectRequirements(repo, io = {}) {
|
|
14101
|
-
const exists = io.exists ??
|
|
14102
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
14729
|
+
const exists = io.exists ?? existsSync17;
|
|
14730
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join29(repo, f)));
|
|
14103
14731
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
14104
14732
|
}
|
|
14105
14733
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -14233,69 +14861,6 @@ init_src();
|
|
|
14233
14861
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
14234
14862
|
import { basename as basename5 } from "path";
|
|
14235
14863
|
import { serve as serve2 } from "@hono/node-server";
|
|
14236
|
-
|
|
14237
|
-
// package.json
|
|
14238
|
-
var package_default = {
|
|
14239
|
-
name: "@taskless-app/swarm",
|
|
14240
|
-
version: "0.6.31",
|
|
14241
|
-
type: "module",
|
|
14242
|
-
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
14243
|
-
keywords: [
|
|
14244
|
-
"ai-agents",
|
|
14245
|
-
"coding-agents",
|
|
14246
|
-
"swarm",
|
|
14247
|
-
"orchestration",
|
|
14248
|
-
"autonomous-agents",
|
|
14249
|
-
"mcp",
|
|
14250
|
-
"taskless",
|
|
14251
|
-
"cli"
|
|
14252
|
-
],
|
|
14253
|
-
license: "SEE LICENSE IN LICENSE",
|
|
14254
|
-
author: "Ilya Smurov <ilya@smurov.com>",
|
|
14255
|
-
homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
|
|
14256
|
-
repository: {
|
|
14257
|
-
type: "git",
|
|
14258
|
-
url: "git+https://github.com/ilyasmurov/taskless.git",
|
|
14259
|
-
directory: "packages/swarm-daemon"
|
|
14260
|
-
},
|
|
14261
|
-
engines: {
|
|
14262
|
-
node: ">=22"
|
|
14263
|
-
},
|
|
14264
|
-
bin: {
|
|
14265
|
-
swarm: "dist/cli.js"
|
|
14266
|
-
},
|
|
14267
|
-
files: [
|
|
14268
|
-
"dist",
|
|
14269
|
-
"docs/HERO.md",
|
|
14270
|
-
"hooks",
|
|
14271
|
-
"roles"
|
|
14272
|
-
],
|
|
14273
|
-
scripts: {
|
|
14274
|
-
overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
|
|
14275
|
-
swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
|
|
14276
|
-
build: "tsup",
|
|
14277
|
-
start: "node dist/overmind.js",
|
|
14278
|
-
prepublishOnly: "pnpm run build",
|
|
14279
|
-
test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
14280
|
-
"test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
|
|
14281
|
-
"test:watch": "vitest",
|
|
14282
|
-
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
14283
|
-
lint: "echo 'no lint in swarm-daemon yet'"
|
|
14284
|
-
},
|
|
14285
|
-
dependencies: {
|
|
14286
|
-
"@hono/node-server": "^2.0.12",
|
|
14287
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
14288
|
-
execa: "^10.0.0",
|
|
14289
|
-
zod: "3.25.76"
|
|
14290
|
-
},
|
|
14291
|
-
gitHead: "82950eae7a6ad09be4193708257798c811bb6fae"
|
|
14292
|
-
};
|
|
14293
|
-
|
|
14294
|
-
// src/version.ts
|
|
14295
|
-
var PACKAGE_VERSION = package_default.version;
|
|
14296
|
-
var MIN_NODE_MAJOR = Number(/(\d+)/.exec(package_default.engines?.node ?? "")?.[1] ?? 0);
|
|
14297
|
-
|
|
14298
|
-
// src/web.ts
|
|
14299
14864
|
init_storm();
|
|
14300
14865
|
init_resources();
|
|
14301
14866
|
init_config();
|
|
@@ -15903,42 +16468,24 @@ function closeWebServer(server) {
|
|
|
15903
16468
|
});
|
|
15904
16469
|
}
|
|
15905
16470
|
|
|
15906
|
-
// src/daemonVersion.ts
|
|
15907
|
-
function captureDaemonStart(now = () => /* @__PURE__ */ new Date()) {
|
|
15908
|
-
return { startedAt: now().toISOString(), version: PACKAGE_VERSION };
|
|
15909
|
-
}
|
|
15910
|
-
var DAEMON_CODE_PATHS = ["packages/swarm-daemon", "packages/swarm-core"];
|
|
15911
|
-
async function checkDaemonVersion(start, opts = {}) {
|
|
15912
|
-
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
15913
|
-
const fetchLatest = opts.latest ?? (() => fetchLatestVersion());
|
|
15914
|
-
const latestVersion = await fetchLatest().catch(() => null);
|
|
15915
|
-
return {
|
|
15916
|
-
startedAt: start.startedAt,
|
|
15917
|
-
version: start.version,
|
|
15918
|
-
latestVersion,
|
|
15919
|
-
stale: isOutdated(start.version, latestVersion),
|
|
15920
|
-
checkedAt: now().toISOString()
|
|
15921
|
-
};
|
|
15922
|
-
}
|
|
15923
|
-
|
|
15924
16471
|
// src/hostCommands.ts
|
|
15925
16472
|
init_src();
|
|
15926
|
-
import { readFileSync as
|
|
15927
|
-
import { join as
|
|
16473
|
+
import { readFileSync as readFileSync22, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "fs";
|
|
16474
|
+
import { join as join30 } from "path";
|
|
15928
16475
|
function hostCommandStatePath(home) {
|
|
15929
|
-
return
|
|
16476
|
+
return join30(home, "host-command.json");
|
|
15930
16477
|
}
|
|
15931
16478
|
function readState(path) {
|
|
15932
16479
|
try {
|
|
15933
|
-
return JSON.parse(
|
|
16480
|
+
return JSON.parse(readFileSync22(path, "utf8"));
|
|
15934
16481
|
} catch {
|
|
15935
16482
|
return {};
|
|
15936
16483
|
}
|
|
15937
16484
|
}
|
|
15938
16485
|
function writeState(path, state) {
|
|
15939
16486
|
try {
|
|
15940
|
-
|
|
15941
|
-
|
|
16487
|
+
writeFileSync15(`${path}.tmp`, JSON.stringify(state));
|
|
16488
|
+
renameSync10(`${path}.tmp`, path);
|
|
15942
16489
|
} catch {
|
|
15943
16490
|
}
|
|
15944
16491
|
}
|
|
@@ -16100,8 +16647,8 @@ async function doRestart(cmd, opts, note) {
|
|
|
16100
16647
|
|
|
16101
16648
|
// src/selfUpdate.ts
|
|
16102
16649
|
init_git();
|
|
16103
|
-
import { existsSync as
|
|
16104
|
-
import { join as
|
|
16650
|
+
import { existsSync as existsSync18 } from "fs";
|
|
16651
|
+
import { join as join31 } from "path";
|
|
16105
16652
|
import { $ as $5 } from "execa";
|
|
16106
16653
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
16107
16654
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -16114,7 +16661,7 @@ function tail(text, max = 400) {
|
|
|
16114
16661
|
}
|
|
16115
16662
|
async function runSelfUpdate(io) {
|
|
16116
16663
|
const run = io.run ?? defaultRun;
|
|
16117
|
-
const exists = io.exists ??
|
|
16664
|
+
const exists = io.exists ?? existsSync18;
|
|
16118
16665
|
const repo = io.repo;
|
|
16119
16666
|
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
16120
16667
|
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
@@ -16143,7 +16690,7 @@ async function runSelfUpdate(io) {
|
|
|
16143
16690
|
};
|
|
16144
16691
|
}
|
|
16145
16692
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
16146
|
-
if (!exists(
|
|
16693
|
+
if (!exists(join31(repo, "pnpm-lock.yaml"))) {
|
|
16147
16694
|
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" };
|
|
16148
16695
|
}
|
|
16149
16696
|
const installed2 = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -16244,7 +16791,7 @@ function orphanNoteText(run, q) {
|
|
|
16244
16791
|
|
|
16245
16792
|
// src/probe.ts
|
|
16246
16793
|
init_executors();
|
|
16247
|
-
import { spawn as
|
|
16794
|
+
import { spawn as spawn3 } from "child_process";
|
|
16248
16795
|
var RESET_RE = /resets[^\n]*/i;
|
|
16249
16796
|
function executorHealthy(opts = {}) {
|
|
16250
16797
|
const executor = opts.executor ?? resolveExecutor();
|
|
@@ -16263,7 +16810,7 @@ function executorHealthy(opts = {}) {
|
|
|
16263
16810
|
}
|
|
16264
16811
|
resolve4(r);
|
|
16265
16812
|
};
|
|
16266
|
-
const child =
|
|
16813
|
+
const child = spawn3(bin, [...executor.probe.args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
16267
16814
|
child.stdout?.on("data", (d) => {
|
|
16268
16815
|
out += d.toString();
|
|
16269
16816
|
});
|
|
@@ -16302,19 +16849,19 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
16302
16849
|
}
|
|
16303
16850
|
|
|
16304
16851
|
// src/anomalyState.ts
|
|
16305
|
-
import { existsSync as
|
|
16306
|
-
import { join as
|
|
16852
|
+
import { existsSync as existsSync19, readFileSync as readFileSync23, renameSync as renameSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
16853
|
+
import { join as join32 } from "path";
|
|
16307
16854
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
16308
16855
|
var MAX_RESOLVED = 200;
|
|
16309
16856
|
function emptyState() {
|
|
16310
16857
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
16311
16858
|
}
|
|
16312
16859
|
function anomalyStatePath(home) {
|
|
16313
|
-
return
|
|
16860
|
+
return join32(home, "anomaly-state.json");
|
|
16314
16861
|
}
|
|
16315
16862
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
16316
|
-
const exists = io.exists ??
|
|
16317
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
16863
|
+
const exists = io.exists ?? existsSync19;
|
|
16864
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync23(p, "utf8"));
|
|
16318
16865
|
const path = anomalyStatePath(home);
|
|
16319
16866
|
if (!exists(path)) return emptyState();
|
|
16320
16867
|
try {
|
|
@@ -16343,11 +16890,11 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
16343
16890
|
}
|
|
16344
16891
|
function saveAnomalyState(home, state, io = {}) {
|
|
16345
16892
|
const path = anomalyStatePath(home);
|
|
16346
|
-
const
|
|
16347
|
-
|
|
16348
|
-
|
|
16893
|
+
const writeFile4 = io.writeFile ?? ((p, data) => {
|
|
16894
|
+
writeFileSync16(`${p}.tmp`, data);
|
|
16895
|
+
renameSync11(`${p}.tmp`, p);
|
|
16349
16896
|
});
|
|
16350
|
-
|
|
16897
|
+
writeFile4(path, JSON.stringify(state, null, 2));
|
|
16351
16898
|
}
|
|
16352
16899
|
var AnomalyStateStore = class {
|
|
16353
16900
|
constructor(home, now = Date.now(), io = {}) {
|
|
@@ -16499,6 +17046,7 @@ function decideParkAction(db, droneCap, exclusivityBlocked) {
|
|
|
16499
17046
|
function decideFreshAction(db, wt) {
|
|
16500
17047
|
const stalled = db.listRuns("stalled").filter((r) => r.resolves_run_id == null && r.reviews_run_id == null);
|
|
16501
17048
|
for (const r of stalled) {
|
|
17049
|
+
if (findEscalation(db.events(r.id).map((e) => e.text ?? ""), r, "author")) continue;
|
|
16502
17050
|
const path = r.worktree_path;
|
|
16503
17051
|
const recoverable = !!path && wt.exists(path) && wt.hasTranscript(path);
|
|
16504
17052
|
if (recoverable) continue;
|
|
@@ -16814,19 +17362,11 @@ var EXITED_CONTINUE_NUDGE = "\u0422\u0432\u043E\u044F \u043F\u0440\u043E\u0448\u
|
|
|
16814
17362
|
var STORM_DEAD_LIMIT = Number(process.env.SWARM_STORM_DEAD_LIMIT) || 3;
|
|
16815
17363
|
var CONFLICT_RESOLVE_COOLDOWN_MS = Number(process.env.SWARM_CONFLICT_COOLDOWN_MS) || 3 * 60 * 1e3;
|
|
16816
17364
|
var RESOLVE_LIMIT = Number(process.env.SWARM_RESOLVE_LIMIT) || 3;
|
|
16817
|
-
var RESOLVE_ESCALATION_MARK = "\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442 \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D";
|
|
16818
17365
|
var REVIEW_SPAWN_LIMIT = Number(process.env.SWARM_REVIEW_SPAWN_LIMIT) || 3;
|
|
16819
|
-
var REVIEW_ESCALATION_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0441\u043C\u043E\u0433\u043B\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C\u0441\u044F";
|
|
16820
|
-
var REVIEW_UNRECOVERABLE_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u044C";
|
|
16821
|
-
function reviewUnrecoverableMark(branch) {
|
|
16822
|
-
return `${REVIEW_UNRECOVERABLE_MARK} (\u0432\u0435\u0442\u043A\u0430 ${branch})`;
|
|
16823
|
-
}
|
|
16824
17366
|
var REVIEW_CYCLE_LIMIT = Number(process.env.SWARM_REVIEW_CYCLE_LIMIT) || 3;
|
|
16825
|
-
var REVIEW_CYCLE_ESCALATION_MARK = "\u0440\u0435-\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u043E\u0441\u044C";
|
|
16826
17367
|
var CI_WAKE_LIMIT = Number(process.env.SWARM_CI_WAKE_LIMIT) || 3;
|
|
16827
17368
|
var CI_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CI_WAKE_COOLDOWN_MS) || 3 * 60 * 1e3;
|
|
16828
17369
|
var CI_CHECK_COOLDOWN_MS = Number(process.env.SWARM_CI_CHECK_COOLDOWN_MS) || 60 * 1e3;
|
|
16829
|
-
var CI_WAKE_ESCALATION_MARK = "CI-\u0430\u0432\u0442\u043E\u043F\u043E\u0447\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0432\u0438\u043B\u0430\u0441\u044C";
|
|
16830
17370
|
var CI_NO_CHECKS_MARK = "\u0443 PR \u043D\u0435\u0442 \u0447\u0435\u043A\u043E\u0432 \u0438 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442";
|
|
16831
17371
|
var CI_WAIT_POLL_MARK = "\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A";
|
|
16832
17372
|
var CI_POLL_LIMIT_MARK = "\u043E\u043F\u0440\u043E\u0441 CI \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043F\u043E \u043F\u043E\u0442\u043E\u043B\u043A\u0443";
|
|
@@ -16842,34 +17382,19 @@ var CI_POLL_WINDOW_MS = Math.max(
|
|
|
16842
17382
|
Number(process.env.SWARM_CI_POLL_WINDOW_MS) || 90 * 60 * 1e3,
|
|
16843
17383
|
CI_WAIT_LIMIT_MS + CI_CHECK_COOLDOWN_MS
|
|
16844
17384
|
);
|
|
16845
|
-
var CI_WAIT_ESCALATION_MARK = "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 CI \u0437\u0430\u0442\u044F\u043D\u0443\u043B\u043E\u0441\u044C";
|
|
16846
17385
|
var CHANGES_WAKE_COOLDOWN_MS = Number(process.env.SWARM_CHANGES_WAKE_COOLDOWN_MS) || 3 * 60 * 1e3;
|
|
16847
17386
|
var CHANGES_WAKE_LIMIT = Number(process.env.SWARM_CHANGES_WAKE_LIMIT) || 2;
|
|
16848
17387
|
var CHANGES_FRESH_WAKE_AFTER_MS = Number(process.env.SWARM_CHANGES_FRESH_WAKE_AFTER_MS) || SWARM_ANOMALY_PR_NO_REVIEWER_MS;
|
|
16849
17388
|
var CHANGES_WAKE_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB";
|
|
16850
|
-
var CHANGES_WAKE_ESCALATION_MARK = "\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0430\u0441\u044C";
|
|
16851
17389
|
var CHANGES_WAKE_FAILED_MARK = "\u043F\u043E\u0431\u0443\u0434\u043A\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C";
|
|
16852
17390
|
var BUDGET_STOP_REASON = "\u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u0441\u044F \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442: \u0441\u0435\u0441\u0441\u0438\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u0431\u044E\u0434\u0436\u0435\u0442 \u043F\u0440\u043E\u0433\u043E\u043D\u0430";
|
|
16853
17391
|
var NO_AUTO_WAKE_REASONS = [EXITED_NO_DONE_REASON, BUDGET_STOP_REASON, ABORTED_STOP_REASON];
|
|
16854
|
-
var REVIEW_BUDGET_MARK = "\u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u0443\u043B\u043E\u0436\u0438\u043B\u043E\u0441\u044C \u0432 \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0442\u0440\u0430\u0442";
|
|
16855
|
-
function reviewBudgetMark(cycle) {
|
|
16856
|
-
return `${REVIEW_BUDGET_MARK} (\u043A\u0440\u0443\u0433 ${cycle})`;
|
|
16857
|
-
}
|
|
16858
|
-
var NEEDS_HUMAN_MARKS = [
|
|
16859
|
-
RESOLVE_ESCALATION_MARK,
|
|
16860
|
-
REVIEW_ESCALATION_MARK,
|
|
16861
|
-
REVIEW_UNRECOVERABLE_MARK,
|
|
16862
|
-
REVIEW_CYCLE_ESCALATION_MARK,
|
|
16863
|
-
CI_WAKE_ESCALATION_MARK,
|
|
16864
|
-
CHANGES_WAKE_ESCALATION_MARK,
|
|
16865
|
-
REVIEW_BUDGET_MARK
|
|
16866
|
-
];
|
|
16867
|
-
var SELF_REFERENCE_MARK = "PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430";
|
|
16868
17392
|
var REVIEW_NO_VERDICT_REASON = "\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430: \u043D\u0438 approve, \u043D\u0438 changes \u2014 \u0437\u0430\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E, \u043D\u0443\u0436\u0435\u043D \u043D\u043E\u0432\u044B\u0439 \u0437\u0430\u0445\u043E\u0434 (TASWM-375)";
|
|
16869
17393
|
function verdictCommentMark(createdAt) {
|
|
16870
17394
|
return `\u043A\u043E\u043C\u043C\u0435\u043D\u0442-\u0432\u0435\u0440\u0434\u0438\u043A\u0442@${createdAt}`;
|
|
16871
17395
|
}
|
|
16872
17396
|
var WATCHDOG_PARK_REASON = "\u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430: \u0441\u043B\u043E\u0442 \u0437\u0430\u043D\u044F\u0442 \u043D\u0438\u0437\u043A\u043E\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u044B\u043C \u2014 \u043F\u0430\u0440\u043A\u0443\u044E, \u0432\u043F\u0443\u0441\u043A\u0430\u044E \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442\u043D\u0435\u0435 \u0436\u0434\u0443\u0449\u0435\u0433\u043E (TASWM-270)";
|
|
17397
|
+
var RELIEF_LOOP_STOP_REASON = "\u0440\u0435\u0441\u0443\u0440\u0441-\u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0437\u0430\u0446\u0438\u043A\u043B\u0438\u043B\u0430\u0441\u044C: \u0441\u043D\u044F\u0442\u0438\u0439 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0441\u0434\u0430\u0447\u0438 \u0431\u043E\u043B\u044C\u0448\u0435 \u043F\u043E\u0442\u043E\u043B\u043A\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A (TASWM-531)";
|
|
16873
17398
|
function livePpids() {
|
|
16874
17399
|
return livePpidsFrom(listProcs());
|
|
16875
17400
|
}
|
|
@@ -17120,11 +17645,17 @@ async function prGateForReviewer(deps, run, prUrl, when) {
|
|
|
17120
17645
|
await finalizeClosedPrReview(deps, run, `PR \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430 (${when}) \u2014 \u0440\u0435\u0432\u044C\u044E \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E`);
|
|
17121
17646
|
narrate(deps, `PR ${droneLabel(run)} \u0437\u0430\u043A\u0440\u044B\u0442 \u0431\u0435\u0437 \u043C\u0435\u0440\u0436\u0430, ${when} \u2014 \u0440\u0435\u0432\u044C\u044E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E, \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u0440\u044B\u0442.`);
|
|
17122
17647
|
return { kind: "finalized" };
|
|
17123
|
-
case "unknown":
|
|
17648
|
+
case "unknown": {
|
|
17649
|
+
const notPr = !isGithubPrUrl(prUrl);
|
|
17124
17650
|
if (!deps.db.events(run.id).some((e) => (e.text ?? "").includes(REVIEW_PR_UNKNOWN_MARK))) {
|
|
17125
|
-
deps.db.addEvent(
|
|
17651
|
+
deps.db.addEvent(
|
|
17652
|
+
run.id,
|
|
17653
|
+
"step",
|
|
17654
|
+
notPr ? `${REVIEW_PR_UNKNOWN_MARK}: \u0432 pr_url \u043D\u0435 GitHub-PR (${prUrl}) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C. \u0421\u0430\u043C\u043E \u043D\u0435 \u043F\u043E\u0447\u0438\u043D\u0438\u0442\u0441\u044F: \u0434\u0440\u043E\u043D \u0441\u0434\u0430\u043B \u0441\u0441\u044B\u043B\u043A\u043E\u0439 \u043D\u0435 \u043D\u0430 PR.` : `${REVIEW_PR_UNKNOWN_MARK}: gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E PR (\u0441\u0435\u0442\u044C/\u043B\u0438\u043C\u0438\u0442) \u2014 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u043C, \u0441\u043F\u0440\u043E\u0441\u0438\u043C \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0442\u0438\u043A\u0435`
|
|
17655
|
+
);
|
|
17126
17656
|
}
|
|
17127
17657
|
return { kind: "wait" };
|
|
17658
|
+
}
|
|
17128
17659
|
default:
|
|
17129
17660
|
if (st.conflicting || st.isDraft) {
|
|
17130
17661
|
deps.db.addEvent(run.id, "step", `PR \u0441\u0442\u0430\u043B \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u044E\u0449\u0438\u043C/\u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u043E\u043C, ${when} \u2014 \u0441\u043F\u0430\u0432\u043D \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u0434\u043E \u0441\u043B\u0435\u0434. \u0442\u0438\u043A\u0430`);
|
|
@@ -17238,17 +17769,11 @@ function relinkUnboundReviewers(deps) {
|
|
|
17238
17769
|
narrate(deps, `${droneLabel(rv)} \u0440\u0430\u0431\u043E\u0442\u0430\u043B \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438 \u043A \u043F\u0440\u043E\u0433\u043E\u043D\u0443 \u2014 \u0441\u0432\u044F\u0437\u0430\u043B \u043F\u043E PR-\u0441\u0441\u044B\u043B\u043A\u0435, \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0442\u0435\u043F\u0435\u0440\u044C \u043B\u044F\u0436\u0435\u0442 \u0432 ${parent.task_slug}, \u0430 \u043D\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u043A\u043E\u043C\u043C\u0435\u043D\u0442 GitHub.`);
|
|
17239
17770
|
}
|
|
17240
17771
|
}
|
|
17772
|
+
var ESCALATION_LIMITS = { reviewSpawn: REVIEW_SPAWN_LIMIT, reviewCycle: REVIEW_CYCLE_LIMIT };
|
|
17241
17773
|
function deliberateReviewStop(db, run) {
|
|
17242
17774
|
const texts = db.events(run.id).map((e) => e.text ?? "");
|
|
17243
|
-
const
|
|
17244
|
-
|
|
17245
|
-
return `PR \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0434 \u0441\u0430\u043C\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 (${DAEMON_CODE_PATHS.join(", ")}) \u2014 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E (TASWM-270): \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0438\u0439 \u0434\u0435\u043C\u043E\u043D \u043A\u0440\u0443\u0442\u0438\u0442\u0441\u044F \u043D\u0430 \u0441\u0442\u0430\u0440\u043E\u043C \u043A\u043E\u0434\u0435 \u0438 \u0441\u0432\u043E\u044E \u0436\u0435 \u043F\u0440\u0430\u0432\u043A\u0443 \u043D\u0435 \u0430\u0442\u0442\u0435\u0441\u0442\u0443\u0435\u0442. \u041D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
17246
|
-
}
|
|
17247
|
-
if (has(REVIEW_ESCALATION_MARK)) return `\u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B (${REVIEW_SPAWN_LIMIT}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
17248
|
-
if (has(REVIEW_CYCLE_ESCALATION_MARK)) return `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0446\u0438\u043A\u043B\u043E\u0432 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (${REVIEW_CYCLE_LIMIT}) \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
17249
|
-
if (has(reviewBudgetMark(run.review_cycle ?? 0))) return "\u0440\u0435\u0432\u044C\u044E \u044D\u0442\u043E\u0433\u043E \u043A\u0440\u0443\u0433\u0430 \u0441\u0433\u043E\u0440\u0435\u043B\u043E \u043D\u0430 \u043F\u043E\u0442\u043E\u043B\u043A\u0435 \u0442\u0440\u0430\u0442 \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u0443\u043F\u0440\u0451\u0442\u0441\u044F \u0432 \u0442\u043E\u0442 \u0436\u0435, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
|
|
17250
|
-
if (has(reviewUnrecoverableMark(run.branch || run.task_slug))) return "\u0432\u0435\u0442\u043A\u0438 PR \u043D\u0435\u0442 \u043D\u0430 origin \u2014 \u043F\u043E\u0432\u0442\u043E\u0440\u043E\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A";
|
|
17251
|
-
return null;
|
|
17775
|
+
const hit = findEscalation(texts, run, "reviewer");
|
|
17776
|
+
return hit?.stopReason?.(run, ESCALATION_LIMITS) ?? null;
|
|
17252
17777
|
}
|
|
17253
17778
|
function noteReviewBlocked(deps, run, reason) {
|
|
17254
17779
|
if (!shouldNoteReason(deps.db, run.id, reason)) return;
|
|
@@ -17322,7 +17847,6 @@ async function closeOrphanedReviewers(deps) {
|
|
|
17322
17847
|
}
|
|
17323
17848
|
}
|
|
17324
17849
|
var VERIFY_SPAWN_LIMIT = Number(process.env.SWARM_VERIFY_SPAWN_LIMIT) || 2;
|
|
17325
|
-
var VERIFY_ESCALATION_MARK = "\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F: \u043F\u043E\u043F\u044B\u0442\u043A\u0438 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D\u044B";
|
|
17326
17850
|
function countActiveVerifiers(db) {
|
|
17327
17851
|
return db.listRuns().filter((r) => r.kind === "verifier" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
|
|
17328
17852
|
}
|
|
@@ -17496,10 +18020,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
17496
18020
|
}
|
|
17497
18021
|
return parts.join("\n");
|
|
17498
18022
|
}
|
|
17499
|
-
var HERO_CHARTER_PATH =
|
|
18023
|
+
var HERO_CHARTER_PATH = join33(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
17500
18024
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
17501
18025
|
const name = run.name || `#${run.id}`;
|
|
17502
|
-
const charter =
|
|
18026
|
+
const charter = readFileSync24(HERO_CHARTER_PATH, "utf8");
|
|
17503
18027
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
17504
18028
|
|
|
17505
18029
|
${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).`;
|
|
@@ -17803,17 +18327,11 @@ async function wakeRun(deps, runId) {
|
|
|
17803
18327
|
if (!run) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u0440\u043E\u044F` };
|
|
17804
18328
|
if (run.pid != null && deps.isAlive(run.pid)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 (pid ${run.pid}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
|
|
17805
18329
|
if (isTerminalStatus(run.status)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435\u043D (${run.status}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
|
|
17806
|
-
const
|
|
17807
|
-
(e) => [RESOLVE_ESCALATION_MARK, CI_WAKE_ESCALATION_MARK, VERIFY_ESCALATION_MARK].some(
|
|
17808
|
-
(m) => (e.text ?? "").includes(m)
|
|
17809
|
-
)
|
|
17810
|
-
);
|
|
17811
|
-
const changesEscalated = deps.db.events(run.id).some((e) => (e.text ?? "").includes(`${CHANGES_WAKE_ESCALATION_MARK} (\u043A\u0440\u0443\u0433 ${run.review_cycle ?? 0})`));
|
|
17812
|
-
const stoppedForHuman = deliberateReviewStop(deps.db, run) != null || changesEscalated || authorSideStopped;
|
|
18330
|
+
const stoppedForHuman = deps.db.events(run.id).some((e) => isEscalationText(e.text ?? "", run, "author"));
|
|
17813
18331
|
if (run.status === "review" && run.pr_url && !needsFixIteration(run) && !stoppedForHuman) {
|
|
17814
18332
|
return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0441\u0434\u0430\u043D \u043D\u0430 \u0440\u0435\u0432\u044C\u044E (${run.pr_url}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E; \u043F\u0440\u0430\u0432\u043A\u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u043E\u0439 \u043F\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0443` };
|
|
17815
18333
|
}
|
|
17816
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18334
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
17817
18335
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
17818
18336
|
const wt = run.worktree_path;
|
|
17819
18337
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -17907,11 +18425,25 @@ async function handleCi(deps, run, now, worktreeExists, hasTranscript, droneCap)
|
|
|
17907
18425
|
}
|
|
17908
18426
|
if (ci?.bucket === "pass" && await wakeReviewerOnCiReady(deps, run, { kind: "pass", ci })) return;
|
|
17909
18427
|
const counts = ci && ci.total > 0 ? ` (${ci.pass}/${ci.total})` : "";
|
|
17910
|
-
const
|
|
17911
|
-
const
|
|
18428
|
+
const why = ci == null ? "\u043E\u043F\u0440\u043E\u0441 CI \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F \u0446\u0435\u043B\u0438\u043A\u043E\u043C (checks \u0431\u0440\u043E\u0441\u0438\u043B) \u2014 \u0436\u0434\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u043F\u0440\u043E\u0441\u0430" : ciUnavailableNote(ci);
|
|
18429
|
+
const note = ci == null || ci.bucket === "unavailable" ? why : ci.bucket === "pending" ? "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0435\u0449\u0451 \u0438\u0434\u0443\u0442" : ci.bucket === "none" ? "\u0447\u0435\u043A-\u0440\u0430\u043D\u043E\u0432 \u0435\u0449\u0451 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E, workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C \u2014 \u0436\u0434\u0443" : "\u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0433\u043E\u0442\u043E\u0432\u044B, \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u043A\u043E\u0433\u043E";
|
|
18430
|
+
const waiting = ci == null || ci.bucket === "unavailable" || ci.bucket === "pending" || ci.bucket === "none";
|
|
17912
18431
|
const text = `CI ${ci?.bucket ?? "\u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D"}${counts} \u2014 ${note}`;
|
|
17913
18432
|
deps.db.addEvent(run.id, "ci_checked", waiting ? `${text} [${CI_WAIT_POLL_MARK}]` : text);
|
|
17914
18433
|
}
|
|
18434
|
+
function ciUnavailableNote(ci) {
|
|
18435
|
+
const detail = ci.detail ? ` \u2014 ${ci.detail}` : "";
|
|
18436
|
+
switch (ci.reason) {
|
|
18437
|
+
case "timeout":
|
|
18438
|
+
return `\u0422\u0410\u0419\u041C\u0410\u0423\u0422: gh pr checks \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(GH_CALL_TIMEOUT_MS / 1e3)} \u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D${detail}. \u0421\u0435\u0442\u044C \u0443 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u0435\u0435, \u0447\u0435\u043C \u0443 \u0448\u0435\u043B\u043B\u0430 (\u043F\u0440\u043E\u043A\u0441\u0438/DNS) \u2014 \u0436\u0434\u0443 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043E\u043F\u0440\u043E\u0441\u0430`;
|
|
18439
|
+
case "crashed":
|
|
18440
|
+
return `gh \u041D\u0415 \u0417\u0410\u041F\u0423\u0421\u0422\u0418\u041B\u0421\u042F${detail}. \u042D\u0442\u043E \u043D\u0435 \u0441\u0435\u0442\u044C: \u0443 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 gh \u0432 PATH \u2014 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435\u043C \u043D\u0435 \u043B\u0435\u0447\u0438\u0442\u0441\u044F, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`;
|
|
18441
|
+
case "unreadable":
|
|
18442
|
+
return `\u041D\u0415\u0427\u0418\u0422\u0410\u0415\u041C\u042B\u0419 \u041E\u0422\u0412\u0415\u0422 gh${detail}. \u0412\u044B\u0432\u043E\u0434 \u0435\u0441\u0442\u044C, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u043A\u043E\u0432 \u2014 \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442 gh, \u0430 \u043D\u0435 \u0436\u0434\u0430\u0442\u044C`;
|
|
18443
|
+
default:
|
|
18444
|
+
return `\u041F\u0423\u0421\u0422\u041E\u0419 \u041E\u0422\u0412\u0415\u0422: gh \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F, \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u0432 \u043D\u0438\u0447\u0435\u0433\u043E${detail}. \u0421\u043C\u043E\u0442\u0440\u0435\u0442\u044C stderr \u0432\u044B\u0437\u043E\u0432\u0430 \u0432 \u043B\u043E\u0433\u0435 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0442\u0430\u043C DNS/\u043F\u0440\u043E\u043A\u0441\u0438/\u043B\u0438\u043C\u0438\u0442`;
|
|
18445
|
+
}
|
|
18446
|
+
}
|
|
17915
18447
|
function repoHasCi(deps, run) {
|
|
17916
18448
|
if (deps.repoHasCi) return deps.repoHasCi(run);
|
|
17917
18449
|
return repoHasCiWorkflows(run.worktree_path, run.repo);
|
|
@@ -17919,7 +18451,7 @@ function repoHasCi(deps, run) {
|
|
|
17919
18451
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
17920
18452
|
for (const base3 of [worktreePath, repo]) {
|
|
17921
18453
|
if (!base3) continue;
|
|
17922
|
-
const dir =
|
|
18454
|
+
const dir = join33(base3, ".github", "workflows");
|
|
17923
18455
|
try {
|
|
17924
18456
|
if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
17925
18457
|
} catch {
|
|
@@ -18105,7 +18637,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
18105
18637
|
return true;
|
|
18106
18638
|
}
|
|
18107
18639
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
18108
|
-
const exists = deps.worktreeExists ??
|
|
18640
|
+
const exists = deps.worktreeExists ?? existsSync20;
|
|
18109
18641
|
const transcript = deps.hasTranscript ?? executorHasSession;
|
|
18110
18642
|
return exists(worktree) && transcript(worktree);
|
|
18111
18643
|
}
|
|
@@ -18614,7 +19146,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
18614
19146
|
}
|
|
18615
19147
|
if (!candidate) return;
|
|
18616
19148
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
18617
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19149
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
18618
19150
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18619
19151
|
const wt = candidate.worktree_path;
|
|
18620
19152
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -18729,17 +19261,31 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
18729
19261
|
console.error(`[overmind] \u0440\u0435\u043A\u043B\u0435\u0439\u043C worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
18730
19262
|
}
|
|
18731
19263
|
}
|
|
19264
|
+
let saved = false;
|
|
18732
19265
|
if (candidate.worktree_path && deps.pauseSnapshot) {
|
|
18733
19266
|
try {
|
|
18734
|
-
|
|
19267
|
+
saved = await deps.pauseSnapshot(candidate.worktree_path);
|
|
18735
19268
|
if (saved) deps.db.addEvent(candidate.id, "step", "\u043D\u0435\u0434\u043E\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043E+\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043E \u043F\u0435\u0440\u0435\u0434 \u043F\u0430\u0443\u0437\u043E\u0439 (TASWM-229) \u2014 --continue \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u0441 \u044D\u0442\u043E\u0439 \u0442\u043E\u0447\u043A\u0438");
|
|
18736
19269
|
} catch (e) {
|
|
18737
19270
|
console.error(`[overmind] \u0441\u043D\u0430\u043F\u0448\u043E\u0442 worktree \u043F\u0430\u0443\u0437\u043D\u0443\u0442\u043E\u0433\u043E ${droneLabel(candidate)} \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
|
|
18738
19271
|
}
|
|
18739
19272
|
}
|
|
19273
|
+
const canContinue = !!candidate.worktree_path && worktreeAndTranscriptAlive(deps, candidate.worktree_path);
|
|
18740
19274
|
deps.db.updateRun(candidate.id, { status: "paused", stop_reason: RELIEF_PAUSE_REASON });
|
|
18741
|
-
deps.db.addEvent(candidate.id, "paused",
|
|
19275
|
+
deps.db.addEvent(candidate.id, "paused", reliefPauseReason(canContinue));
|
|
18742
19276
|
state.lastPauseAt = now;
|
|
19277
|
+
const loopEntry = deps.reliefLoop?.recordPause(
|
|
19278
|
+
reliefLoopKey(normalizeRepo(candidate.repo || deps.repoFallback), candidate.task_slug),
|
|
19279
|
+
now,
|
|
19280
|
+
candidate.pr_url != null || saved
|
|
19281
|
+
);
|
|
19282
|
+
if (loopEntry && loopEntry.pauses > 1) {
|
|
19283
|
+
deps.db.addEvent(
|
|
19284
|
+
candidate.id,
|
|
19285
|
+
"step",
|
|
19286
|
+
`${RELIEF_PAUSE_MARK}: \u0441\u043D\u044F\u0442\u0438\u0435 ${loopEntry.pauses}-\u0435 \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0441\u0434\u0430\u0447\u0438 \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u043E\u0442\u043B\u043E\u0436\u0435\u043D \u043D\u0430 ${Math.round(reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs) / 1e3)}\u0441 (TASWM-531)`
|
|
19287
|
+
);
|
|
19288
|
+
}
|
|
18743
19289
|
recordReliefPause(
|
|
18744
19290
|
state.cost ??= { pauses: [] },
|
|
18745
19291
|
{ runId: candidate.id, label: droneLabel(candidate), at: now, resumedAt: null, stepsSinceResume },
|
|
@@ -18771,7 +19317,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
18771
19317
|
state.lastLoggedGateReason = null;
|
|
18772
19318
|
}
|
|
18773
19319
|
if (countActiveDrones(deps.db) >= droneCap) return;
|
|
18774
|
-
const pausedForRelief = deps.db.listRuns("paused").filter((r) => r.stop_reason
|
|
19320
|
+
const pausedForRelief = deps.db.listRuns("paused").filter((r) => isReliefPause(r.stop_reason));
|
|
18775
19321
|
const recovering = pausedForRelief.length > 0 || state.overloadedAt != null && now - state.overloadedAt < cfg.rampWindowMs;
|
|
18776
19322
|
if (!recovering) {
|
|
18777
19323
|
await batchSpawnQueue(deps, droneCap);
|
|
@@ -18782,10 +19328,27 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
18782
19328
|
if (resumeCandidate) {
|
|
18783
19329
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
18784
19330
|
if (!deps.respawn) return;
|
|
19331
|
+
const loopKey = reliefLoopKey(normalizeRepo(resumeCandidate.repo || deps.repoFallback), resumeCandidate.task_slug);
|
|
19332
|
+
const loopEntry = deps.reliefLoop?.get(loopKey);
|
|
19333
|
+
if (loopEntry) {
|
|
19334
|
+
const loopCfg = resolveReliefLoopConfig(deps.reliefLoopConfig);
|
|
19335
|
+
if (reliefLoopTripped(loopEntry, loopCfg)) {
|
|
19336
|
+
if (!loopEntry.escalated) {
|
|
19337
|
+
deps.db.updateRun(resumeCandidate.id, { status: "stalled", stop_reason: RELIEF_LOOP_STOP_REASON });
|
|
19338
|
+
deps.db.addEvent(
|
|
19339
|
+
resumeCandidate.id,
|
|
19340
|
+
"step",
|
|
19341
|
+
`${RELIEF_LOOP_ESCALATION_MARK}: \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043D\u0438\u043C\u0430\u043B\u0430 \u043F\u0440\u043E\u0433\u043E\u043D ${loopEntry.pauses} \u0440\u0430\u0437(\u0430) \u043F\u043E\u0434\u0440\u044F\u0434 \u0438 \u043D\u0438 \u043E\u0434\u0438\u043D \u043A\u0440\u0443\u0433 \u043D\u0435 \u043F\u0440\u0438\u043D\u0451\u0441 \u0441\u0434\u0430\u0447\u0438 (\u043D\u0438 PR, \u043D\u0438 \u043A\u043E\u043C\u043C\u0438\u0442\u0430) \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A (\u043F\u043E\u0442\u043E\u043B\u043E\u043A ${loopCfg.limit}, TASWM-531)`
|
|
19342
|
+
);
|
|
19343
|
+
deps.reliefLoop?.markEscalated(loopKey);
|
|
19344
|
+
narrate(deps, `${droneLabel(resumeCandidate)} \u0441\u043D\u0438\u043C\u0430\u043B\u0441\u044F \u0440\u0430\u0437\u0433\u0440\u0443\u0437\u043A\u043E\u0439 ${loopEntry.pauses} \u0440\u0430\u0437(\u0430) \u043F\u043E\u0434\u0440\u044F\u0434 \u0431\u0435\u0437 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u2014 \u044D\u0442\u043E \u043F\u0435\u0442\u043B\u044F, \u0434\u0430\u043B\u044C\u0448\u0435 \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440.`);
|
|
19345
|
+
}
|
|
19346
|
+
return;
|
|
19347
|
+
}
|
|
19348
|
+
if (now - loopEntry.lastPauseAt < reliefBackoffMs(loopEntry.pauses, cfg.rampWindowMs, loopCfg)) return;
|
|
19349
|
+
}
|
|
18785
19350
|
const wt = resumeCandidate.worktree_path;
|
|
18786
|
-
const
|
|
18787
|
-
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
18788
|
-
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
19351
|
+
const canContinue = !!wt && worktreeAndTranscriptAlive(deps, wt);
|
|
18789
19352
|
const mode = canContinue ? "continue" : "fresh";
|
|
18790
19353
|
if (mode === "continue" && wt && deps.reclaimWorktree) {
|
|
18791
19354
|
try {
|
|
@@ -18824,7 +19387,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
18824
19387
|
(runId) => {
|
|
18825
19388
|
const run = runById.get(runId);
|
|
18826
19389
|
if (!run) return now;
|
|
18827
|
-
if (run.status === "paused" && run.stop_reason
|
|
19390
|
+
if (run.status === "paused" && isReliefPause(run.stop_reason)) return null;
|
|
18828
19391
|
return sqliteMs(run.updated_at);
|
|
18829
19392
|
},
|
|
18830
19393
|
now
|
|
@@ -18840,7 +19403,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
|
|
|
18840
19403
|
if (!reliefAlertDue(cost, thresholds)) return [];
|
|
18841
19404
|
const pressure = deps.hostPressure?.() ?? { culprits: [], readings: [], totalGb: 0 };
|
|
18842
19405
|
const occupying = allRuns.filter(
|
|
18843
|
-
(r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || r.status === "paused" && r.stop_reason
|
|
19406
|
+
(r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || r.status === "paused" && isReliefPause(r.stop_reason))
|
|
18844
19407
|
);
|
|
18845
19408
|
const repoOf = (r) => normalizeRepo(r.repo || deps.repoFallback || "");
|
|
18846
19409
|
const busyRepos = [...new Set(occupying.map(repoOf).filter(Boolean))].filter(
|
|
@@ -19043,7 +19606,7 @@ async function handleBudgetExhausted(deps, run, ctx) {
|
|
|
19043
19606
|
function buildEscalationCandidates(db) {
|
|
19044
19607
|
const candidates = [];
|
|
19045
19608
|
for (const run of db.listRuns("review")) {
|
|
19046
|
-
const hit = db.events(run.id).findLast((e) =>
|
|
19609
|
+
const hit = db.events(run.id).findLast((e) => isEscalationText(e.text ?? "", run, "supervision"));
|
|
19047
19610
|
if (!hit) continue;
|
|
19048
19611
|
candidates.push({
|
|
19049
19612
|
key: `needs_human:${run.id}`,
|
|
@@ -19061,7 +19624,7 @@ function buildEscalationCandidates(db) {
|
|
|
19061
19624
|
}
|
|
19062
19625
|
async function tickOnce(deps) {
|
|
19063
19626
|
const now = deps.now ? deps.now() : Date.now();
|
|
19064
|
-
const worktreeExists = deps.worktreeExists ??
|
|
19627
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
19065
19628
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
19066
19629
|
deps.sampleHost?.();
|
|
19067
19630
|
if (deps.rehydrate) {
|
|
@@ -19107,12 +19670,12 @@ async function tickOnce(deps) {
|
|
|
19107
19670
|
}
|
|
19108
19671
|
}
|
|
19109
19672
|
if (deps.repoMap) {
|
|
19110
|
-
const
|
|
19111
|
-
const sinceLast =
|
|
19112
|
-
if (sinceLast >=
|
|
19113
|
-
|
|
19673
|
+
const rm4 = deps.repoMap;
|
|
19674
|
+
const sinceLast = rm4.lastAt == null ? Infinity : now - rm4.lastAt;
|
|
19675
|
+
if (sinceLast >= rm4.intervalMs) {
|
|
19676
|
+
rm4.lastAt = now;
|
|
19114
19677
|
try {
|
|
19115
|
-
await
|
|
19678
|
+
await rm4.run();
|
|
19116
19679
|
} catch (e) {
|
|
19117
19680
|
console.error("[overmind] \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043A\u0430\u0440\u0442\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:", e);
|
|
19118
19681
|
}
|
|
@@ -19578,8 +20141,13 @@ async function tickOnce(deps) {
|
|
|
19578
20141
|
const ciWatchOff = parent != null && ciWatchClosed(deps.db, parent);
|
|
19579
20142
|
const ci = ciWatchOff ? null : await deps.ci.checks(run.pr_url).catch(() => null);
|
|
19580
20143
|
const noCiRepo = ci?.bucket === "none" && !repoHasCi(deps, run);
|
|
19581
|
-
if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied")) {
|
|
19582
|
-
const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : ci.bucket === "denied" ? `\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)${ci.detail ? ` \u2014 ${ci.detail}` : ""}` :
|
|
20144
|
+
if (!ciWatchOff && !noCiRepo && (ci == null || ci.bucket === "pending" || ci.bucket === "none" || ci.bucket === "denied" || ci.bucket === "unavailable")) {
|
|
20145
|
+
const what = ci == null ? "gh \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043F\u0440\u043E \u0447\u0435\u043A\u0438 \u2014 \u0436\u0434\u0443, \u043F\u043E\u043A\u0430 \u043F\u0440\u043E\u044F\u0441\u043D\u0438\u0442\u0441\u044F" : ci.bucket === "none" ? "\u0447\u0435\u043A\u043E\u0432 \u043D\u0430 PR \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u2014 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0447\u0435\u043A-\u0440\u0430\u043D\u0430, \u043D\u043E workflow \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0441\u0442\u044C" : ci.bucket === "denied" ? `\u0447\u0435\u043A\u0438 \u0440\u043E\u044E \u043D\u0435 \u0432\u0438\u0434\u043D\u044B: GitHub \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043F\u043E \u043F\u0440\u0430\u0432\u0430\u043C (403)${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : (
|
|
20146
|
+
// TASWM-497: и здесь причина СЛОВАМИ, а не общее «gh не ответил». Тот же словарь,
|
|
20147
|
+
// что в ленте опроса (callFailureWord), чтобы одно и то же состояние в журнале
|
|
20148
|
+
// прогона и в событии парковки не называлось двумя разными способами.
|
|
20149
|
+
ci.bucket === "unavailable" ? `\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u043A\u0438 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: gh ${callFailureWord({ reason: ci.reason ?? "empty", detail: "" })}${ci.detail ? ` \u2014 ${ci.detail}` : ""}` : `CI ${ci.pass}/${ci.total}`
|
|
20150
|
+
);
|
|
19583
20151
|
deps.db.updateRun(run.id, { status: "paused", stop_reason: AWAITING_CI_REASON });
|
|
19584
20152
|
deps.db.addEvent(run.id, "paused", `${AWAITING_CI_REASON} (${what}) \u2014 \u043D\u0435 \u0441\u043C\u0435\u0440\u0442\u044C: \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043A\u0438 \u0434\u043E\u0439\u0434\u0443\u0442 \u0434\u043E \u043A\u043E\u043D\u0446\u0430`);
|
|
19585
20153
|
narrate(deps, `${droneLabel(run)} \u0443\u0448\u0451\u043B \u0436\u0434\u0430\u0442\u044C CI (${what}) \u2014 \u043D\u0435 \u0445\u043E\u0440\u043E\u043D\u044E, \u0440\u0430\u0437\u0431\u0443\u0436\u0443 \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A.`);
|
|
@@ -19642,7 +20210,7 @@ async function tickOnce(deps) {
|
|
|
19642
20210
|
}
|
|
19643
20211
|
for (const run of deps.db.listRuns("paused")) {
|
|
19644
20212
|
if (run.kind === "hero") continue;
|
|
19645
|
-
if (run.stop_reason === AWAITING_CI_REASON || run.stop_reason
|
|
20213
|
+
if (run.stop_reason === AWAITING_CI_REASON || isReliefPause(run.stop_reason)) continue;
|
|
19646
20214
|
const last = lastActivityMs(deps.db, run);
|
|
19647
20215
|
if (now - last <= STALL_FLOOR_MS) continue;
|
|
19648
20216
|
const evs = deps.db.events(run.id);
|
|
@@ -19658,7 +20226,7 @@ async function tickOnce(deps) {
|
|
|
19658
20226
|
for (const run of deps.db.listRuns("paused")) {
|
|
19659
20227
|
if (run.kind === "hero") continue;
|
|
19660
20228
|
if (run.reviews_run_id != null) continue;
|
|
19661
|
-
if (run.stop_reason
|
|
20229
|
+
if (isReliefPause(run.stop_reason)) continue;
|
|
19662
20230
|
if (NO_AUTO_WAKE_REASONS.some((r) => run.stop_reason?.startsWith(r))) continue;
|
|
19663
20231
|
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
19664
20232
|
if (!await projectQuotaGateAllows(deps, run)) continue;
|
|
@@ -19852,7 +20420,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
19852
20420
|
model: reviewRun.model,
|
|
19853
20421
|
review_model: reviewRun.review_model
|
|
19854
20422
|
});
|
|
19855
|
-
const wt =
|
|
20423
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
19856
20424
|
try {
|
|
19857
20425
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
19858
20426
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -19931,7 +20499,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
19931
20499
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
19932
20500
|
workspace_id: reviewRun.workspace_id
|
|
19933
20501
|
});
|
|
19934
|
-
const wt =
|
|
20502
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
19935
20503
|
try {
|
|
19936
20504
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
19937
20505
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -20012,7 +20580,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
20012
20580
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
20013
20581
|
workspace_id: verifiedRun.workspace_id
|
|
20014
20582
|
});
|
|
20015
|
-
const wt =
|
|
20583
|
+
const wt = join33(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
20016
20584
|
try {
|
|
20017
20585
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
20018
20586
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
@@ -20045,7 +20613,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20045
20613
|
if (mode === "continue") {
|
|
20046
20614
|
const prompt2 = nudge ?? (run.kind === "hero" ? HERO_CONTINUE_NUDGE : CONTINUE_NUDGE);
|
|
20047
20615
|
const pid2 = deps.launch({ worktree: wt, run, prompt: prompt2, continueSession: true });
|
|
20048
|
-
deps.db.updateRun(run.id, { status: "running", pid: pid2 });
|
|
20616
|
+
deps.db.updateRun(run.id, { status: "running", pid: pid2, ...worktreePathPatch(run, wt) });
|
|
20049
20617
|
return;
|
|
20050
20618
|
}
|
|
20051
20619
|
const [task, verifyMode, projectBase, zoneNotes] = await Promise.all([
|
|
@@ -20058,7 +20626,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20058
20626
|
]);
|
|
20059
20627
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
20060
20628
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
20061
|
-
if (!
|
|
20629
|
+
if (!existsSync20(wt)) {
|
|
20062
20630
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
20063
20631
|
const source = await addWorktree(repo, wt, branch, {
|
|
20064
20632
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -20082,10 +20650,13 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
20082
20650
|
const notice = depsPromptNotice(depsReport);
|
|
20083
20651
|
const prompt = [freshPrompt, notice, nudge].filter(Boolean).join("\n\n");
|
|
20084
20652
|
const pid = deps.launch({ worktree: wt, run: runToSpawn, prompt });
|
|
20085
|
-
deps.db.updateRun(run.id, { status: "running", pid });
|
|
20653
|
+
deps.db.updateRun(run.id, { status: "running", pid, ...worktreePathPatch(run, wt) });
|
|
20654
|
+
}
|
|
20655
|
+
function worktreePathPatch(run, wt) {
|
|
20656
|
+
return run.worktree_path === wt ? {} : { worktree_path: wt };
|
|
20086
20657
|
}
|
|
20087
20658
|
async function syncCrashedOnStartup(deps) {
|
|
20088
|
-
const worktreeExists = deps.worktreeExists ??
|
|
20659
|
+
const worktreeExists = deps.worktreeExists ?? existsSync20;
|
|
20089
20660
|
let orphaned = 0;
|
|
20090
20661
|
let merged = 0;
|
|
20091
20662
|
let requeued = 0;
|
|
@@ -20239,7 +20810,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
20239
20810
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
20240
20811
|
targetDir: WORKTREES_DIR,
|
|
20241
20812
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
20242
|
-
exists: (d) =>
|
|
20813
|
+
exists: (d) => existsSync20(d),
|
|
20243
20814
|
isNonEmptyDir: (d) => {
|
|
20244
20815
|
try {
|
|
20245
20816
|
return readdirSync13(d).length > 0;
|
|
@@ -20248,13 +20819,13 @@ async function migrateWorktreesAtStartup() {
|
|
|
20248
20819
|
}
|
|
20249
20820
|
},
|
|
20250
20821
|
liveDronesUnder: (d) => pidsWithCwdUnder(d).length > 0,
|
|
20251
|
-
rename: (from, to) =>
|
|
20822
|
+
rename: (from, to) => renameSync12(from, to),
|
|
20252
20823
|
repairMoved: (t) => repairMovedWorktrees(t),
|
|
20253
20824
|
log: (m) => console.log(m)
|
|
20254
20825
|
});
|
|
20255
20826
|
} catch (e) {
|
|
20256
20827
|
console.error("[migrate] \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F worktrees \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u2014 \u0444\u043E\u043B\u0431\u044D\u043A:", e);
|
|
20257
|
-
return
|
|
20828
|
+
return existsSync20(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
20258
20829
|
}
|
|
20259
20830
|
}
|
|
20260
20831
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -20727,7 +21298,7 @@ async function runDaemon(opts) {
|
|
|
20727
21298
|
baseSync: (url) => prBaseSync(url),
|
|
20728
21299
|
checks: async (url) => {
|
|
20729
21300
|
const ci = await prCiStatus(url);
|
|
20730
|
-
if (ci
|
|
21301
|
+
if (ci.bucket === "denied" || ci.bucket === "unavailable") return null;
|
|
20731
21302
|
return { bucket: ci.bucket };
|
|
20732
21303
|
}
|
|
20733
21304
|
} : {}
|
|
@@ -20907,7 +21478,7 @@ async function runDaemon(opts) {
|
|
|
20907
21478
|
isDirty: (p) => worktreeDirty(p),
|
|
20908
21479
|
releaseWorktree: (repo, p) => releaseWorktree(repo || opts.repo, p),
|
|
20909
21480
|
pruneWorktrees: (repo) => pruneWorktrees(repo || opts.repo),
|
|
20910
|
-
worktreeExists:
|
|
21481
|
+
worktreeExists: existsSync20,
|
|
20911
21482
|
liveWorktrees: () => new Set(listDroneProcesses().map((p) => p.worktree)),
|
|
20912
21483
|
narrate: narrator.narrate
|
|
20913
21484
|
});
|
|
@@ -20947,6 +21518,9 @@ async function runDaemon(opts) {
|
|
|
20947
21518
|
// разгрузки (TASWM-316) — заводим ОДИН раз на цикл демона, tickOnce (manageRelief)
|
|
20948
21519
|
// мутирует эти же поля каждый тик.
|
|
20949
21520
|
relief: { cost: { pauses: [] } },
|
|
21521
|
+
// Счёт снятий разгрузкой подряд без сдачи (TASWM-531) — durable, в отличие от relief-состояния
|
|
21522
|
+
// выше: он обязан пережить и рестарт демона, и подмену прогона сторожем прогресса.
|
|
21523
|
+
reliefLoop: new ReliefLoopStore(SWARM_HOME),
|
|
20950
21524
|
// Гейт окружения машины (TASWM-313): не спавним дрона, если проекту не хватает сервисов
|
|
20951
21525
|
// (лежащий докер = заведомо красные api-тесты). Только client-режим — требования живут в
|
|
20952
21526
|
// настройках проекта; без него гейта нет и рой работает как раньше. Дедуп событий —
|
|
@@ -21030,7 +21604,7 @@ async function runDaemon(opts) {
|
|
|
21030
21604
|
mode,
|
|
21031
21605
|
nudge
|
|
21032
21606
|
),
|
|
21033
|
-
worktreeExists: (p) =>
|
|
21607
|
+
worktreeExists: (p) => existsSync20(p),
|
|
21034
21608
|
// TASWM-514: с этого момента считаем попытки спавна — счётчик живёт ровно одну жизнь
|
|
21035
21609
|
// процесса демона и рестарт не переживает (см. preStartAttempts).
|
|
21036
21610
|
startedAt: Date.now(),
|
|
@@ -21105,9 +21679,11 @@ async function runDaemon(opts) {
|
|
|
21105
21679
|
setTimeout(() => {
|
|
21106
21680
|
Promise.all([closeWebServer(web.server), droneChannel ? closeDroneChannel(droneChannel.server) : Promise.resolve()]).then(() => {
|
|
21107
21681
|
if (!stopping) {
|
|
21108
|
-
|
|
21682
|
+
spawn4(process.execPath, respawnArgs, {
|
|
21109
21683
|
detached: true,
|
|
21110
|
-
stdio: "ignore"
|
|
21684
|
+
// TASWM-497: в ЛОГ, а не в /dev/null. Со `stdio: "ignore"` демон после каждого
|
|
21685
|
+
// `update`/`restart` замолкал навсегда, и разбирать инциденты по логу было нечем.
|
|
21686
|
+
stdio: respawnStdio(DAEMON_LOG_PATH),
|
|
21111
21687
|
cwd: process.cwd(),
|
|
21112
21688
|
env: { ...process.env, SWARM_REPO: opts.repo }
|
|
21113
21689
|
}).unref();
|
|
@@ -21497,6 +22073,7 @@ export {
|
|
|
21497
22073
|
HERO_CONTINUE_NUDGE,
|
|
21498
22074
|
OPEN_PR_HOLD_MARK,
|
|
21499
22075
|
OVERMIND_ENTRY,
|
|
22076
|
+
RELIEF_LOOP_STOP_REASON,
|
|
21500
22077
|
RESOLVE_ESCALATION_MARK,
|
|
21501
22078
|
RESOLVE_LIMIT,
|
|
21502
22079
|
RESPAWN_LIMIT,
|