@taskless-app/swarm 0.6.14 → 0.6.16
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/README.md +3 -1
- package/dist/cli.js +913 -341
- package/dist/overmind.js +1101 -382
- package/package.json +2 -2
package/dist/overmind.js
CHANGED
|
@@ -58,6 +58,13 @@ var init_types = __esm({
|
|
|
58
58
|
// вопрос дрона оператору (прод: SwarmEvent kind=question, TASWM-74)
|
|
59
59
|
"answer",
|
|
60
60
|
// ответ оператора на вопрос дрона (прод: SwarmEvent kind=answer)
|
|
61
|
+
// Черновик ответа, собранный машиной (TASWM-430): контекст задачи/репозитория + один
|
|
62
|
+
// вызов модели, реплика в ленте прогона рядом с вопросом. НЕ "answer" с флагом — и это
|
|
63
|
+
// главное в этом kind: вопрос закрывают (и дрона будят) все, кто сравнивает kind со
|
|
64
|
+
// строкой "answer"/"answered" — прод (state/taskRuns/timeline/fleetMetrics), веб-чат и
|
|
65
|
+
// синхронизация демона (droneBridge тянет вниз только ответ). Отдельный kind не знает
|
|
66
|
+
// никто из них, поэтому черновик физически не может разбудить дрона: он ждёт человека.
|
|
67
|
+
"answer_draft",
|
|
61
68
|
"ci_wake",
|
|
62
69
|
// review-PR покраснел (gh pr checks) — рой разбудил дрона со сжатым summary фейла (TASWM-102)
|
|
63
70
|
"ci_checked",
|
|
@@ -317,21 +324,28 @@ function computeReviewPhase(input) {
|
|
|
317
324
|
return { kind: "reviewer_dead", label: REVIEW_PHASE_TEXT.reviewer_dead, ready: true };
|
|
318
325
|
}
|
|
319
326
|
const p = input.ciProgress;
|
|
327
|
+
if (p && p.total > 0 && p.pass >= p.total) {
|
|
328
|
+
return { kind: "awaiting_reviewer", label: REVIEW_PHASE_TEXT.awaiting_reviewer, ready: false };
|
|
329
|
+
}
|
|
320
330
|
const label = p && p.total > 0 ? `${REVIEW_PHASE_TEXT.checks} \u2014 CI ${p.pass}/${p.total}` : REVIEW_PHASE_TEXT.checks;
|
|
321
331
|
return { kind: "checks", label, ready: false };
|
|
322
332
|
}
|
|
323
|
-
var REVIEW_PHASE_TEXT;
|
|
333
|
+
var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
|
|
324
334
|
var init_reviewPhase = __esm({
|
|
325
335
|
"../swarm-core/src/reviewPhase.ts"() {
|
|
326
336
|
"use strict";
|
|
327
337
|
REVIEW_PHASE_TEXT = {
|
|
328
338
|
checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
|
|
339
|
+
awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
|
|
329
340
|
ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
|
|
330
341
|
in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
|
|
331
342
|
changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
|
|
332
343
|
ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
|
|
333
344
|
reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
334
345
|
};
|
|
346
|
+
PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
|
|
347
|
+
(a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
|
|
348
|
+
);
|
|
335
349
|
}
|
|
336
350
|
});
|
|
337
351
|
|
|
@@ -511,7 +525,7 @@ var init_status = __esm({
|
|
|
511
525
|
// ../swarm-core/src/deps.ts
|
|
512
526
|
function judgeDep(runs) {
|
|
513
527
|
const real = runs.filter((r) => !r.service);
|
|
514
|
-
if (real.length === 0) return { ...
|
|
528
|
+
if (real.length === 0) return { ...UNKNOWN };
|
|
515
529
|
if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
|
|
516
530
|
if (real.some((r) => r.status === "done")) {
|
|
517
531
|
return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
@@ -531,12 +545,13 @@ function depClearedText(slug, v) {
|
|
|
531
545
|
const why = v.stopReason ? ` \u2014 ${v.stopReason}` : "";
|
|
532
546
|
return `\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u0441\u043D\u044F\u0442\u0430: \u0436\u0434\u0430\u043B ${who}, \u0442\u043E\u0442 ${how}${why}. \u0423\u0441\u043F\u0435\u0448\u043D\u043E\u0433\u043E \u0444\u0438\u043D\u0430\u043B\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u2014 \u0435\u0434\u0443.`;
|
|
533
547
|
}
|
|
534
|
-
var PENDING, DEP_END_LABEL;
|
|
548
|
+
var PENDING, UNKNOWN, DEP_END_LABEL;
|
|
535
549
|
var init_deps = __esm({
|
|
536
550
|
"../swarm-core/src/deps.ts"() {
|
|
537
551
|
"use strict";
|
|
538
552
|
init_status();
|
|
539
553
|
PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
554
|
+
UNKNOWN = { outcome: "unknown", met: false, status: null, name: null, stopReason: null };
|
|
540
555
|
DEP_END_LABEL = {
|
|
541
556
|
stopped: "\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
542
557
|
failed: "\u0443\u043F\u0430\u043B",
|
|
@@ -555,6 +570,9 @@ function stampFromEpochSec(sec) {
|
|
|
555
570
|
function sqliteMs(ts) {
|
|
556
571
|
return Date.parse(ts.replace(" ", "T") + "Z");
|
|
557
572
|
}
|
|
573
|
+
function stampMs(at) {
|
|
574
|
+
return at.includes("T") ? Date.parse(at) : sqliteMs(at);
|
|
575
|
+
}
|
|
558
576
|
function toStamp(v) {
|
|
559
577
|
if (typeof v !== "string" || !v) return null;
|
|
560
578
|
const d = new Date(v);
|
|
@@ -903,9 +921,17 @@ var init_baseBranch = __esm({
|
|
|
903
921
|
});
|
|
904
922
|
|
|
905
923
|
// ../swarm-core/src/hosts.ts
|
|
924
|
+
function hostTickStallMs(intervalMs) {
|
|
925
|
+
const iv = intervalMs != null && Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 0;
|
|
926
|
+
return Math.max(iv * HOST_TICK_STALL_TICKS, HOST_TICK_STALL_FLOOR_MS);
|
|
927
|
+
}
|
|
928
|
+
var HOST_TICK_STALL_TICKS, HOST_TICK_STALL_FLOOR_MS, HOST_TICK_STALLED_ACTION;
|
|
906
929
|
var init_hosts = __esm({
|
|
907
930
|
"../swarm-core/src/hosts.ts"() {
|
|
908
931
|
"use strict";
|
|
932
|
+
HOST_TICK_STALL_TICKS = 10;
|
|
933
|
+
HOST_TICK_STALL_FLOOR_MS = 12e4;
|
|
934
|
+
HOST_TICK_STALLED_ACTION = '\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0434\u0435\u043C\u043E\u043D \u043C\u0430\u0448\u0438\u043D\u044B \u2014 swarm_host_command{kind:"restart"}';
|
|
909
935
|
}
|
|
910
936
|
});
|
|
911
937
|
|
|
@@ -936,10 +962,27 @@ var init_hostResources = __esm({
|
|
|
936
962
|
});
|
|
937
963
|
|
|
938
964
|
// ../swarm-core/src/hostPool.ts
|
|
965
|
+
var HOST_REJECTION_TEXT, HOST_REPO_BLOCK_TEXT;
|
|
939
966
|
var init_hostPool = __esm({
|
|
940
967
|
"../swarm-core/src/hostPool.ts"() {
|
|
941
968
|
"use strict";
|
|
942
969
|
init_hosts();
|
|
970
|
+
HOST_REJECTION_TEXT = {
|
|
971
|
+
offline: "\u043D\u0435 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438",
|
|
972
|
+
tick_stalled: `\u0446\u0438\u043A\u043B \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B \u2014 \u043F\u0443\u043B\u044C\u0441 \u0438\u0434\u0451\u0442, \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442; ${HOST_TICK_STALLED_ACTION}`,
|
|
973
|
+
not_signed_in: "\u043D\u0435 \u0432\u043E\u0448\u043B\u0430 \u0432 Taskless \u2014 \u043D\u0430 \u0435\u0451 \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0435 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u0432\u0445\u043E\u0434",
|
|
974
|
+
paused: "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435",
|
|
975
|
+
draining: "\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435",
|
|
976
|
+
repos_off: "\u0432\u0441\u0435 \u0435\u0451 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u044B",
|
|
977
|
+
full: "\u0437\u0430\u043D\u044F\u0442\u0430 \u043F\u043E\u043B\u043D\u043E\u0441\u0442\u044C\u044E"
|
|
978
|
+
};
|
|
979
|
+
HOST_REPO_BLOCK_TEXT = {
|
|
980
|
+
offline: HOST_REJECTION_TEXT.offline,
|
|
981
|
+
paused: HOST_REJECTION_TEXT.paused,
|
|
982
|
+
draining: HOST_REJECTION_TEXT.draining,
|
|
983
|
+
no_clone: "\u043A\u043B\u043E\u043D\u0430 \u043D\u0435\u0442",
|
|
984
|
+
repo_off: "\u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u0430"
|
|
985
|
+
};
|
|
943
986
|
}
|
|
944
987
|
});
|
|
945
988
|
|
|
@@ -1263,31 +1306,6 @@ function buildZoneModel(entries, opts = {}) {
|
|
|
1263
1306
|
const entryTerms = tasks.map((t) => new Set(t.text ? tokenize(t.text) : []));
|
|
1264
1307
|
return { tasks: tasks.length, zones, fileZone, spine, taskZone, zoneTerms, termZones, entries: tasks, entryTerms };
|
|
1265
1308
|
}
|
|
1266
|
-
function zoneForFiles(model, files) {
|
|
1267
|
-
if (!hasZoneModel(model) || !files.length) return null;
|
|
1268
|
-
const spineSet = new Set(model.spine);
|
|
1269
|
-
const touchedSpine = files.filter((f) => spineSet.has(f));
|
|
1270
|
-
const votes = /* @__PURE__ */ new Map();
|
|
1271
|
-
for (const f of new Set(files)) {
|
|
1272
|
-
const z2 = model.fileZone.get(f) ?? (spineSet.has(f) ? null : pathZoneCandidate(f));
|
|
1273
|
-
if (z2) bump(votes, z2);
|
|
1274
|
-
}
|
|
1275
|
-
const top = topKey(votes);
|
|
1276
|
-
if (!top) {
|
|
1277
|
-
return {
|
|
1278
|
-
zone: SPINE_ZONE,
|
|
1279
|
-
confidence: 1,
|
|
1280
|
-
why: `\u0432\u0441\u0435 \u0444\u0430\u0439\u043B\u044B \u2014 \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${touchedSpine.slice(0, 3).join(", ")})`,
|
|
1281
|
-
spine: true
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
return {
|
|
1285
|
-
zone: top.key,
|
|
1286
|
-
confidence: top.share,
|
|
1287
|
-
why: `${top.count} \u0438\u0437 ${[...votes.values()].reduce((a, b) => a + b, 0)} \u0444\u0430\u0439\u043B\u043E\u0432 \u2014 \u0437\u043E\u043D\u0430 ${top.key}`,
|
|
1288
|
-
spine: touchedSpine.length > 0
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
1309
|
function suggestZonesFromText(model, text, limit = 3) {
|
|
1292
1310
|
if (!hasZoneModel(model)) return [];
|
|
1293
1311
|
const terms = tokenize(text);
|
|
@@ -2105,6 +2123,7 @@ __export(config_exports, {
|
|
|
2105
2123
|
REPO_MAP_PATH: () => REPO_MAP_PATH,
|
|
2106
2124
|
REVIEW_CUTOFF_PATH: () => REVIEW_CUTOFF_PATH,
|
|
2107
2125
|
RUN_LOGS_DIR: () => RUN_LOGS_DIR,
|
|
2126
|
+
SWARM_ANOMALY_DEP_MISSING_MS: () => SWARM_ANOMALY_DEP_MISSING_MS,
|
|
2108
2127
|
SWARM_ANOMALY_FAILED_SPIKE_COUNT: () => SWARM_ANOMALY_FAILED_SPIKE_COUNT,
|
|
2109
2128
|
SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS: () => SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS,
|
|
2110
2129
|
SWARM_ANOMALY_MAX_REPEATS: () => SWARM_ANOMALY_MAX_REPEATS,
|
|
@@ -2147,6 +2166,7 @@ __export(config_exports, {
|
|
|
2147
2166
|
SWARM_REPO: () => SWARM_REPO,
|
|
2148
2167
|
SWARM_REPO_MAP_MS: () => SWARM_REPO_MAP_MS,
|
|
2149
2168
|
SWARM_REPO_PATHS: () => SWARM_REPO_PATHS,
|
|
2169
|
+
SWARM_REPO_SCAN_TTL_MS: () => SWARM_REPO_SCAN_TTL_MS,
|
|
2150
2170
|
SWARM_REVIEW_CAP: () => SWARM_REVIEW_CAP,
|
|
2151
2171
|
SWARM_SCAN_DEPTH: () => SWARM_SCAN_DEPTH,
|
|
2152
2172
|
SWARM_SCAN_ROOTS_RAW: () => SWARM_SCAN_ROOTS_RAW,
|
|
@@ -2168,6 +2188,7 @@ __export(config_exports, {
|
|
|
2168
2188
|
WORKTREES_LEGACY_DIR: () => WORKTREES_LEGACY_DIR,
|
|
2169
2189
|
WORKTREES_NOINDEX_DIR: () => WORKTREES_NOINDEX_DIR,
|
|
2170
2190
|
droneWorktreePath: () => droneWorktreePath,
|
|
2191
|
+
droneWorktreeRoot: () => droneWorktreeRoot,
|
|
2171
2192
|
mcpEndpoint: () => mcpEndpoint,
|
|
2172
2193
|
migrateWorktreesToNoindex: () => migrateWorktreesToNoindex,
|
|
2173
2194
|
normalizeRepo: () => normalizeRepo,
|
|
@@ -2186,7 +2207,10 @@ function normalizeRepo(repo) {
|
|
|
2186
2207
|
}
|
|
2187
2208
|
}
|
|
2188
2209
|
function droneWorktreePath(worktreesDir, repo, slug) {
|
|
2189
|
-
return join5(worktreesDir,
|
|
2210
|
+
return join5(droneWorktreeRoot(worktreesDir, repo), slug);
|
|
2211
|
+
}
|
|
2212
|
+
function droneWorktreeRoot(worktreesDir, repo) {
|
|
2213
|
+
return join5(worktreesDir, basename(normalizeRepo(repo)));
|
|
2190
2214
|
}
|
|
2191
2215
|
function resolveEffectiveWorktreesDir(deps) {
|
|
2192
2216
|
if (deps.envOverride) return deps.envOverride;
|
|
@@ -2248,7 +2272,7 @@ function reviewCutoffAt() {
|
|
|
2248
2272
|
}
|
|
2249
2273
|
return at;
|
|
2250
2274
|
}
|
|
2251
|
-
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, 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, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, 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_WATCHDOG_STALL_MS;
|
|
2275
|
+
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, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, 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;
|
|
2252
2276
|
var init_config = __esm({
|
|
2253
2277
|
"src/config.ts"() {
|
|
2254
2278
|
"use strict";
|
|
@@ -2310,6 +2334,7 @@ var init_config = __esm({
|
|
|
2310
2334
|
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
2311
2335
|
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join5(SWARM_HOME, "repo-map.json");
|
|
2312
2336
|
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
2337
|
+
SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
|
|
2313
2338
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
2314
2339
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
2315
2340
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
@@ -2336,6 +2361,7 @@ var init_config = __esm({
|
|
|
2336
2361
|
SWARM_ANOMALY_QUESTION_UNANSWERED_MS = envNum("SWARM_ANOMALY_QUESTION_UNANSWERED_MS", 20 * 6e4);
|
|
2337
2362
|
SWARM_ANOMALY_OUTBOX_STUCK_MS = envNum("SWARM_ANOMALY_OUTBOX_STUCK_MS", 10 * 6e4);
|
|
2338
2363
|
SWARM_ANOMALY_RESOURCE_GATE_STALL_MS = envNum("SWARM_ANOMALY_RESOURCE_GATE_STALL_MS", 5 * 6e4);
|
|
2364
|
+
SWARM_ANOMALY_DEP_MISSING_MS = envNum("SWARM_ANOMALY_DEP_MISSING_MS", 2 * 6e4);
|
|
2339
2365
|
SWARM_WATCHDOG_STALL_MS = envNum("SWARM_WATCHDOG_STALL_MS", 15 * 6e4);
|
|
2340
2366
|
}
|
|
2341
2367
|
});
|
|
@@ -2734,11 +2760,11 @@ var init_storm = __esm({
|
|
|
2734
2760
|
});
|
|
2735
2761
|
|
|
2736
2762
|
// src/heartbeat.ts
|
|
2737
|
-
import { readFileSync as readFileSync5, renameSync as
|
|
2763
|
+
import { readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2738
2764
|
function writeHeartbeat(path, now) {
|
|
2739
2765
|
const tmp = `${path}.tmp`;
|
|
2740
2766
|
writeFileSync5(tmp, String(now));
|
|
2741
|
-
|
|
2767
|
+
renameSync4(tmp, path);
|
|
2742
2768
|
}
|
|
2743
2769
|
function readHeartbeat(path) {
|
|
2744
2770
|
try {
|
|
@@ -2761,7 +2787,7 @@ var init_heartbeat = __esm({
|
|
|
2761
2787
|
|
|
2762
2788
|
// src/agentToken.ts
|
|
2763
2789
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
2764
|
-
import { join as
|
|
2790
|
+
import { join as join9 } from "path";
|
|
2765
2791
|
function loadAgentToken() {
|
|
2766
2792
|
try {
|
|
2767
2793
|
const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
|
|
@@ -2817,7 +2843,7 @@ var init_agentToken = __esm({
|
|
|
2817
2843
|
"src/agentToken.ts"() {
|
|
2818
2844
|
"use strict";
|
|
2819
2845
|
init_config();
|
|
2820
|
-
STORE_PATH =
|
|
2846
|
+
STORE_PATH = join9(SWARM_HOME, "agent.json");
|
|
2821
2847
|
base = (api) => api.replace(/\/$/, "");
|
|
2822
2848
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2823
2849
|
}
|
|
@@ -3177,9 +3203,9 @@ var init_procCleanup = __esm({
|
|
|
3177
3203
|
});
|
|
3178
3204
|
|
|
3179
3205
|
// src/git.ts
|
|
3180
|
-
import { existsSync as
|
|
3206
|
+
import { existsSync as existsSync10, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
3181
3207
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3182
|
-
import { isAbsolute as isAbsolute2, join as
|
|
3208
|
+
import { isAbsolute as isAbsolute2, join as join17, sep as sep2 } from "path";
|
|
3183
3209
|
import { $ as $2 } from "execa";
|
|
3184
3210
|
function isUnrecoverableRefError(e) {
|
|
3185
3211
|
if (e instanceof MissingRefError) return true;
|
|
@@ -3191,7 +3217,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
3191
3217
|
}
|
|
3192
3218
|
function isLinkedWorktree(dir) {
|
|
3193
3219
|
try {
|
|
3194
|
-
return
|
|
3220
|
+
return statSync8(join17(dir, ".git")).isFile();
|
|
3195
3221
|
} catch {
|
|
3196
3222
|
return false;
|
|
3197
3223
|
}
|
|
@@ -3203,7 +3229,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
3203
3229
|
}
|
|
3204
3230
|
}
|
|
3205
3231
|
async function ensureWorktreeDirGone(worktree) {
|
|
3206
|
-
if (!
|
|
3232
|
+
if (!existsSync10(worktree)) return false;
|
|
3207
3233
|
if (!isLinkedWorktree(worktree)) {
|
|
3208
3234
|
console.error(
|
|
3209
3235
|
`[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.`
|
|
@@ -3218,7 +3244,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
3218
3244
|
);
|
|
3219
3245
|
return true;
|
|
3220
3246
|
}
|
|
3221
|
-
if (
|
|
3247
|
+
if (existsSync10(worktree)) {
|
|
3222
3248
|
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.`);
|
|
3223
3249
|
}
|
|
3224
3250
|
return true;
|
|
@@ -3266,7 +3292,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3266
3292
|
}
|
|
3267
3293
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
3268
3294
|
if (busyAt) {
|
|
3269
|
-
const exists =
|
|
3295
|
+
const exists = existsSync10(busyAt);
|
|
3270
3296
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
3271
3297
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
3272
3298
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -3301,8 +3327,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3301
3327
|
try {
|
|
3302
3328
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3303
3329
|
if (!commonDir) return false;
|
|
3304
|
-
const infoDir =
|
|
3305
|
-
const excludeFile =
|
|
3330
|
+
const infoDir = join17(commonDir, "info");
|
|
3331
|
+
const excludeFile = join17(infoDir, "exclude");
|
|
3306
3332
|
let current = "";
|
|
3307
3333
|
try {
|
|
3308
3334
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3322,7 +3348,7 @@ async function gitPath(worktree, flag) {
|
|
|
3322
3348
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3323
3349
|
const raw = out.stdout.trim();
|
|
3324
3350
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3325
|
-
return isAbsolute2(raw) ? raw :
|
|
3351
|
+
return isAbsolute2(raw) ? raw : join17(worktree, raw);
|
|
3326
3352
|
}
|
|
3327
3353
|
async function ensureWorktreeHygiene(worktree) {
|
|
3328
3354
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3332,12 +3358,12 @@ async function installGuardHooks(worktree) {
|
|
|
3332
3358
|
try {
|
|
3333
3359
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3334
3360
|
if (!gitDir) return false;
|
|
3335
|
-
const hooksDir =
|
|
3361
|
+
const hooksDir = join17(gitDir, "swarm-hooks");
|
|
3336
3362
|
const prev = await previousHooksPath(worktree);
|
|
3337
3363
|
await mkdir(hooksDir, { recursive: true });
|
|
3338
3364
|
for (const hook of GUARD_HOOKS) {
|
|
3339
|
-
await writeFile(
|
|
3340
|
-
await chmod(
|
|
3365
|
+
await writeFile(join17(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
3366
|
+
await chmod(join17(hooksDir, hook), 493);
|
|
3341
3367
|
}
|
|
3342
3368
|
await $2`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3343
3369
|
await $2`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3356,7 +3382,7 @@ async function previousHooksPath(worktree) {
|
|
|
3356
3382
|
if (out.exitCode === 0 && value) return value;
|
|
3357
3383
|
}
|
|
3358
3384
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3359
|
-
return commonDir ?
|
|
3385
|
+
return commonDir ? join17(commonDir, "hooks") : null;
|
|
3360
3386
|
}
|
|
3361
3387
|
async function removeWorktree(repo, path) {
|
|
3362
3388
|
const branch = (await $2({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3375,7 +3401,7 @@ async function worktreeDirty(path) {
|
|
|
3375
3401
|
async function repairMovedWorktrees(targetDir) {
|
|
3376
3402
|
const subdirs = (dir) => {
|
|
3377
3403
|
try {
|
|
3378
|
-
return
|
|
3404
|
+
return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join17(dir, d.name));
|
|
3379
3405
|
} catch {
|
|
3380
3406
|
return [];
|
|
3381
3407
|
}
|
|
@@ -3502,7 +3528,7 @@ __export(droneChannel_exports, {
|
|
|
3502
3528
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
3503
3529
|
});
|
|
3504
3530
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3505
|
-
import { chmodSync, mkdirSync as
|
|
3531
|
+
import { chmodSync, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
3506
3532
|
import { dirname as dirname3 } from "path";
|
|
3507
3533
|
import { serve } from "@hono/node-server";
|
|
3508
3534
|
function loadChannelSecret(path) {
|
|
@@ -3512,7 +3538,7 @@ function loadChannelSecret(path) {
|
|
|
3512
3538
|
} catch {
|
|
3513
3539
|
}
|
|
3514
3540
|
const secret = randomBytes(32).toString("hex");
|
|
3515
|
-
|
|
3541
|
+
mkdirSync8(dirname3(path), { recursive: true });
|
|
3516
3542
|
writeFileSync10(path, secret, { mode: 384 });
|
|
3517
3543
|
try {
|
|
3518
3544
|
chmodSync(path, 384);
|
|
@@ -3857,7 +3883,7 @@ var init_droneChannel = __esm({
|
|
|
3857
3883
|
|
|
3858
3884
|
// src/attachments.ts
|
|
3859
3885
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3860
|
-
import { join as
|
|
3886
|
+
import { join as join20 } from "path";
|
|
3861
3887
|
function attachmentsFromDoc(doc, origin) {
|
|
3862
3888
|
const out = [];
|
|
3863
3889
|
const walk2 = (node) => {
|
|
@@ -3948,7 +3974,7 @@ async function materializeAttachments(opts) {
|
|
|
3948
3974
|
};
|
|
3949
3975
|
const result = { files: [], skipped: [] };
|
|
3950
3976
|
if (opts.refs.length === 0) return result;
|
|
3951
|
-
const dir =
|
|
3977
|
+
const dir = join20(opts.worktree, ATTACHMENTS_DIR);
|
|
3952
3978
|
await io.removeDir(dir);
|
|
3953
3979
|
let dirReady = false;
|
|
3954
3980
|
let total = 0;
|
|
@@ -3994,7 +4020,7 @@ async function materializeAttachments(opts) {
|
|
|
3994
4020
|
await io.mkdir(dir);
|
|
3995
4021
|
dirReady = true;
|
|
3996
4022
|
}
|
|
3997
|
-
await io.writeFile(
|
|
4023
|
+
await io.writeFile(join20(dir, name), file.bytes);
|
|
3998
4024
|
} catch (e) {
|
|
3999
4025
|
result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
4000
4026
|
continue;
|
|
@@ -4147,7 +4173,7 @@ var init_attachments = __esm({
|
|
|
4147
4173
|
|
|
4148
4174
|
// src/shotUpload.ts
|
|
4149
4175
|
import { readFile as fsReadFile } from "fs/promises";
|
|
4150
|
-
import { basename as basename2, isAbsolute as isAbsolute3, join as
|
|
4176
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join21 } from "path";
|
|
4151
4177
|
function shotMimetype(path) {
|
|
4152
4178
|
const dot = path.lastIndexOf(".");
|
|
4153
4179
|
if (dot < 0) return null;
|
|
@@ -4173,7 +4199,7 @@ async function readShots(paths, opts) {
|
|
|
4173
4199
|
}
|
|
4174
4200
|
let bytes;
|
|
4175
4201
|
try {
|
|
4176
|
-
bytes = await read(isAbsolute3(path) ? path :
|
|
4202
|
+
bytes = await read(isAbsolute3(path) ? path : join21(opts.worktree, path));
|
|
4177
4203
|
} catch (e) {
|
|
4178
4204
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
4179
4205
|
continue;
|
|
@@ -4252,7 +4278,7 @@ var init_shotUpload = __esm({
|
|
|
4252
4278
|
// src/oauth.ts
|
|
4253
4279
|
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
4254
4280
|
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
|
|
4255
|
-
import { join as
|
|
4281
|
+
import { join as join22 } from "path";
|
|
4256
4282
|
function makePkce() {
|
|
4257
4283
|
const verifier = randomBytes2(48).toString("base64url");
|
|
4258
4284
|
const challenge = createHash2("sha256").update(verifier).digest("base64url");
|
|
@@ -4387,8 +4413,8 @@ var init_oauth = __esm({
|
|
|
4387
4413
|
AUTHORIZE = "/api/oauth/authorize";
|
|
4388
4414
|
TOKEN = "/api/oauth/token";
|
|
4389
4415
|
SCOPE = "openid profile email taskless:full";
|
|
4390
|
-
STORE_PATH2 =
|
|
4391
|
-
CLIENT_PATH =
|
|
4416
|
+
STORE_PATH2 = join22(SWARM_HOME, "oauth.json");
|
|
4417
|
+
CLIENT_PATH = join22(SWARM_HOME, "oauth-client.json");
|
|
4392
4418
|
base2 = (api) => api.replace(/\/$/, "");
|
|
4393
4419
|
}
|
|
4394
4420
|
});
|
|
@@ -4435,7 +4461,7 @@ var init_authState = __esm({
|
|
|
4435
4461
|
|
|
4436
4462
|
// src/workspace.ts
|
|
4437
4463
|
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
|
|
4438
|
-
import { join as
|
|
4464
|
+
import { join as join23 } from "path";
|
|
4439
4465
|
function loadWorkspace() {
|
|
4440
4466
|
try {
|
|
4441
4467
|
const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
|
|
@@ -4452,7 +4478,7 @@ var init_workspace = __esm({
|
|
|
4452
4478
|
"src/workspace.ts"() {
|
|
4453
4479
|
"use strict";
|
|
4454
4480
|
init_config();
|
|
4455
|
-
WORKSPACE_PATH =
|
|
4481
|
+
WORKSPACE_PATH = join23(SWARM_HOME, "workspace.json");
|
|
4456
4482
|
}
|
|
4457
4483
|
});
|
|
4458
4484
|
|
|
@@ -4558,6 +4584,28 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4558
4584
|
const text = res.content?.find((c) => c.type === "text")?.text;
|
|
4559
4585
|
return typeof text === "string" && text.trim() ? text : null;
|
|
4560
4586
|
}
|
|
4587
|
+
async function proposeRepoRules(input) {
|
|
4588
|
+
const res = await safeCall("swarm_repo_rule_propose", {
|
|
4589
|
+
repoFullName: input.repoFullName,
|
|
4590
|
+
rules: input.rules,
|
|
4591
|
+
...input.by ? { by: input.by } : {},
|
|
4592
|
+
...input.taskSlug ? { taskSlug: input.taskSlug } : {}
|
|
4593
|
+
});
|
|
4594
|
+
if (!res || res.isError) return null;
|
|
4595
|
+
const text = res.content?.find((c) => c.type === "text")?.text;
|
|
4596
|
+
return typeof text === "string" && text.trim() ? text : null;
|
|
4597
|
+
}
|
|
4598
|
+
async function decideRepoRules(input) {
|
|
4599
|
+
const res = await safeCall("swarm_repo_rule_decide", {
|
|
4600
|
+
repoFullName: input.repoFullName,
|
|
4601
|
+
decisions: input.decisions,
|
|
4602
|
+
...input.by ? { by: input.by } : {},
|
|
4603
|
+
...input.taskSlug ? { taskSlug: input.taskSlug } : {}
|
|
4604
|
+
});
|
|
4605
|
+
if (!res || res.isError) return null;
|
|
4606
|
+
const text = res.content?.find((c) => c.type === "text")?.text;
|
|
4607
|
+
return typeof text === "string" && text.trim() ? text : null;
|
|
4608
|
+
}
|
|
4561
4609
|
async function resolveId(idOrSlug) {
|
|
4562
4610
|
const cached = idCache.get(idOrSlug);
|
|
4563
4611
|
if (cached) return cached;
|
|
@@ -4638,6 +4686,8 @@ function makeTasklessClient(call, workspaceId, taskUrlBase, fetchFile, fetchImpl
|
|
|
4638
4686
|
getDocument,
|
|
4639
4687
|
getTaskContext,
|
|
4640
4688
|
reportAcceptanceCheck,
|
|
4689
|
+
proposeRepoRules,
|
|
4690
|
+
decideRepoRules,
|
|
4641
4691
|
createTask: async (input) => {
|
|
4642
4692
|
if (!input.projectId) {
|
|
4643
4693
|
throw new Error("createTask: projectId \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D \u2014 \u0430\u0432\u0442\u043E-\u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u043E\u0435\u043C \u0431\u0435\u0437 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E (TASWM-204)");
|
|
@@ -4947,7 +4997,8 @@ __export(db_exports, {
|
|
|
4947
4997
|
SwarmDb: () => SwarmDb
|
|
4948
4998
|
});
|
|
4949
4999
|
import { createRequire as createRequire2 } from "module";
|
|
4950
|
-
import {
|
|
5000
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5001
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4951
5002
|
import { resolve as resolve3 } from "path";
|
|
4952
5003
|
function rowToQuestion(row) {
|
|
4953
5004
|
return {
|
|
@@ -5109,6 +5160,12 @@ CREATE INDEX IF NOT EXISTS idx_runs_area ON runs(area);
|
|
|
5109
5160
|
CREATE INDEX IF NOT EXISTS idx_questions_run ON questions(run_id);
|
|
5110
5161
|
CREATE INDEX IF NOT EXISTS idx_questions_status ON questions(status);
|
|
5111
5162
|
CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id);
|
|
5163
|
+
-- \u043A\u043B\u044E\u0447-\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u043E \u0421\u0410\u041C \u0424\u0410\u0419\u041B \u0431\u0430\u0437\u044B (\u043D\u0435 \u043F\u0440\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u044B). \u041F\u043E\u043A\u0430 \u0432 \u043D\u0451\u043C \u043E\u0434\u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C \u2014 instance_id
|
|
5164
|
+
-- (TASWM-419): \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u042D\u0422\u041E\u0419 \u0436\u0438\u0437\u043D\u0438 \u0431\u0430\u0437\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u043D\u0435\u0439\u043C\u0441\u043F\u0435\u0439\u0441\u044F\u0442\u0441\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438 run-log'\u043E\u0432.
|
|
5165
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
5166
|
+
key TEXT PRIMARY KEY,
|
|
5167
|
+
value TEXT NOT NULL
|
|
5168
|
+
);
|
|
5112
5169
|
-- \u043C\u0430\u0440\u043A\u0435\u0440 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u044B\u0445 \u0438\u043C\u043F\u043E\u0440\u0442\u043E\u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u0411\u0414 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C importDb)
|
|
5113
5170
|
CREATE TABLE IF NOT EXISTS imports (
|
|
5114
5171
|
source TEXT PRIMARY KEY,
|
|
@@ -5121,6 +5178,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5121
5178
|
defaultRepo;
|
|
5122
5179
|
databaseCtor;
|
|
5123
5180
|
// для importDb (второй Database, source)
|
|
5181
|
+
/** Идентификатор этой жизни файла базы (TASWM-419) — им неймспейсятся каталоги run-log'ов. */
|
|
5182
|
+
instanceId;
|
|
5124
5183
|
constructor(path = "swarm.db", defaultRepo = "") {
|
|
5125
5184
|
this.defaultRepo = normalizeRepo(defaultRepo);
|
|
5126
5185
|
this.databaseCtor = loadBetterSqlite3();
|
|
@@ -5188,6 +5247,22 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5188
5247
|
if (this.defaultRepo) {
|
|
5189
5248
|
this.db.prepare("UPDATE runs SET repo = ? WHERE repo IS NULL").run(this.defaultRepo);
|
|
5190
5249
|
}
|
|
5250
|
+
this.instanceId = this.ensureInstanceId();
|
|
5251
|
+
}
|
|
5252
|
+
/**
|
|
5253
|
+
* Идентификатор ЭТОЙ жизни файла базы (TASWM-419). Заводится при первом открытии и живёт
|
|
5254
|
+
* в самой базе, поэтому переживает рестарт демона, но НЕ переживает пересоздание файла:
|
|
5255
|
+
* снесли базу — новый id, и каталоги run-log'ов нового прогона #1 не подселяются к файлам
|
|
5256
|
+
* прогона #1 из прошлой жизни. Именно это и было корнем «лог не того прогона»: номер там
|
|
5257
|
+
* был вся информация об адресе.
|
|
5258
|
+
*/
|
|
5259
|
+
ensureInstanceId() {
|
|
5260
|
+
const read = () => this.db.prepare("SELECT value FROM meta WHERE key = 'instance_id'").get()?.value;
|
|
5261
|
+
const existing = read();
|
|
5262
|
+
if (existing) return existing;
|
|
5263
|
+
const fresh = randomUUID3().replace(/-/g, "").slice(0, 12);
|
|
5264
|
+
this.db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('instance_id', ?)").run(fresh);
|
|
5265
|
+
return read() ?? fresh;
|
|
5191
5266
|
}
|
|
5192
5267
|
close() {
|
|
5193
5268
|
this.db.close();
|
|
@@ -5282,6 +5357,15 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5282
5357
|
const m = /^local:(\d+)$/.exec(key);
|
|
5283
5358
|
return m ? this.getRun(Number(m[1])) : null;
|
|
5284
5359
|
}
|
|
5360
|
+
/**
|
|
5361
|
+
* TASWM-419: адрес каталога run-log'ов — номер, НЕЙМСПЕЙСНУТЫЙ идентификатором файла базы
|
|
5362
|
+
* (`local:<instance>:<id>`). Тот же номер в пересозданной базе даёт другой instance, то
|
|
5363
|
+
* есть другой каталог: подселиться к файлам чужого прогона с тем же номером больше нечем.
|
|
5364
|
+
* Псевдонимов у sqlite-прогона нет — ключ не меняется, пока жив файл базы.
|
|
5365
|
+
*/
|
|
5366
|
+
runLogKeys(id) {
|
|
5367
|
+
return this.getRun(id) ? [`local:${this.instanceId}:${id}`] : [];
|
|
5368
|
+
}
|
|
5285
5369
|
listRuns(status) {
|
|
5286
5370
|
const rows = status ? this.db.prepare("SELECT * FROM runs WHERE status = ? ORDER BY id DESC").all(status) : this.db.prepare("SELECT * FROM runs ORDER BY id DESC").all();
|
|
5287
5371
|
return rows.map(rowToRun);
|
|
@@ -5522,7 +5606,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5522
5606
|
const repoN = normalizeRepo(repo);
|
|
5523
5607
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
5524
5608
|
if (already) return { imported: false, runs: already.run_count };
|
|
5525
|
-
if (!
|
|
5609
|
+
if (!existsSync12(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
5526
5610
|
const src = new this.databaseCtor(abs);
|
|
5527
5611
|
try {
|
|
5528
5612
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -5891,8 +5975,8 @@ var init_droneStore = __esm({
|
|
|
5891
5975
|
|
|
5892
5976
|
// src/overmind.ts
|
|
5893
5977
|
import { homedir as homedir5 } from "os";
|
|
5894
|
-
import { existsSync as
|
|
5895
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
5978
|
+
import { existsSync as existsSync17, readFileSync as readFileSync20, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
|
|
5979
|
+
import { basename as basename4, dirname as dirname4, join as join30 } from "path";
|
|
5896
5980
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
5897
5981
|
import { spawn as spawn3 } from "child_process";
|
|
5898
5982
|
|
|
@@ -5972,13 +6056,114 @@ init_config();
|
|
|
5972
6056
|
init_resources();
|
|
5973
6057
|
init_names2();
|
|
5974
6058
|
init_storm();
|
|
5975
|
-
import { appendFileSync, existsSync as
|
|
6059
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
5976
6060
|
import { dirname } from "path";
|
|
5977
6061
|
import { randomUUID } from "crypto";
|
|
5978
6062
|
|
|
6063
|
+
// src/repoRules.ts
|
|
6064
|
+
var RULE_FIELDS = ["brief", "zones", "env", "verify", "conventions", "spine"];
|
|
6065
|
+
var DRONE_RULES_PER_RUN_MAX = 2;
|
|
6066
|
+
function parsePendingRepoRules(raw) {
|
|
6067
|
+
if (!Array.isArray(raw)) return [];
|
|
6068
|
+
const out = [];
|
|
6069
|
+
for (const item of raw) {
|
|
6070
|
+
if (!item || typeof item !== "object") continue;
|
|
6071
|
+
const r = item;
|
|
6072
|
+
const field = RULE_FIELDS.find((f) => f === r.field);
|
|
6073
|
+
if (!field) continue;
|
|
6074
|
+
out.push({
|
|
6075
|
+
field,
|
|
6076
|
+
value: r.value,
|
|
6077
|
+
by: typeof r.by === "string" ? r.by : null,
|
|
6078
|
+
task: typeof r.task === "string" ? r.task : null,
|
|
6079
|
+
why: typeof r.why === "string" ? r.why : null,
|
|
6080
|
+
cheap: r.cheap === true,
|
|
6081
|
+
overwritesHuman: r.overwritesHuman === true
|
|
6082
|
+
});
|
|
6083
|
+
}
|
|
6084
|
+
return out;
|
|
6085
|
+
}
|
|
6086
|
+
function ruleValueLine(value) {
|
|
6087
|
+
const json = JSON.stringify(value ?? null);
|
|
6088
|
+
return json.length > 400 ? `${json.slice(0, 400)}\u2026 (\u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043E)` : json;
|
|
6089
|
+
}
|
|
6090
|
+
function renderPendingRulesBlock(rules) {
|
|
6091
|
+
if (rules.length === 0) return "";
|
|
6092
|
+
const lines = rules.map((r) => {
|
|
6093
|
+
const who = r.by ? `\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438\u043B ${r.by}` : "\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438\u043B \u0434\u0440\u043E\u043D";
|
|
6094
|
+
const on = r.task ? ` \u043D\u0430 ${r.task}` : "";
|
|
6095
|
+
const fate = r.cheap && !r.overwritesHuman ? "\u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u0441\u044F \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0441\u0440\u0430\u0437\u0443" : r.overwritesHuman ? "\u043F\u0435\u0440\u0435\u043F\u0438\u0441\u0430\u043B\u043E \u0431\u044B \u043F\u0440\u0430\u0432\u043A\u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \u0443\u0439\u0434\u0451\u0442 \u0435\u043C\u0443 \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0443, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043F\u0440\u0438\u043C\u0435\u0448\u044C" : "\u0443\u0439\u0434\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0443, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043F\u0440\u0438\u043C\u0435\u0448\u044C (env/zones/spine \u0440\u043E\u0439 \u0441\u0430\u043C \u043D\u0435 \u043F\u0438\u0448\u0435\u0442)";
|
|
6096
|
+
return [
|
|
6097
|
+
`\u2022 ${r.field} \u2014 ${who}${on}. ${fate}.`,
|
|
6098
|
+
` \u043F\u043E\u0447\u0435\u043C\u0443: ${r.why?.trim() || "(\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0430 \u2014 \u044D\u0442\u043E \u0441\u0430\u043C\u043E \u043F\u043E \u0441\u0435\u0431\u0435 \u043F\u043E\u0432\u043E\u0434 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C)"}`,
|
|
6099
|
+
` \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435: ${ruleValueLine(r.value)}`
|
|
6100
|
+
].join("\n");
|
|
6101
|
+
});
|
|
6102
|
+
return [
|
|
6103
|
+
"\u{1F4CC} \u041F\u0420\u0410\u0412\u0418\u041B\u0410 \u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0418, \u041F\u0420\u0418\u041D\u0415\u0421\u0401\u041D\u041D\u042B\u0415 \u0414\u0420\u041E\u041D\u041E\u041C (TASWM-431) \u2014 \u0440\u0435\u0448\u0430\u0435\u0448\u044C \u0442\u044B, \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C.",
|
|
6104
|
+
"\u0414\u0440\u043E\u043D \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0435\u0442 \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0442\u043E \u0432 \u043A\u043E\u0434\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u0440\u0430\u0437\u0432\u0435\u0434\u0447\u0438\u043A \u0442\u0430\u043A\u043E\u0435 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u0438\u0442). \u0422\u044B \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043B \u0434\u0438\u0444\u0444 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0432\u0438\u0434\u0438\u0448\u044C \u0447\u0438\u0441\u043B\u043E \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439 \u0438 \u0447\u0442\u043E \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u043B \u2014 \u0442\u043E \u0435\u0441\u0442\u044C \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439, \u043A\u0442\u043E \u043E\u0442\u043B\u0438\u0447\u0438\u0442 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E\u0442 \u043E\u0442\u043F\u0438\u0441\u043A\u0438.",
|
|
6105
|
+
...lines,
|
|
6106
|
+
'\u0420\u0435\u0448\u0438 \u043F\u043E \u041A\u0410\u0416\u0414\u041E\u041C\u0423 \u0438 \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u0432 done: `rule_verdicts: [{ field: "verify", apply: true }, \u2026]`. apply=true \u2014 \xAB\u043F\u0440\u0430\u0432\u0434\u0430, \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0434\u0440\u043E\u043D \u043E\u0431\u044F\u0437\u0430\u043D \u044D\u0442\u043E \u0437\u043D\u0430\u0442\u044C\xBB; apply=false \u2014 \u043E\u0442\u043F\u0438\u0441\u043A\u0430/\u043E\u0431\u0449\u0435\u0435 \u043C\u0435\u0441\u0442\u043E (\xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0438\u0437 README\xBB), \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u0441\u043D\u0438\u043C\u0435\u0442\u0441\u044F. \u041D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D \u2014 apply=false: \u043F\u0440\u0430\u0432\u0438\u043B\u043E, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435\u0442, \u0441\u0442\u043E\u0438\u0442 \u0434\u0435\u0448\u0435\u0432\u043B\u0435 \u043D\u0435\u0432\u0435\u0440\u043D\u043E\u0433\u043E.',
|
|
6107
|
+
"\u041C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 \u2014 \u0442\u043E\u0436\u0435 \u0438\u0441\u0445\u043E\u0434: \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043B \u0440\u0435\u0448\u0435\u043D\u0438\u0435, \u0438 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u0432\u0438\u0441\u0435\u0442\u044C \u043D\u0430 \u043F\u0440\u0438\u0451\u043C\u043A\u0435 \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u042D\u0442\u043E \u043D\u0435 \u043E\u0448\u0438\u0431\u043A\u0430, \u043D\u043E \u0438 \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430."
|
|
6108
|
+
].join("\n");
|
|
6109
|
+
}
|
|
6110
|
+
|
|
5979
6111
|
// src/logTail.ts
|
|
5980
|
-
import { closeSync, existsSync as
|
|
6112
|
+
import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
6113
|
+
import { join as join7 } from "path";
|
|
6114
|
+
|
|
6115
|
+
// src/runLogDir.ts
|
|
6116
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
5981
6117
|
import { join as join6 } from "path";
|
|
6118
|
+
var LEGACY_RUN_LOGS_SUBDIR = "legacy";
|
|
6119
|
+
function runLogDirName(runLogKey) {
|
|
6120
|
+
const safe2 = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
6121
|
+
if (!safe2) throw new Error(`\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435 \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0432 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 run-log'\u043E\u0432: ${JSON.stringify(runLogKey)}`);
|
|
6122
|
+
return /^\d+$/.test(safe2) ? `k-${safe2}` : safe2;
|
|
6123
|
+
}
|
|
6124
|
+
function runLogDirPath(runLogsDir, runLogKey) {
|
|
6125
|
+
return join6(runLogsDir, runLogDirName(runLogKey));
|
|
6126
|
+
}
|
|
6127
|
+
function findRunLogDir(runLogsDir, keys) {
|
|
6128
|
+
for (const key of keys) {
|
|
6129
|
+
const dir = runLogDirPath(runLogsDir, key);
|
|
6130
|
+
if (existsSync4(dir)) return dir;
|
|
6131
|
+
}
|
|
6132
|
+
return null;
|
|
6133
|
+
}
|
|
6134
|
+
function runLogDirForWrite(runLogsDir, keys) {
|
|
6135
|
+
const canonical = keys[0];
|
|
6136
|
+
if (!canonical) return null;
|
|
6137
|
+
return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
|
|
6138
|
+
}
|
|
6139
|
+
function sweepLegacyRunLogs(runLogsDir) {
|
|
6140
|
+
const legacyDir = join6(runLogsDir, LEGACY_RUN_LOGS_SUBDIR);
|
|
6141
|
+
let entries;
|
|
6142
|
+
try {
|
|
6143
|
+
entries = readdirSync4(runLogsDir);
|
|
6144
|
+
} catch {
|
|
6145
|
+
return { moved: 0, failed: 0, legacyDir };
|
|
6146
|
+
}
|
|
6147
|
+
let moved = 0;
|
|
6148
|
+
let failed = 0;
|
|
6149
|
+
for (const name of entries) {
|
|
6150
|
+
if (!/^\d+$/.test(name)) continue;
|
|
6151
|
+
const from = join6(runLogsDir, name);
|
|
6152
|
+
try {
|
|
6153
|
+
if (!statSync3(from).isDirectory()) continue;
|
|
6154
|
+
mkdirSync4(legacyDir, { recursive: true });
|
|
6155
|
+
let to = join6(legacyDir, name);
|
|
6156
|
+
for (let n = 2; existsSync4(to) && n < 100; n++) to = join6(legacyDir, `${name}-${n}`);
|
|
6157
|
+
renameSync3(from, to);
|
|
6158
|
+
moved++;
|
|
6159
|
+
} catch {
|
|
6160
|
+
failed++;
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
return { moved, failed, legacyDir };
|
|
6164
|
+
}
|
|
6165
|
+
|
|
6166
|
+
// src/logTail.ts
|
|
5982
6167
|
var TAIL_LINES_FALLBACK = 200;
|
|
5983
6168
|
var TAIL_MAX_BYTES_FALLBACK = 64 * 1024;
|
|
5984
6169
|
function fileHeader(name, size, shown) {
|
|
@@ -5988,7 +6173,7 @@ function fileHeader(name, size, shown) {
|
|
|
5988
6173
|
function readTail(path, opts) {
|
|
5989
6174
|
let size;
|
|
5990
6175
|
try {
|
|
5991
|
-
size =
|
|
6176
|
+
size = statSync4(path).size;
|
|
5992
6177
|
} catch {
|
|
5993
6178
|
return null;
|
|
5994
6179
|
}
|
|
@@ -6018,11 +6203,12 @@ function readTail(path, opts) {
|
|
|
6018
6203
|
text = rows.join("\n");
|
|
6019
6204
|
return { text, size, shown: Buffer.byteLength(text, "utf8") };
|
|
6020
6205
|
}
|
|
6021
|
-
function latestRunLaunch(runLogsDir,
|
|
6022
|
-
const dir =
|
|
6206
|
+
function latestRunLaunch(runLogsDir, runLogKeys) {
|
|
6207
|
+
const dir = findRunLogDir(runLogsDir, runLogKeys);
|
|
6208
|
+
if (!dir) return null;
|
|
6023
6209
|
let files;
|
|
6024
6210
|
try {
|
|
6025
|
-
files =
|
|
6211
|
+
files = readdirSync5(dir);
|
|
6026
6212
|
} catch {
|
|
6027
6213
|
return null;
|
|
6028
6214
|
}
|
|
@@ -6033,13 +6219,17 @@ function latestRunLaunch(runLogsDir, localRunId) {
|
|
|
6033
6219
|
}
|
|
6034
6220
|
if (indexes.size === 0) return null;
|
|
6035
6221
|
const index = Math.max(...indexes);
|
|
6036
|
-
const out = [
|
|
6037
|
-
const err =
|
|
6222
|
+
const out = [join7(dir, `${index}.json`), join7(dir, `${index}.json.done`)].find((p) => existsSync5(p)) ?? null;
|
|
6223
|
+
const err = existsSync5(join7(dir, `${index}.err.log`)) ? join7(dir, `${index}.err.log`) : null;
|
|
6038
6224
|
return { index, total: indexes.size, out, err };
|
|
6039
6225
|
}
|
|
6040
|
-
function runLogTail(runLogsDir,
|
|
6041
|
-
const launch = latestRunLaunch(runLogsDir,
|
|
6042
|
-
if (!launch)
|
|
6226
|
+
function runLogTail(runLogsDir, runLogKeys, opts) {
|
|
6227
|
+
const launch = latestRunLaunch(runLogsDir, runLogKeys);
|
|
6228
|
+
if (!launch) {
|
|
6229
|
+
const canonical = runLogKeys[0];
|
|
6230
|
+
const where = canonical ? runLogDirPath(runLogsDir, canonical) : runLogsDir;
|
|
6231
|
+
return { error: `\u043B\u043E\u0433\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442 (${where} \u043F\u0443\u0441\u0442 \u0438\u043B\u0438 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442)` };
|
|
6232
|
+
}
|
|
6043
6233
|
const parts = [];
|
|
6044
6234
|
let budget = opts.maxBytes;
|
|
6045
6235
|
if (launch.err) {
|
|
@@ -6094,8 +6284,8 @@ async function serveLogRequests(deps, commands) {
|
|
|
6094
6284
|
} else if (!cmd.runId) {
|
|
6095
6285
|
result = { error: "\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0431\u0435\u0437 runId \u2014 \u043D\u0435\u0447\u0435\u0433\u043E \u0447\u0438\u0442\u0430\u0442\u044C" };
|
|
6096
6286
|
} else {
|
|
6097
|
-
const
|
|
6098
|
-
result =
|
|
6287
|
+
const keys = deps.runLogKeys(cmd.runId);
|
|
6288
|
+
result = keys.length === 0 ? { error: `\u043F\u0440\u043E\u0433\u043E\u043D ${cmd.runId} \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D (\u0435\u0433\u043E \u0432\u0435\u043B\u043E \u043D\u0435 \u043E\u043D\u0430?)` } : runLogTail(deps.runLogsDir, keys, opts);
|
|
6099
6289
|
}
|
|
6100
6290
|
} catch (e) {
|
|
6101
6291
|
result = { error: `\u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043B\u043E\u0433: ${e instanceof Error ? e.message : String(e)}` };
|
|
@@ -6191,6 +6381,11 @@ var SwarmClient = class _SwarmClient {
|
|
|
6191
6381
|
// Живёт в памяти: рестарт демона просто переспросит прод.
|
|
6192
6382
|
depsProd = /* @__PURE__ */ new Map();
|
|
6193
6383
|
depsAskedAt = /* @__PURE__ */ new Map();
|
|
6384
|
+
// TASWM-399: ссылка dependsOn → человеко-слаг блокера, как его назвал прод. В колонке
|
|
6385
|
+
// лежит cuid прогона или задачи, а в лог демона, в событие прогона и в аномалию должен
|
|
6386
|
+
// уходить «TASWM-392», а не «cmsqqpphd011y2xtbckkut6bj» — иначе оператору всё равно
|
|
6387
|
+
// нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
|
|
6388
|
+
depSlug = /* @__PURE__ */ new Map();
|
|
6194
6389
|
static DEPS_TTL_MS = 6e4;
|
|
6195
6390
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
6196
6391
|
// ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
|
|
@@ -6250,25 +6445,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
6250
6445
|
this.repoFullNames = registry.map((e) => e.repoFullName);
|
|
6251
6446
|
this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
|
|
6252
6447
|
this.host = opts.host ?? null;
|
|
6253
|
-
this.repoExists = opts.repoExists ??
|
|
6448
|
+
this.repoExists = opts.repoExists ?? existsSync6;
|
|
6254
6449
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
6255
6450
|
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
6256
6451
|
this.outboxPath = opts.outboxPath;
|
|
6257
6452
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
6258
6453
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
6259
|
-
|
|
6454
|
+
mkdirSync5(dirname(this.outboxPath), { recursive: true });
|
|
6260
6455
|
this.recover();
|
|
6261
6456
|
}
|
|
6262
6457
|
// ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
|
|
6263
6458
|
recover() {
|
|
6264
|
-
if (
|
|
6459
|
+
if (existsSync6(this.mapPath)) {
|
|
6265
6460
|
try {
|
|
6266
6461
|
const obj = JSON.parse(readFileSync4(this.mapPath, "utf8"));
|
|
6267
6462
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
6268
6463
|
} catch {
|
|
6269
6464
|
}
|
|
6270
6465
|
}
|
|
6271
|
-
if (
|
|
6466
|
+
if (existsSync6(this.outboxPath)) {
|
|
6272
6467
|
const txt = readFileSync4(this.outboxPath, "utf8");
|
|
6273
6468
|
for (const line of txt.split("\n")) {
|
|
6274
6469
|
const s = line.trim();
|
|
@@ -6289,7 +6484,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6289
6484
|
// eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
|
|
6290
6485
|
// переприсваиваем локально (id событий — только порядок, не корреляция с продом).
|
|
6291
6486
|
replayGlobalEvents() {
|
|
6292
|
-
if (!
|
|
6487
|
+
if (!existsSync6(this.stormPath)) return;
|
|
6293
6488
|
let txt;
|
|
6294
6489
|
try {
|
|
6295
6490
|
txt = readFileSync4(this.stormPath, "utf8");
|
|
@@ -6554,6 +6749,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
6554
6749
|
const viaProd = [...this.runs.values()].find((r) => r.runKey === `p:${prodId}` || this.prodIdByKey.get(r.runKey) === prodId);
|
|
6555
6750
|
return viaProd ? this.mat(viaProd) : null;
|
|
6556
6751
|
}
|
|
6752
|
+
/**
|
|
6753
|
+
* TASWM-419: адреса каталога run-log'ов прогона. КАНОНИЧЕСКИЙ — прод-вида `p:<cuid>`, как
|
|
6754
|
+
* только cuid известен: он один и тот же во всех жизнях демона, а локальный uuid прогона
|
|
6755
|
+
* после рестарта уже не воскресает (гидрированный прогон приезжает под прод-ключом).
|
|
6756
|
+
* Дальше — псевдонимы: uuid, под которым каталог мог завестись в окно между спавном и
|
|
6757
|
+
* первым флашем в прод. По ним каталог ищется, чтобы запуски одного прогона не расползлись
|
|
6758
|
+
* по двум каталогам (иначе cost недосчитал бы часть, а хвост показал бы не тот запуск).
|
|
6759
|
+
* Номер прогона в адресе не участвует вовсе — он и есть источник «лога чужого прогона».
|
|
6760
|
+
*/
|
|
6761
|
+
runLogKeys(id) {
|
|
6762
|
+
const key = this.runs.get(id)?.runKey;
|
|
6763
|
+
if (!key) return [];
|
|
6764
|
+
const prodId = key.startsWith("p:") ? key.slice(2) : this.prodIdByKey.get(key);
|
|
6765
|
+
if (!prodId) return [key];
|
|
6766
|
+
const canonical = `p:${prodId}`;
|
|
6767
|
+
const aliases = [key, ...[...this.prodIdByKey.entries()].filter(([, v]) => v === prodId).map(([k]) => k)];
|
|
6768
|
+
return [canonical, ...new Set(aliases.filter((k) => k !== canonical))];
|
|
6769
|
+
}
|
|
6557
6770
|
listRuns(status) {
|
|
6558
6771
|
const all = [...this.runs.values()].filter((r) => status ? r.status === status : true);
|
|
6559
6772
|
all.sort((a, b) => b.id - a.id);
|
|
@@ -6670,16 +6883,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
6670
6883
|
/**
|
|
6671
6884
|
* TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
|
|
6672
6885
|
*
|
|
6673
|
-
*
|
|
6674
|
-
* (
|
|
6675
|
-
* стоят разного:
|
|
6676
|
-
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
|
|
6677
|
-
* портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
6678
|
-
* •
|
|
6679
|
-
* на соседней машине как раз и может быть
|
|
6680
|
-
*
|
|
6681
|
-
*
|
|
6682
|
-
*
|
|
6886
|
+
* ИСТОЧНИК ПРАВДЫ — ПРОД (TASWM-399), зеркало здесь только быстрый путь. Зеркало —
|
|
6887
|
+
* ЧАСТИЧНАЯ выборка (прогоны этой машины, окно гидрации), и по частичной выборке два
|
|
6888
|
+
* вывода стоят разного:
|
|
6889
|
+
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
|
|
6890
|
+
* не портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
6891
|
+
* • всё остальное — вывод от ОТСУТСТВИЯ, и его принимаем ТОЛЬКО из ответа прода
|
|
6892
|
+
* (refreshDeps): невидимый зеркалу прогон на соседней машине как раз и может быть
|
|
6893
|
+
* живым, а закрытый с прода — давно смерженным.
|
|
6894
|
+
*
|
|
6895
|
+
* ССЫЛКА — НЕ ОБЯЗАТЕЛЬНО СЛАГ. В `dependsOn` лежит чаще всего cuid ПРОГОНА (так пишет
|
|
6896
|
+
* overmind), реже cuid задачи, и только дев-спавн `--after` пишет человеко-слаг. Быстрый
|
|
6897
|
+
* путь по зеркалу сравнивает `task_slug`, то есть срабатывает лишь на третьей форме —
|
|
6898
|
+
* это нормально: остальные две резолвит прод. А вот считать «раз в зеркале не нашёл —
|
|
6899
|
+
* значит не готово» было корнем TASWM-399.
|
|
6900
|
+
*
|
|
6901
|
+
* Прода ещё не спрашивали (первый тик) или он моргнул → `pending`: прогон честно ждёт
|
|
6902
|
+
* следующего окна. Лишняя минута очереди дешевле дрона, поднятого поверх работающего
|
|
6903
|
+
* блокера. `unknown` (блокера НЕ ЗНАЕТ И ПРОД) ставит только ответ прода — местным
|
|
6904
|
+
* незнанием такой вывод не делается.
|
|
6683
6905
|
*/
|
|
6684
6906
|
depVerdicts(depends_on) {
|
|
6685
6907
|
if (!depends_on || depends_on.length === 0) return [];
|
|
@@ -6689,57 +6911,89 @@ var SwarmClient = class _SwarmClient {
|
|
|
6689
6911
|
doneInMirror.add(r.task_slug);
|
|
6690
6912
|
}
|
|
6691
6913
|
}
|
|
6692
|
-
return depends_on.map((
|
|
6693
|
-
if (doneInMirror.has(
|
|
6694
|
-
return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
6914
|
+
return depends_on.map((ref) => {
|
|
6915
|
+
if (doneInMirror.has(ref)) {
|
|
6916
|
+
return { slug: ref, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
6917
|
+
}
|
|
6918
|
+
const fromProd = this.depsProd.get(ref);
|
|
6919
|
+
if (fromProd) {
|
|
6920
|
+
if (fromProd.outcome === "unknown" && this.knowsDepRef(ref)) {
|
|
6921
|
+
return { slug: this.depSlug.get(ref) ?? ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
6922
|
+
}
|
|
6923
|
+
return { slug: this.depSlug.get(ref) ?? ref, ...fromProd };
|
|
6695
6924
|
}
|
|
6696
|
-
|
|
6697
|
-
if (fromProd) return { slug, ...fromProd };
|
|
6698
|
-
return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
6925
|
+
return { slug: ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
6699
6926
|
});
|
|
6700
6927
|
}
|
|
6701
6928
|
/**
|
|
6702
|
-
*
|
|
6929
|
+
* Знает ли ЗЕРКАЛО хоть один прогон по ссылке `dependsOn` (TASWM-399). Ссылка бывает
|
|
6930
|
+
* слагом, cuid'ом задачи и cuid'ом прогона — последний сверяем с прод-id, как это
|
|
6931
|
+
* делают runByProdId/refreshRun. Служебные сабраны (ревьюер/резолвер/верификатор) не
|
|
6932
|
+
* в счёт: их существование не означает, что жив сам блокер.
|
|
6933
|
+
*/
|
|
6934
|
+
knowsDepRef(ref) {
|
|
6935
|
+
for (const r of this.runs.values()) {
|
|
6936
|
+
if (r.resolves_run_id != null || r.reviews_run_id != null || r.verifies_run_id != null) continue;
|
|
6937
|
+
if (r.task_slug === ref) return true;
|
|
6938
|
+
if (r.runKey === `p:${ref}` || this.prodIdByKey.get(r.runKey) === ref) return true;
|
|
6939
|
+
}
|
|
6940
|
+
return false;
|
|
6941
|
+
}
|
|
6942
|
+
/**
|
|
6943
|
+
* TASWM-300 → TASWM-399: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
|
|
6944
|
+
*
|
|
6945
|
+
* Почему зеркала мало. В нём лежат только прогоны ЭТОЙ машины (гейт isMine, hydrateRun) и
|
|
6946
|
+
* только те, что попали в окно гидрации (`swarm_list_runs limit`, сортировка по updatedAt
|
|
6947
|
+
* desc — сутки работы роя). Зависимость, закрытая на соседней машине, обнулившаяся при
|
|
6948
|
+
* рестарте демона или просто позавчерашняя, в зеркало не попадала — и `dependsOn` держал
|
|
6949
|
+
* прогон в очереди вечно.
|
|
6703
6950
|
*
|
|
6704
|
-
*
|
|
6705
|
-
*
|
|
6706
|
-
*
|
|
6707
|
-
*
|
|
6708
|
-
* (
|
|
6951
|
+
* ЧЕМ СПРАШИВАЕМ И ПОЧЕМУ НЕ `swarm_list_runs` (корень TASWM-399). До этой задачи здесь
|
|
6952
|
+
* шёл `swarm_list_runs { taskId: <ref> }` — то есть ссылка трактовалась как ЗАДАЧА. Но в
|
|
6953
|
+
* `dependsOn` чаще всего лежит cuid ПРОГОНА: `resolveTaskRef` его задачей не признаёт,
|
|
6954
|
+
* ответ приходит `isError`, вердикт не появляется НИКОГДА, и прогон стоит молча при
|
|
6955
|
+
* свободной машине (12–13.08 — пять остановок роя за сутки). `swarm_deps_status` понимает
|
|
6956
|
+
* все три формы ссылки и отвечает по единому правилу роя (`judgeDep` на прод-стороне).
|
|
6709
6957
|
*
|
|
6710
6958
|
* Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
|
|
6711
6959
|
* положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
|
|
6712
|
-
* «исчерпана
|
|
6713
|
-
*
|
|
6714
|
-
*
|
|
6960
|
+
* «исчерпана неуспешно» и «блокера не знает никто») переспрашиваем не чаще DEPS_TTL_MS:
|
|
6961
|
+
* оператор вправе переиграть блокер новым прогоном, а тик идёт раз в ~4 с — без TTL это
|
|
6962
|
+
* был бы поход в прод на каждую ссылку каждый тик.
|
|
6963
|
+
*
|
|
6964
|
+
* Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
|
|
6965
|
+
* бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
|
|
6966
|
+
* дороже.
|
|
6715
6967
|
*/
|
|
6716
|
-
async refreshDeps(
|
|
6717
|
-
|
|
6718
|
-
if (!
|
|
6719
|
-
const asked = this.depsAskedAt.get(
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6968
|
+
async refreshDeps(refs, nowMs = Date.now()) {
|
|
6969
|
+
const ask = [...new Set(refs)].filter((ref) => {
|
|
6970
|
+
if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
|
|
6971
|
+
const asked = this.depsAskedAt.get(ref);
|
|
6972
|
+
return asked === void 0 || nowMs - asked >= _SwarmClient.DEPS_TTL_MS;
|
|
6973
|
+
});
|
|
6974
|
+
if (!ask.length) return;
|
|
6975
|
+
for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
|
|
6976
|
+
let res;
|
|
6977
|
+
try {
|
|
6978
|
+
res = await this.call("swarm_deps_status", { refs: ask });
|
|
6979
|
+
} catch {
|
|
6980
|
+
for (const ref of ask) this.depsAskedAt.delete(ref);
|
|
6981
|
+
return;
|
|
6982
|
+
}
|
|
6983
|
+
if (res?.isError) return;
|
|
6984
|
+
const deps = res.structuredContent?.deps ?? [];
|
|
6985
|
+
for (const d of deps) {
|
|
6986
|
+
const ref = typeof d.ref === "string" ? d.ref : null;
|
|
6987
|
+
if (!ref) continue;
|
|
6988
|
+
const outcome = String(d.outcome ?? "pending");
|
|
6989
|
+
this.depsProd.set(ref, {
|
|
6990
|
+
outcome,
|
|
6991
|
+
met: Boolean(d.met),
|
|
6992
|
+
status: d.status ?? null,
|
|
6993
|
+
name: d.name ?? null,
|
|
6994
|
+
stopReason: d.stopReason ?? null
|
|
6995
|
+
});
|
|
6996
|
+
if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
|
|
6743
6997
|
}
|
|
6744
6998
|
}
|
|
6745
6999
|
/**
|
|
@@ -7058,7 +7312,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7058
7312
|
* транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
|
|
7059
7313
|
*/
|
|
7060
7314
|
reconcileRuntime(opts) {
|
|
7061
|
-
const wtExists = opts.worktreeExists ??
|
|
7315
|
+
const wtExists = opts.worktreeExists ?? existsSync6;
|
|
7062
7316
|
const pidByWorktree = /* @__PURE__ */ new Map();
|
|
7063
7317
|
try {
|
|
7064
7318
|
for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
|
|
@@ -7368,6 +7622,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
7368
7622
|
const sc = res.structuredContent ?? {};
|
|
7369
7623
|
return normalizeBaseBranch(sc.config?.baseBranch) ?? null;
|
|
7370
7624
|
}
|
|
7625
|
+
/**
|
|
7626
|
+
* ПРАВИЛА ОТ ДРОНОВ, ждущие вердикта ревьюера (TASWM-431): их демон кладёт в промпт
|
|
7627
|
+
* ревьюера — тот решает по ним в своём же вердикте. `taskSlug` — правила ЭТОГО
|
|
7628
|
+
* прогона: ревьюер отвечает за находки своего дрона, а не за чужие.
|
|
7629
|
+
*
|
|
7630
|
+
* Прод недоступен / старый прод (тула нет) / ответ не той формы → пустой список:
|
|
7631
|
+
* ревью обязано подниматься и без этого блока — правила тут не главное.
|
|
7632
|
+
*/
|
|
7633
|
+
async repoPendingRules(repoFullName, taskSlug) {
|
|
7634
|
+
let res;
|
|
7635
|
+
try {
|
|
7636
|
+
res = await this.call("swarm_repo_rule_pending", { repoFullName, ...taskSlug ? { taskSlug } : {} });
|
|
7637
|
+
} catch {
|
|
7638
|
+
return [];
|
|
7639
|
+
}
|
|
7640
|
+
if (res?.isError) return [];
|
|
7641
|
+
const sc = res.structuredContent ?? {};
|
|
7642
|
+
return parsePendingRepoRules(sc.rules);
|
|
7643
|
+
}
|
|
7371
7644
|
/**
|
|
7372
7645
|
* Live КОМАНДЫ ПОДГОТОВКИ worktree из Taskless (TASWM-312): тот же swarm_repo_config по
|
|
7373
7646
|
* repoFullName — Project.swarmConfig.prepare. Настройка сильнее детекта демона (deps.ts):
|
|
@@ -7411,29 +7684,11 @@ var SwarmClient = class _SwarmClient {
|
|
|
7411
7684
|
const p = sc.profile;
|
|
7412
7685
|
return p && typeof p === "object" && !Array.isArray(p) ? p : null;
|
|
7413
7686
|
}
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
*
|
|
7420
|
-
* Мимо outbox (в отличие от мутаций прогонов): предложение имеет смысл только вместе
|
|
7421
|
-
* со свежим состоянием репозитория, и доехавшее через сутки после разведки — хуже,
|
|
7422
|
-
* чем не доехавшее вовсе. Прод недоступен — человек перезапустит разведку.
|
|
7423
|
-
*/
|
|
7424
|
-
async reportRepoScout(repoFullName, report, by) {
|
|
7425
|
-
let res;
|
|
7426
|
-
try {
|
|
7427
|
-
res = await this.call("swarm_repo_scout_report", { repoFullName, report, ...by ? { by } : {} });
|
|
7428
|
-
} catch (e) {
|
|
7429
|
-
return { ok: false, error: `\u043F\u0440\u043E\u0434 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${e.message}` };
|
|
7430
|
-
}
|
|
7431
|
-
const text = res?.content?.map((c) => "text" in c ? c.text : "").join(" ") ?? "";
|
|
7432
|
-
if (res?.isError) return { ok: false, error: text || "\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0440\u0430\u0437\u0432\u0435\u0434\u043A\u0438" };
|
|
7433
|
-
const sc = res.structuredContent ?? {};
|
|
7434
|
-
const diff = Array.isArray(sc.diff) ? sc.diff : [];
|
|
7435
|
-
return { ok: true, diff, display: text };
|
|
7436
|
-
}
|
|
7687
|
+
// TASWM-441: отправка предложения разведки в прод (`swarm_repo_scout_report`) убрана —
|
|
7688
|
+
// разведчик больше не предлагает разметку в базу, а ведёт файл В САМОМ репозитории
|
|
7689
|
+
// (docs/AGENT_BRIEF.md, см. repoScout.ts). Прод-сторона тула и экран приёмки уезжают
|
|
7690
|
+
// отдельной задачей TASWM-442; `repoProfile` выше — её же остаток, читателей у него
|
|
7691
|
+
// в демоне не осталось.
|
|
7437
7692
|
/**
|
|
7438
7693
|
* Live ТРЕБОВАНИЯ К ОКРУЖЕНИЮ машины из Taskless (TASWM-313): тот же swarm_repo_config по
|
|
7439
7694
|
* repoFullName — Project.swarmConfig.requires. Демон проверяет их ПЕРЕД спавном дрона и не
|
|
@@ -7547,6 +7802,12 @@ var SwarmClient = class _SwarmClient {
|
|
|
7547
7802
|
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
7548
7803
|
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
7549
7804
|
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
7805
|
+
// TASWM-424: «доступ отозван» — состояние ровно того же рода, что пауза, и
|
|
7806
|
+
// формулировка у него ОДНА на все поверхности (HOST_ACCESS_REVOKED_TEXT):
|
|
7807
|
+
// `hostLines` зовёт тот же `hostStateText`, что и парк машин в /swarm. Без
|
|
7808
|
+
// этого поля `swarm hosts` показывал отвергнутую машину просто молчащей —
|
|
7809
|
+
// то самое различие, ради которого флаг и заводили (TASWM-324).
|
|
7810
|
+
...typeof h.accessRevoked === "boolean" ? { accessRevoked: h.accessRevoked } : {},
|
|
7550
7811
|
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
7551
7812
|
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
7552
7813
|
...resources ? { resources } : {}
|
|
@@ -7632,9 +7893,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
7632
7893
|
* ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
|
|
7633
7894
|
* означало бы «прод помнит вчерашний шторм», когда рой давно работает.
|
|
7634
7895
|
*
|
|
7896
|
+
* TASWM-402: тем же пульсом уезжают КОРНИ WORKTREE'ЕВ этой машины (`worktreeRoots` —
|
|
7897
|
+
* `owner/repo` → `<worktreesDir>/<repo-basename>`). Это НЕ рантайм и правило «pid и путь
|
|
7898
|
+
* дрона в прод не уходят» не трогает: корень — статичная конфигурация машины, того же
|
|
7899
|
+
* рода, что реестр репо и ёмкость, он не зависит ни от прогона, ни от живости процесса и
|
|
7900
|
+
* не протухает между пульсами. Прод склеивает `<корень>/<слаг>` сам — и отвечает человеку
|
|
7901
|
+
* на вопрос «куда идти руками», когда команда машине не доиграла (а в этом случае, по
|
|
7902
|
+
* определению, спросить рантайм у молчащей машины уже нельзя). Не передали корень (не
|
|
7903
|
+
* знаем своего каталога) → ключа нет, прод прошлое не трогает — как с capacity/resources.
|
|
7904
|
+
*
|
|
7635
7905
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
7636
7906
|
*/
|
|
7637
|
-
async heartbeat(daemonVersion, pause, capacity, resources, storm, signIn) {
|
|
7907
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn, tick) {
|
|
7638
7908
|
try {
|
|
7639
7909
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
7640
7910
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -7652,10 +7922,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
7652
7922
|
args.storm = storm.active;
|
|
7653
7923
|
if (storm.reason) args.stormReason = storm.reason;
|
|
7654
7924
|
}
|
|
7925
|
+
if (worktreesDir) {
|
|
7926
|
+
const roots = {};
|
|
7927
|
+
for (const [full, path] of this.repoPathByFullName) roots[full] = droneWorktreeRoot(worktreesDir, path);
|
|
7928
|
+
if (Object.keys(roots).length) args.worktreeRoots = roots;
|
|
7929
|
+
}
|
|
7655
7930
|
if (this.host) {
|
|
7656
7931
|
args.hostId = this.host.hostId;
|
|
7657
7932
|
args.hostName = this.host.hostName;
|
|
7658
7933
|
}
|
|
7934
|
+
if (tick && Number.isFinite(tick.seq) && Number.isFinite(tick.ageMs) && tick.intervalMs > 0) {
|
|
7935
|
+
args.tick = { seq: Math.max(0, Math.trunc(tick.seq)), ageMs: Math.max(0, Math.trunc(tick.ageMs)), intervalMs: Math.trunc(tick.intervalMs) };
|
|
7936
|
+
}
|
|
7659
7937
|
const res = await this.call("swarm_heartbeat", args);
|
|
7660
7938
|
if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
|
|
7661
7939
|
this.heartbeatOkAt = this.now();
|
|
@@ -7800,10 +8078,20 @@ var SwarmClient = class _SwarmClient {
|
|
|
7800
8078
|
* именно путь. Не нашли в реестре (репо не наш / ручной `--repo <path>` спавн, где
|
|
7801
8079
|
* repo — ВСЕГДА уже путь, старый формат) → возвращаем как есть без изменений
|
|
7802
8080
|
* (обратная совместимость: раньше pr.repo ВСЕГДА был локальным путём).
|
|
8081
|
+
*
|
|
8082
|
+
* РЕГИСТР НЕ РАЗЛИЧАЕМ (TASWM-422). Ключи карты приведены к нижнему регистру
|
|
8083
|
+
* (parseRepoFullName — «GitHub сам не различает case в owner/repo»), а прод присылает
|
|
8084
|
+
* `run.repo` тем регистром, каким репозиторий записан в проекте. 17.08 это разошлось:
|
|
8085
|
+
* машина объявляла `ilyasmurov/ansamblist`, задача пришла с `ilyasmurov/Ansamblist`,
|
|
8086
|
+
* `Map.get` промахнулся — и вместо пути в прогон уехало полное имя репозитория. Дальше
|
|
8087
|
+
* git-операции шли в несуществующий каталог, прогон умирал за 5 секунд, а задача
|
|
8088
|
+
* возвращалась в очередь и бралась снова: пять павших прогонов за пять минут.
|
|
8089
|
+
* Возвращаем ИСХОДНУЮ строку, а не приведённую: промах бывает и на локальном пути
|
|
8090
|
+
* (ручной спавн), а путь регистр различает — вернув lowercase, мы сломали бы его.
|
|
7803
8091
|
*/
|
|
7804
8092
|
resolveRepo(raw) {
|
|
7805
8093
|
if (!raw) return "";
|
|
7806
|
-
return this.repoPathByFullName.get(raw) ?? raw;
|
|
8094
|
+
return this.repoPathByFullName.get(raw) ?? this.repoPathByFullName.get(raw.toLowerCase()) ?? raw;
|
|
7807
8095
|
}
|
|
7808
8096
|
/**
|
|
7809
8097
|
* TASWM-281: МОЯ ли это работа. Три исхода:
|
|
@@ -8243,21 +8531,49 @@ function parseDecision(v) {
|
|
|
8243
8531
|
init_config();
|
|
8244
8532
|
init_heartbeat();
|
|
8245
8533
|
|
|
8534
|
+
// src/tickHealth.ts
|
|
8535
|
+
init_src();
|
|
8536
|
+
function initialTickHealth(now) {
|
|
8537
|
+
return { seq: 0, lastAt: null, startedAt: now, lastError: null };
|
|
8538
|
+
}
|
|
8539
|
+
function markTickDone(health, now, error) {
|
|
8540
|
+
health.seq += 1;
|
|
8541
|
+
health.lastAt = now;
|
|
8542
|
+
health.lastError = error === void 0 ? null : describeTickError(error);
|
|
8543
|
+
}
|
|
8544
|
+
function describeTickError(error) {
|
|
8545
|
+
if (error instanceof Error) return error.message || error.name;
|
|
8546
|
+
return String(error);
|
|
8547
|
+
}
|
|
8548
|
+
function tickAgeMs(health, now) {
|
|
8549
|
+
return Math.max(0, now - (health.lastAt ?? health.startedAt));
|
|
8550
|
+
}
|
|
8551
|
+
var TICK_SELF_HEAL_FLOOR_MS = 6e5;
|
|
8552
|
+
function tickSelfHealMs(intervalMs) {
|
|
8553
|
+
return Math.max(hostTickStallMs(intervalMs) * 2, TICK_SELF_HEAL_FLOOR_MS);
|
|
8554
|
+
}
|
|
8555
|
+
function tickSelfHealDue(health, now, intervalMs) {
|
|
8556
|
+
return tickAgeMs(health, now) > tickSelfHealMs(intervalMs);
|
|
8557
|
+
}
|
|
8558
|
+
function tickPulse(health, now, intervalMs) {
|
|
8559
|
+
return { seq: health.seq, ageMs: tickAgeMs(health, now), intervalMs };
|
|
8560
|
+
}
|
|
8561
|
+
|
|
8246
8562
|
// src/logRotate.ts
|
|
8247
|
-
import { copyFileSync, renameSync as
|
|
8563
|
+
import { copyFileSync, renameSync as renameSync5, statSync as statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
|
|
8248
8564
|
var realIo = {
|
|
8249
8565
|
size: (path) => {
|
|
8250
8566
|
try {
|
|
8251
|
-
return
|
|
8567
|
+
return statSync5(path).size;
|
|
8252
8568
|
} catch {
|
|
8253
8569
|
return null;
|
|
8254
8570
|
}
|
|
8255
8571
|
},
|
|
8256
8572
|
copy: copyFileSync,
|
|
8257
|
-
rename:
|
|
8573
|
+
rename: renameSync5,
|
|
8258
8574
|
truncate: (path) => truncateSync(path, 0),
|
|
8259
8575
|
unlink: unlinkSync2,
|
|
8260
|
-
exists:
|
|
8576
|
+
exists: existsSync7
|
|
8261
8577
|
};
|
|
8262
8578
|
var realOwnerIo = {
|
|
8263
8579
|
statFd: (fd) => {
|
|
@@ -8270,7 +8586,7 @@ var realOwnerIo = {
|
|
|
8270
8586
|
},
|
|
8271
8587
|
statPath: (path) => {
|
|
8272
8588
|
try {
|
|
8273
|
-
const s =
|
|
8589
|
+
const s = statSync5(path);
|
|
8274
8590
|
return { dev: s.dev, ino: s.ino };
|
|
8275
8591
|
} catch {
|
|
8276
8592
|
return null;
|
|
@@ -8342,12 +8658,12 @@ async function buildRepoRegistry(paths) {
|
|
|
8342
8658
|
|
|
8343
8659
|
// src/repoMemory.ts
|
|
8344
8660
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
8345
|
-
import { join as
|
|
8661
|
+
import { join as join8 } from "path";
|
|
8346
8662
|
var FILE = "repos.json";
|
|
8347
8663
|
function loadRememberedRepos(home) {
|
|
8348
8664
|
let raw;
|
|
8349
8665
|
try {
|
|
8350
|
-
raw = readFileSync6(
|
|
8666
|
+
raw = readFileSync6(join8(home, FILE), "utf8");
|
|
8351
8667
|
} catch {
|
|
8352
8668
|
return [];
|
|
8353
8669
|
}
|
|
@@ -8362,7 +8678,7 @@ function loadRememberedRepos(home) {
|
|
|
8362
8678
|
function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
|
|
8363
8679
|
const clean = paths.filter((p) => p.trim().length > 0);
|
|
8364
8680
|
if (clean.length === 0) return;
|
|
8365
|
-
writeFileSync6(
|
|
8681
|
+
writeFileSync6(join8(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
|
|
8366
8682
|
`);
|
|
8367
8683
|
}
|
|
8368
8684
|
function pickRepoPaths(args) {
|
|
@@ -8377,12 +8693,12 @@ init_src();
|
|
|
8377
8693
|
init_config();
|
|
8378
8694
|
init_linkFailure();
|
|
8379
8695
|
import { homedir as homedir3 } from "os";
|
|
8380
|
-
import { readFileSync as readFileSync9, renameSync as
|
|
8381
|
-
import { isAbsolute, join as
|
|
8696
|
+
import { readFileSync as readFileSync9, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
8697
|
+
import { isAbsolute, join as join11, resolve as resolve2 } from "path";
|
|
8382
8698
|
|
|
8383
8699
|
// src/repoScan.ts
|
|
8384
|
-
import { readdirSync as
|
|
8385
|
-
import { join as
|
|
8700
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
8701
|
+
import { join as join10 } from "path";
|
|
8386
8702
|
var SKIP = /* @__PURE__ */ new Set([
|
|
8387
8703
|
"node_modules",
|
|
8388
8704
|
"vendor",
|
|
@@ -8412,7 +8728,7 @@ function parseOriginFromGitConfig(text) {
|
|
|
8412
8728
|
}
|
|
8413
8729
|
function originOf(dir) {
|
|
8414
8730
|
try {
|
|
8415
|
-
return parseOriginFromGitConfig(readFileSync8(
|
|
8731
|
+
return parseOriginFromGitConfig(readFileSync8(join10(dir, ".git", "config"), "utf8"));
|
|
8416
8732
|
} catch {
|
|
8417
8733
|
return null;
|
|
8418
8734
|
}
|
|
@@ -8441,15 +8757,15 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
8441
8757
|
}
|
|
8442
8758
|
let entries;
|
|
8443
8759
|
try {
|
|
8444
|
-
entries =
|
|
8760
|
+
entries = readdirSync6(dir);
|
|
8445
8761
|
} catch {
|
|
8446
8762
|
return;
|
|
8447
8763
|
}
|
|
8448
8764
|
for (const name of entries) {
|
|
8449
8765
|
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
8450
|
-
const child =
|
|
8766
|
+
const child = join10(dir, name);
|
|
8451
8767
|
try {
|
|
8452
|
-
if (!
|
|
8768
|
+
if (!statSync6(child).isDirectory()) continue;
|
|
8453
8769
|
} catch {
|
|
8454
8770
|
continue;
|
|
8455
8771
|
}
|
|
@@ -8469,7 +8785,7 @@ function defaultScanRoots(home, knownPaths = []) {
|
|
|
8469
8785
|
if (parent && parent !== p) add(parent);
|
|
8470
8786
|
}
|
|
8471
8787
|
for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
|
|
8472
|
-
add(
|
|
8788
|
+
add(join10(home, name));
|
|
8473
8789
|
}
|
|
8474
8790
|
return out;
|
|
8475
8791
|
}
|
|
@@ -8483,7 +8799,7 @@ function resolveScanRoots(raw, home = homedir3(), knownPaths = []) {
|
|
|
8483
8799
|
for (const part of v.split(",")) {
|
|
8484
8800
|
const p = part.trim();
|
|
8485
8801
|
if (!p) continue;
|
|
8486
|
-
const abs = p === "~" ? home : p.startsWith("~/") ?
|
|
8802
|
+
const abs = p === "~" ? home : p.startsWith("~/") ? join11(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
|
|
8487
8803
|
if (!out.includes(abs)) out.push(abs);
|
|
8488
8804
|
}
|
|
8489
8805
|
return out;
|
|
@@ -8548,6 +8864,28 @@ function describeReposFailure(kind, detail) {
|
|
|
8548
8864
|
function emptyRepoMapState(roots) {
|
|
8549
8865
|
return { clones: [], missing: [], roots: [...roots], at: null, unknown: true };
|
|
8550
8866
|
}
|
|
8867
|
+
var SCAN_REASON_TEXT = {
|
|
8868
|
+
"list-unknown": "\u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F \u2014 \u0441\u043C\u043E\u0442\u0440\u044E \u0441\u0430\u043C, \u0447\u0435\u0433\u043E \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043F\u0440\u0438\u0431\u0430\u0432\u0438\u043B\u043E\u0441\u044C",
|
|
8869
|
+
"wanted-missing": "\u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0440\u043E\u044F \u0447\u0435\u0433\u043E-\u0442\u043E \u043D\u0435\u0442 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435",
|
|
8870
|
+
"cache-expired": "\u043A\u044D\u0448 \u043A\u0430\u0440\u0442\u044B \u043F\u0440\u043E\u0442\u0443\u0445"
|
|
8871
|
+
};
|
|
8872
|
+
var REPO_SCAN_TTL_MS = 24 * 60 * 6e4;
|
|
8873
|
+
var UNKNOWN_LIST_SCAN_TTL_MS = 60 * 6e4;
|
|
8874
|
+
function isScanStale(scannedAt, ttlMs, now = Date.now()) {
|
|
8875
|
+
if (!scannedAt) return true;
|
|
8876
|
+
const t = stampMs(scannedAt);
|
|
8877
|
+
if (!Number.isFinite(t)) return true;
|
|
8878
|
+
return now - t >= ttlMs;
|
|
8879
|
+
}
|
|
8880
|
+
function decideScan(opts) {
|
|
8881
|
+
if (!opts.known) {
|
|
8882
|
+
const ttl = Math.min(opts.ttlMs, UNKNOWN_LIST_SCAN_TTL_MS);
|
|
8883
|
+
return isScanStale(opts.scannedAt, ttl, opts.now) ? { scan: true, reason: "list-unknown" } : { scan: false, reason: null };
|
|
8884
|
+
}
|
|
8885
|
+
if (opts.missingWanted > 0) return { scan: true, reason: "wanted-missing" };
|
|
8886
|
+
if (isScanStale(opts.scannedAt, opts.ttlMs, opts.now)) return { scan: true, reason: "cache-expired" };
|
|
8887
|
+
return { scan: false, reason: null };
|
|
8888
|
+
}
|
|
8551
8889
|
async function refreshRepoMap(opts) {
|
|
8552
8890
|
const log = opts.log ?? (() => {
|
|
8553
8891
|
});
|
|
@@ -8561,14 +8899,21 @@ async function refreshRepoMap(opts) {
|
|
|
8561
8899
|
clones.set(repoFullName, { repoFullName, path, source, alternatives });
|
|
8562
8900
|
};
|
|
8563
8901
|
for (const e of opts.configured) take(e.repoFullName.toLowerCase(), e.path, "configured");
|
|
8564
|
-
|
|
8902
|
+
const cache = loadRepoMapCache(opts.cachePath);
|
|
8903
|
+
for (const e of cache.entries) {
|
|
8565
8904
|
const name = e.repoFullName.toLowerCase();
|
|
8566
8905
|
if (clones.has(name)) continue;
|
|
8567
8906
|
if (repoAt(e.path) === name) take(name, e.path, "cache");
|
|
8568
8907
|
}
|
|
8569
8908
|
const wantedNames = wanted ? normalizeFullNames(wanted.map((r) => r.repoFullName)) : [];
|
|
8570
|
-
const
|
|
8571
|
-
|
|
8909
|
+
const decision = decideScan({
|
|
8910
|
+
known: !!wanted,
|
|
8911
|
+
missingWanted: wantedNames.filter((n) => !clones.has(n)).length,
|
|
8912
|
+
scannedAt: cache.scannedAt,
|
|
8913
|
+
ttlMs: opts.scanTtlMs ?? REPO_SCAN_TTL_MS
|
|
8914
|
+
});
|
|
8915
|
+
let scannedAt = cache.scannedAt;
|
|
8916
|
+
if (decision.scan) {
|
|
8572
8917
|
const startedAt = Date.now();
|
|
8573
8918
|
const scanned = scan(opts.roots, opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {});
|
|
8574
8919
|
let added = 0;
|
|
@@ -8577,9 +8922,10 @@ async function refreshRepoMap(opts) {
|
|
|
8577
8922
|
take(full, paths[0], "scan", paths.slice(1));
|
|
8578
8923
|
added++;
|
|
8579
8924
|
}
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8925
|
+
scannedAt = stamp();
|
|
8926
|
+
log(
|
|
8927
|
+
`[overmind] \u043F\u0440\u043E\u0448\u0451\u043B \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C (${SCAN_REASON_TEXT[decision.reason]}): ${added > 0 ? `\u043D\u0430\u0448\u0451\u043B \u0435\u0449\u0451 ${added}` : "\u043D\u043E\u0432\u043E\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u0451\u043B"}, \u0432\u0441\u0435\u0433\u043E \u043A\u043B\u043E\u043D\u043E\u0432 ${clones.size}, \u0437\u0430 ${Date.now() - startedAt}\u043C\u0441. \u0418\u0441\u043A\u0430\u043B \u0432: ${opts.roots.length ? opts.roots.slice(0, 6).join(", ") : "\u043D\u0438\u0433\u0434\u0435 \u2014 \u043F\u043E\u0438\u0441\u043A \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)"}`
|
|
8928
|
+
);
|
|
8583
8929
|
}
|
|
8584
8930
|
const registry = [...clones.values()].map((c) => ({
|
|
8585
8931
|
repoFullName: c.repoFullName,
|
|
@@ -8587,10 +8933,12 @@ async function refreshRepoMap(opts) {
|
|
|
8587
8933
|
}));
|
|
8588
8934
|
writeRepoMapCache(
|
|
8589
8935
|
opts.cachePath,
|
|
8590
|
-
[...clones.values()].map((c) => ({ repoFullName: c.repoFullName, path: c.path }))
|
|
8936
|
+
[...clones.values()].map((c) => ({ repoFullName: c.repoFullName, path: c.path })),
|
|
8937
|
+
scannedAt
|
|
8591
8938
|
);
|
|
8592
8939
|
if (!answer.ok) {
|
|
8593
8940
|
log(describeReposFailure(answer.kind, answer.detail));
|
|
8941
|
+
log(`[overmind] \u0438\u0442\u043E\u0433: \u043A\u043B\u043E\u043D\u043E\u0432 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E ${clones.size}, \u0441\u043F\u0438\u0441\u043E\u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0440\u043E\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D (\u0441\u043C. \u0441\u0442\u0440\u043E\u043A\u0443 \u0432\u044B\u0448\u0435)`);
|
|
8594
8942
|
return registry;
|
|
8595
8943
|
}
|
|
8596
8944
|
opts.state.unknown = false;
|
|
@@ -8602,7 +8950,7 @@ async function refreshRepoMap(opts) {
|
|
|
8602
8950
|
log("[overmind] \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u043C \u0440\u043E\u0435\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435");
|
|
8603
8951
|
return registry;
|
|
8604
8952
|
}
|
|
8605
|
-
log(`[overmind]
|
|
8953
|
+
log(`[overmind] ${describeMapSummary(opts.state, opts.roots)}`);
|
|
8606
8954
|
for (const c of opts.state.clones) log(`[overmind] ${describeClone(c)}`);
|
|
8607
8955
|
const prefixOf = new Map(answer.repos.map((r) => [r.repoFullName, r.taskPrefix]));
|
|
8608
8956
|
for (const name of opts.state.missing) {
|
|
@@ -8611,6 +8959,11 @@ async function refreshRepoMap(opts) {
|
|
|
8611
8959
|
}
|
|
8612
8960
|
return registry;
|
|
8613
8961
|
}
|
|
8962
|
+
function describeMapSummary(state, roots) {
|
|
8963
|
+
const found = state.clones.length;
|
|
8964
|
+
const where = roots.length ? roots.slice(0, 6).join(", ") : "\u043F\u043E\u0438\u0441\u043A \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)";
|
|
8965
|
+
return `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F (${found + state.missing.length}): \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 ${found}, \u0431\u0435\u0437 \u043A\u043B\u043E\u043D\u0430 ${state.missing.length}. \u0418\u0441\u043A\u0430\u043B \u0432: ${where}`;
|
|
8966
|
+
}
|
|
8614
8967
|
function describeClone(c) {
|
|
8615
8968
|
const how = c.source === "configured" ? "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C" : c.source === "cache" ? "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B" : "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C";
|
|
8616
8969
|
const alt = c.alternatives.length ? ` \u26A0 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0435\u0449\u0451 ${c.alternatives.length}: ${c.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C \u043F\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0443 \u043A\u043E\u0440\u043D\u0435\u0439` : "";
|
|
@@ -8631,40 +8984,43 @@ function repoMapRows(state) {
|
|
|
8631
8984
|
...state.missing.map((repoFullName) => ({ repoFullName, path: null, source: null, alternatives: [] }))
|
|
8632
8985
|
];
|
|
8633
8986
|
}
|
|
8634
|
-
function
|
|
8987
|
+
function loadRepoMapCache(path) {
|
|
8635
8988
|
try {
|
|
8636
8989
|
const raw = JSON.parse(readFileSync9(path, "utf8"));
|
|
8637
|
-
if (!Array.isArray(raw?.entries)) return [];
|
|
8638
|
-
return
|
|
8639
|
-
|
|
8640
|
-
|
|
8990
|
+
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
8991
|
+
return {
|
|
8992
|
+
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
8993
|
+
entries: raw.entries.filter(
|
|
8994
|
+
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
8995
|
+
)
|
|
8996
|
+
};
|
|
8641
8997
|
} catch {
|
|
8642
|
-
return [];
|
|
8998
|
+
return { scannedAt: null, entries: [] };
|
|
8643
8999
|
}
|
|
8644
9000
|
}
|
|
8645
|
-
function writeRepoMapCache(path, entries) {
|
|
9001
|
+
function writeRepoMapCache(path, entries, scannedAt = stamp()) {
|
|
8646
9002
|
try {
|
|
8647
9003
|
const tmp = `${path}.tmp`;
|
|
8648
|
-
writeFileSync8(tmp, JSON.stringify({ at: stamp(), entries }, null, 2));
|
|
8649
|
-
|
|
9004
|
+
writeFileSync8(tmp, JSON.stringify({ at: stamp(), scannedAt, entries }, null, 2));
|
|
9005
|
+
renameSync6(tmp, path);
|
|
8650
9006
|
} catch {
|
|
8651
9007
|
}
|
|
8652
9008
|
}
|
|
8653
9009
|
|
|
8654
9010
|
// src/hostIdentity.ts
|
|
8655
9011
|
import { hostname } from "os";
|
|
8656
|
-
import { existsSync as
|
|
8657
|
-
import { join as
|
|
9012
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
9013
|
+
import { join as join12 } from "path";
|
|
8658
9014
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8659
9015
|
function hostIdentityPath(home) {
|
|
8660
|
-
return
|
|
9016
|
+
return join12(home, "host.json");
|
|
8661
9017
|
}
|
|
8662
9018
|
function loadHostIdentity(deps) {
|
|
8663
|
-
const exists = deps.exists ??
|
|
9019
|
+
const exists = deps.exists ?? existsSync8;
|
|
8664
9020
|
const readFile2 = deps.readFile ?? ((p) => readFileSync10(p, "utf8"));
|
|
8665
9021
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
8666
9022
|
writeFileSync9(`${p}.tmp`, data);
|
|
8667
|
-
|
|
9023
|
+
renameSync7(`${p}.tmp`, p);
|
|
8668
9024
|
});
|
|
8669
9025
|
const host = deps.hostname ?? hostname;
|
|
8670
9026
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -8846,15 +9202,15 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
8846
9202
|
|
|
8847
9203
|
// src/launcher.ts
|
|
8848
9204
|
import { spawn } from "child_process";
|
|
8849
|
-
import { closeSync as closeSync2, existsSync as
|
|
9205
|
+
import { closeSync as closeSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, openSync as openSync2, readdirSync as readdirSync8 } from "fs";
|
|
8850
9206
|
import { createRequire } from "module";
|
|
8851
9207
|
import { homedir as homedir4 } from "os";
|
|
8852
|
-
import { dirname as dirname2, join as
|
|
9208
|
+
import { dirname as dirname2, join as join14 } from "path";
|
|
8853
9209
|
import { fileURLToPath } from "url";
|
|
8854
9210
|
|
|
8855
9211
|
// src/droneHome.ts
|
|
8856
|
-
import { lstatSync, mkdirSync as
|
|
8857
|
-
import { join as
|
|
9212
|
+
import { lstatSync, mkdirSync as mkdirSync6, readdirSync as readdirSync7, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
9213
|
+
import { join as join13, relative, sep } from "path";
|
|
8858
9214
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
8859
9215
|
function droneHomeDeny(env = process.env) {
|
|
8860
9216
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -8872,29 +9228,29 @@ function buildDroneHome(opts) {
|
|
|
8872
9228
|
const { realHome, droneHome } = opts;
|
|
8873
9229
|
const deny = opts.deny ?? droneHomeDeny();
|
|
8874
9230
|
const platform = opts.platform ?? process.platform;
|
|
8875
|
-
|
|
8876
|
-
const wanted = new Set(mirroredEntries(
|
|
9231
|
+
mkdirSync6(droneHome, { recursive: true, mode: 448 });
|
|
9232
|
+
const wanted = new Set(mirroredEntries(readdirSync7(realHome), deny, platform));
|
|
8877
9233
|
const rel = relative(realHome, droneHome);
|
|
8878
9234
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
8879
|
-
for (const existing of
|
|
9235
|
+
for (const existing of readdirSync7(droneHome)) {
|
|
8880
9236
|
if (wanted.has(existing)) continue;
|
|
8881
|
-
const p =
|
|
9237
|
+
const p = join13(droneHome, existing);
|
|
8882
9238
|
try {
|
|
8883
9239
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
8884
9240
|
} catch {
|
|
8885
9241
|
}
|
|
8886
9242
|
}
|
|
8887
9243
|
for (const name of wanted) {
|
|
8888
|
-
const link =
|
|
9244
|
+
const link = join13(droneHome, name);
|
|
8889
9245
|
try {
|
|
8890
9246
|
lstatSync(link);
|
|
8891
9247
|
continue;
|
|
8892
9248
|
} catch {
|
|
8893
9249
|
}
|
|
8894
|
-
const target =
|
|
9250
|
+
const target = join13(realHome, name);
|
|
8895
9251
|
let isDir;
|
|
8896
9252
|
try {
|
|
8897
|
-
isDir =
|
|
9253
|
+
isDir = statSync7(target).isDirectory();
|
|
8898
9254
|
} catch {
|
|
8899
9255
|
continue;
|
|
8900
9256
|
}
|
|
@@ -8912,7 +9268,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
8912
9268
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
8913
9269
|
];
|
|
8914
9270
|
function realPathEnvPins(realHome) {
|
|
8915
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
9271
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join13(realHome, ...segments)]));
|
|
8916
9272
|
}
|
|
8917
9273
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
8918
9274
|
return [
|
|
@@ -9024,7 +9380,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
9024
9380
|
const env = { ...base3 };
|
|
9025
9381
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
9026
9382
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
9027
|
-
env.TURBO_CACHE_DIR =
|
|
9383
|
+
env.TURBO_CACHE_DIR = join14(worktree, ".turbo", "cache");
|
|
9028
9384
|
}
|
|
9029
9385
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
9030
9386
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -9052,20 +9408,21 @@ var DAEMON_STATE_ENV = [
|
|
|
9052
9408
|
"TASKLESS_TOKEN"
|
|
9053
9409
|
];
|
|
9054
9410
|
function bundledRoleDir(kind) {
|
|
9055
|
-
return
|
|
9411
|
+
return join14(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
9056
9412
|
}
|
|
9057
9413
|
function roleDir(worktree, kind) {
|
|
9058
|
-
return
|
|
9414
|
+
return join14(worktree, ".claude", "roles", kind);
|
|
9059
9415
|
}
|
|
9060
9416
|
function roleDirs(worktree, kind) {
|
|
9061
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
9417
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync9(dir));
|
|
9062
9418
|
}
|
|
9063
|
-
function nextRunLogPath(runLogsDir,
|
|
9064
|
-
const dir =
|
|
9065
|
-
|
|
9419
|
+
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
9420
|
+
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
9421
|
+
if (!dir) throw new Error("\u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 (SwarmStore.runLogKeys)");
|
|
9422
|
+
mkdirSync7(dir, { recursive: true });
|
|
9066
9423
|
let files = [];
|
|
9067
9424
|
try {
|
|
9068
|
-
files =
|
|
9425
|
+
files = readdirSync8(dir);
|
|
9069
9426
|
} catch {
|
|
9070
9427
|
}
|
|
9071
9428
|
let next = 0;
|
|
@@ -9073,11 +9430,11 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
9073
9430
|
const m = /^(\d+)\.json/.exec(f);
|
|
9074
9431
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
9075
9432
|
}
|
|
9076
|
-
return
|
|
9433
|
+
return join14(dir, `${next}.json`);
|
|
9077
9434
|
}
|
|
9078
|
-
function openNextRunLog(runLogsDir,
|
|
9435
|
+
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
9079
9436
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
9080
|
-
const path = nextRunLogPath(runLogsDir,
|
|
9437
|
+
const path = nextRunLogPath(runLogsDir, runLogKeys);
|
|
9081
9438
|
let out;
|
|
9082
9439
|
try {
|
|
9083
9440
|
out = openSync2(path, "wx");
|
|
@@ -9093,7 +9450,7 @@ function openNextRunLog(runLogsDir, runId) {
|
|
|
9093
9450
|
}
|
|
9094
9451
|
return { out, err };
|
|
9095
9452
|
}
|
|
9096
|
-
throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0439\u0442\u0438 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0439 run-log \u0444\u0430\u0439\u043B \
|
|
9453
|
+
throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0439\u0442\u0438 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0439 run-log \u0444\u0430\u0439\u043B \u043F\u0440\u043E\u0433\u043E\u043D\u0430 (${runLogKeys[0]}) \u2014 \u0433\u043E\u043D\u043A\u0430?`);
|
|
9097
9454
|
}
|
|
9098
9455
|
function makeLauncher(cfg) {
|
|
9099
9456
|
return ({ worktree, run, prompt, continueSession }) => {
|
|
@@ -9156,9 +9513,9 @@ function makeLauncher(cfg) {
|
|
|
9156
9513
|
}
|
|
9157
9514
|
const realHome = cfg.droneIsolation?.realHome ?? homedir4();
|
|
9158
9515
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
9159
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
9516
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(join14(cfg.droneIsolation.swarmHome, "token"))) {
|
|
9160
9517
|
console.error(
|
|
9161
|
-
`[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 ${
|
|
9518
|
+
`[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 ${join14(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.`
|
|
9162
9519
|
);
|
|
9163
9520
|
}
|
|
9164
9521
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -9185,7 +9542,13 @@ function makeLauncher(cfg) {
|
|
|
9185
9542
|
let outFd;
|
|
9186
9543
|
let errFd;
|
|
9187
9544
|
if (cfg.runLogsDir) {
|
|
9188
|
-
const
|
|
9545
|
+
const logKeys = cfg.runLogKeys?.(run) ?? [];
|
|
9546
|
+
if (logKeys.length === 0) {
|
|
9547
|
+
throw new Error(
|
|
9548
|
+
`\u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id} (${run.task_slug}) \u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u0441\u0442\u043E\u0440\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430. \u0414\u0440\u043E\u043D \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0435\u043D \u2014 \u0438\u043D\u0430\u0447\u0435 \u0435\u0433\u043E \u043B\u043E\u0433 \u043B\u0451\u0433 \u0431\u044B \u0432 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0447\u0443\u0436\u043E\u0439 (TASWM-419).`
|
|
9549
|
+
);
|
|
9550
|
+
}
|
|
9551
|
+
const fds = openNextRunLog(cfg.runLogsDir, logKeys);
|
|
9189
9552
|
outFd = fds.out;
|
|
9190
9553
|
errFd = fds.err ?? void 0;
|
|
9191
9554
|
stdio = ["ignore", outFd, errFd ?? "ignore"];
|
|
@@ -9213,21 +9576,21 @@ function makeLauncher(cfg) {
|
|
|
9213
9576
|
init_procCleanup();
|
|
9214
9577
|
|
|
9215
9578
|
// src/cost.ts
|
|
9579
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync9, renameSync as renameSync8 } from "fs";
|
|
9580
|
+
import { join as join15 } from "path";
|
|
9216
9581
|
init_executors();
|
|
9217
|
-
import { readFileSync as readFileSync11, readdirSync as readdirSync8, renameSync as renameSync7 } from "fs";
|
|
9218
|
-
import { join as join14 } from "path";
|
|
9219
9582
|
var UNPROCESSED_LOG_RE = /^\d+\.json$/;
|
|
9220
9583
|
function unprocessedLogFiles(dir) {
|
|
9221
9584
|
try {
|
|
9222
|
-
return
|
|
9585
|
+
return readdirSync9(dir).filter((f) => UNPROCESSED_LOG_RE.test(f));
|
|
9223
9586
|
} catch {
|
|
9224
9587
|
return [];
|
|
9225
9588
|
}
|
|
9226
9589
|
}
|
|
9227
9590
|
function accountRunCost(deps, run) {
|
|
9228
|
-
const dir =
|
|
9229
|
-
const files = unprocessedLogFiles(dir);
|
|
9230
|
-
if (files.length === 0) return;
|
|
9591
|
+
const dir = findRunLogDir(deps.runLogsDir, deps.db.runLogKeys(run.id));
|
|
9592
|
+
const files = dir ? unprocessedLogFiles(dir) : [];
|
|
9593
|
+
if (!dir || files.length === 0) return;
|
|
9231
9594
|
const model = resolveRunModel(run, deps.swarmModels?.() ?? null);
|
|
9232
9595
|
const executor = deps.executor ?? resolveExecutor();
|
|
9233
9596
|
let added = 0;
|
|
@@ -9236,7 +9599,7 @@ function accountRunCost(deps, run) {
|
|
|
9236
9599
|
let durationMs = 0;
|
|
9237
9600
|
const processed = [];
|
|
9238
9601
|
for (const f of files) {
|
|
9239
|
-
const full =
|
|
9602
|
+
const full = join15(dir, f);
|
|
9240
9603
|
let raw = null;
|
|
9241
9604
|
try {
|
|
9242
9605
|
raw = readFileSync11(full, "utf8");
|
|
@@ -9265,7 +9628,7 @@ function accountRunCost(deps, run) {
|
|
|
9265
9628
|
}
|
|
9266
9629
|
for (const full of processed) {
|
|
9267
9630
|
try {
|
|
9268
|
-
|
|
9631
|
+
renameSync8(full, `${full}.done`);
|
|
9269
9632
|
} catch {
|
|
9270
9633
|
}
|
|
9271
9634
|
}
|
|
@@ -9273,20 +9636,34 @@ function accountRunCost(deps, run) {
|
|
|
9273
9636
|
function makeCostAccountant(deps) {
|
|
9274
9637
|
return (run) => accountRunCost(deps, run);
|
|
9275
9638
|
}
|
|
9639
|
+
function sweepPendingRunCosts(deps) {
|
|
9640
|
+
for (const run of deps.db.listRuns()) {
|
|
9641
|
+
if (run.pid != null && deps.isAlive(run.pid)) continue;
|
|
9642
|
+
try {
|
|
9643
|
+
accountRunCost(deps, run);
|
|
9644
|
+
} catch (e) {
|
|
9645
|
+
console.error(`[cost] \u043F\u0440\u043E\u0433\u043E\u043D #${run.id}: \u0443\u0447\u0451\u0442 \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F \u2014 ${String(e)}`);
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
}
|
|
9649
|
+
function makeCostSweeper(deps) {
|
|
9650
|
+
return () => sweepPendingRunCosts(deps);
|
|
9651
|
+
}
|
|
9276
9652
|
|
|
9277
9653
|
// src/runOutcome.ts
|
|
9654
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync12 } from "fs";
|
|
9655
|
+
import { join as join16 } from "path";
|
|
9278
9656
|
init_executors();
|
|
9279
|
-
import { readdirSync as readdirSync9, readFileSync as readFileSync12 } from "fs";
|
|
9280
|
-
import { join as join15 } from "path";
|
|
9281
9657
|
function endedItself(outcome) {
|
|
9282
9658
|
return outcome === "graceful" || outcome === "budget";
|
|
9283
9659
|
}
|
|
9284
9660
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
9285
|
-
function classifyExit(runLogsDir,
|
|
9286
|
-
const dir =
|
|
9661
|
+
function classifyExit(runLogsDir, runLogKeys, executor = resolveExecutor()) {
|
|
9662
|
+
const dir = findRunLogDir(runLogsDir, runLogKeys);
|
|
9663
|
+
if (!dir) return "unknown";
|
|
9287
9664
|
let files;
|
|
9288
9665
|
try {
|
|
9289
|
-
files =
|
|
9666
|
+
files = readdirSync10(dir);
|
|
9290
9667
|
} catch {
|
|
9291
9668
|
return "unknown";
|
|
9292
9669
|
}
|
|
@@ -9300,7 +9677,7 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
|
9300
9677
|
if (!latest) return "unknown";
|
|
9301
9678
|
let raw;
|
|
9302
9679
|
try {
|
|
9303
|
-
raw = readFileSync12(
|
|
9680
|
+
raw = readFileSync12(join16(dir, latest.name), "utf8");
|
|
9304
9681
|
} catch {
|
|
9305
9682
|
return "unknown";
|
|
9306
9683
|
}
|
|
@@ -9308,9 +9685,9 @@ function classifyExit(runLogsDir, runId, executor = resolveExecutor()) {
|
|
|
9308
9685
|
if (!result) return "crash";
|
|
9309
9686
|
return result.stoppedByBudget ? "budget" : "graceful";
|
|
9310
9687
|
}
|
|
9311
|
-
function makeExitClassifier(runLogsDir, executor) {
|
|
9688
|
+
function makeExitClassifier(runLogsDir, runLogKeys, executor) {
|
|
9312
9689
|
const exec = executor ?? resolveExecutor();
|
|
9313
|
-
return (run) => classifyExit(runLogsDir, run.id, exec);
|
|
9690
|
+
return (run) => classifyExit(runLogsDir, runLogKeys(run.id), exec);
|
|
9314
9691
|
}
|
|
9315
9692
|
|
|
9316
9693
|
// src/orchestrator.ts
|
|
@@ -9318,20 +9695,20 @@ init_src();
|
|
|
9318
9695
|
init_git();
|
|
9319
9696
|
|
|
9320
9697
|
// src/deps.ts
|
|
9321
|
-
import { existsSync as
|
|
9322
|
-
import { join as
|
|
9698
|
+
import { existsSync as existsSync11, readdirSync as readdirSync12, readFileSync as readFileSync13 } from "fs";
|
|
9699
|
+
import { join as join19 } from "path";
|
|
9323
9700
|
import { execa } from "execa";
|
|
9324
9701
|
|
|
9325
9702
|
// src/prepareLock.ts
|
|
9326
9703
|
import { createHash } from "crypto";
|
|
9327
|
-
import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as
|
|
9328
|
-
import { join as
|
|
9704
|
+
import { closeSync as closeSync3, openSync as openSync3, rmSync as rmSync2, statSync as statSync9, writeSync } from "fs";
|
|
9705
|
+
import { join as join18 } from "path";
|
|
9329
9706
|
import { tmpdir } from "os";
|
|
9330
9707
|
var WAIT_TIMEOUT_MS = 5 * 6e4;
|
|
9331
9708
|
var POLL_MS = 300;
|
|
9332
9709
|
var STALE_MS = 20 * 6e4;
|
|
9333
9710
|
function machineLockPath(key, dir = tmpdir()) {
|
|
9334
|
-
return
|
|
9711
|
+
return join18(dir, `swarm-prepare-${createHash("sha1").update(key).digest("hex").slice(0, 16)}.lock`);
|
|
9335
9712
|
}
|
|
9336
9713
|
function defaultTryCreate(path, content) {
|
|
9337
9714
|
try {
|
|
@@ -9348,7 +9725,7 @@ function defaultTryCreate(path, content) {
|
|
|
9348
9725
|
}
|
|
9349
9726
|
function defaultAgeMs(path) {
|
|
9350
9727
|
try {
|
|
9351
|
-
return Date.now() -
|
|
9728
|
+
return Date.now() - statSync9(path).mtimeMs;
|
|
9352
9729
|
} catch {
|
|
9353
9730
|
return null;
|
|
9354
9731
|
}
|
|
@@ -9412,14 +9789,14 @@ function safeReadText(p) {
|
|
|
9412
9789
|
}
|
|
9413
9790
|
function safeListDirs(p) {
|
|
9414
9791
|
try {
|
|
9415
|
-
return
|
|
9792
|
+
return readdirSync12(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
9416
9793
|
} catch {
|
|
9417
9794
|
return [];
|
|
9418
9795
|
}
|
|
9419
9796
|
}
|
|
9420
9797
|
function safeListFiles(p) {
|
|
9421
9798
|
try {
|
|
9422
|
-
return
|
|
9799
|
+
return readdirSync12(p, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
|
|
9423
9800
|
} catch {
|
|
9424
9801
|
return [];
|
|
9425
9802
|
}
|
|
@@ -9445,21 +9822,21 @@ function overridePlan(override) {
|
|
|
9445
9822
|
};
|
|
9446
9823
|
}
|
|
9447
9824
|
function detectPackageManager(worktree, io = {}) {
|
|
9448
|
-
const exists = io.exists ??
|
|
9825
|
+
const exists = io.exists ?? existsSync11;
|
|
9449
9826
|
const readText = io.readText ?? safeReadText;
|
|
9450
|
-
const hasPkgJson = exists(
|
|
9827
|
+
const hasPkgJson = exists(join19(worktree, "package.json"));
|
|
9451
9828
|
if (!hasPkgJson) {
|
|
9452
|
-
if (exists(
|
|
9829
|
+
if (exists(join19(worktree, "deno.json")) || exists(join19(worktree, "deno.jsonc")) || exists(join19(worktree, "deno.lock"))) return "deno";
|
|
9453
9830
|
return null;
|
|
9454
9831
|
}
|
|
9455
|
-
const pkg = readJson(readText,
|
|
9832
|
+
const pkg = readJson(readText, join19(worktree, "package.json"));
|
|
9456
9833
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
9457
9834
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
9458
|
-
if (exists(
|
|
9459
|
-
if (exists(
|
|
9460
|
-
if (exists(
|
|
9461
|
-
if (exists(
|
|
9462
|
-
if (exists(
|
|
9835
|
+
if (exists(join19(worktree, "bun.lock")) || exists(join19(worktree, "bun.lockb"))) return "bun";
|
|
9836
|
+
if (exists(join19(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
9837
|
+
if (exists(join19(worktree, "yarn.lock"))) return "yarn";
|
|
9838
|
+
if (exists(join19(worktree, "package-lock.json"))) return "npm";
|
|
9839
|
+
if (exists(join19(worktree, "deno.lock"))) return "deno";
|
|
9463
9840
|
return "npm";
|
|
9464
9841
|
}
|
|
9465
9842
|
function jsInstallCommand(pm) {
|
|
@@ -9512,7 +9889,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
9512
9889
|
// конфигурация — дело сборки, не подготовки
|
|
9513
9890
|
];
|
|
9514
9891
|
function detectPlan(worktree, io) {
|
|
9515
|
-
const exists = io.exists ??
|
|
9892
|
+
const exists = io.exists ?? existsSync11;
|
|
9516
9893
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
9517
9894
|
const pm = detectPackageManager(worktree, io);
|
|
9518
9895
|
if (pm) {
|
|
@@ -9520,7 +9897,7 @@ function detectPlan(worktree, io) {
|
|
|
9520
9897
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
9521
9898
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
9522
9899
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
9523
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
9900
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join19(worktree, "node_modules"), source: "detect" }
|
|
9524
9901
|
];
|
|
9525
9902
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
9526
9903
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -9529,12 +9906,12 @@ function detectPlan(worktree, io) {
|
|
|
9529
9906
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
9530
9907
|
}
|
|
9531
9908
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
9532
|
-
const hit = marker.files.some((f) => exists(
|
|
9909
|
+
const hit = marker.files.some((f) => exists(join19(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
9533
9910
|
if (!hit) continue;
|
|
9534
9911
|
let ecosystem = marker.ecosystem;
|
|
9535
9912
|
let command = marker.command;
|
|
9536
9913
|
for (const refine of marker.refine ?? []) {
|
|
9537
|
-
if (!exists(
|
|
9914
|
+
if (!exists(join19(worktree, refine.file))) continue;
|
|
9538
9915
|
ecosystem = refine.ecosystem;
|
|
9539
9916
|
command = refine.command;
|
|
9540
9917
|
break;
|
|
@@ -9591,12 +9968,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
9591
9968
|
function ciWorkflowPlan(worktree, io) {
|
|
9592
9969
|
const readText = io.readText ?? safeReadText;
|
|
9593
9970
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
9594
|
-
const dir =
|
|
9971
|
+
const dir = join19(worktree, ".github", "workflows");
|
|
9595
9972
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
9596
9973
|
const seen = /* @__PURE__ */ new Set();
|
|
9597
9974
|
const commands = [];
|
|
9598
9975
|
for (const name of files.sort()) {
|
|
9599
|
-
const yaml = readText(
|
|
9976
|
+
const yaml = readText(join19(dir, name));
|
|
9600
9977
|
if (yaml === null) continue;
|
|
9601
9978
|
for (const raw of workflowRunLines(yaml)) {
|
|
9602
9979
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -9611,7 +9988,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
9611
9988
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
9612
9989
|
}
|
|
9613
9990
|
function workspaceGlobs(worktree, readText) {
|
|
9614
|
-
const yaml = readText(
|
|
9991
|
+
const yaml = readText(join19(worktree, "pnpm-workspace.yaml"));
|
|
9615
9992
|
if (yaml !== null) {
|
|
9616
9993
|
const globs = [];
|
|
9617
9994
|
let inPackages = false;
|
|
@@ -9631,7 +10008,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
9631
10008
|
}
|
|
9632
10009
|
if (globs.length) return globs;
|
|
9633
10010
|
}
|
|
9634
|
-
const pkg = readJson(readText,
|
|
10011
|
+
const pkg = readJson(readText, join19(worktree, "package.json"));
|
|
9635
10012
|
const ws = pkg?.workspaces;
|
|
9636
10013
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
9637
10014
|
return list.filter((g) => typeof g === "string");
|
|
@@ -9642,14 +10019,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
9642
10019
|
if (glob.startsWith("!")) continue;
|
|
9643
10020
|
const star = glob.indexOf("*");
|
|
9644
10021
|
if (star === -1) {
|
|
9645
|
-
const dir =
|
|
9646
|
-
if (io.exists(
|
|
10022
|
+
const dir = join19(worktree, glob);
|
|
10023
|
+
if (io.exists(join19(dir, "package.json"))) dirs.add(dir);
|
|
9647
10024
|
continue;
|
|
9648
10025
|
}
|
|
9649
|
-
const base3 =
|
|
10026
|
+
const base3 = join19(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
9650
10027
|
for (const name of io.listDirs(base3)) {
|
|
9651
|
-
const dir =
|
|
9652
|
-
if (io.exists(
|
|
10028
|
+
const dir = join19(base3, name);
|
|
10029
|
+
if (io.exists(join19(dir, "package.json"))) dirs.add(dir);
|
|
9653
10030
|
}
|
|
9654
10031
|
}
|
|
9655
10032
|
return [...dirs];
|
|
@@ -9670,18 +10047,18 @@ function entryPaths(pkg) {
|
|
|
9670
10047
|
return out;
|
|
9671
10048
|
}
|
|
9672
10049
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
9673
|
-
const exists = io.exists ??
|
|
10050
|
+
const exists = io.exists ?? existsSync11;
|
|
9674
10051
|
const readText = io.readText ?? safeReadText;
|
|
9675
10052
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
9676
10053
|
const globs = workspaceGlobs(worktree, readText);
|
|
9677
10054
|
if (!globs.length) return [];
|
|
9678
10055
|
const pkgs = [];
|
|
9679
10056
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
9680
|
-
const pkg = readJson(readText,
|
|
10057
|
+
const pkg = readJson(readText, join19(dir, "package.json"));
|
|
9681
10058
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
9682
10059
|
const scripts = pkg.scripts ?? {};
|
|
9683
10060
|
const entries = entryPaths(pkg);
|
|
9684
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
10061
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join19(dir, e)));
|
|
9685
10062
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
9686
10063
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
9687
10064
|
}
|
|
@@ -9709,7 +10086,7 @@ function devDatabaseUrl(worktree, io = {}) {
|
|
|
9709
10086
|
const fromEnv = (io.env ?? process.env).DATABASE_URL;
|
|
9710
10087
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
9711
10088
|
for (const file of [".env", ".env.example"]) {
|
|
9712
|
-
const raw = readText(
|
|
10089
|
+
const raw = readText(join19(worktree, file));
|
|
9713
10090
|
if (raw === null) continue;
|
|
9714
10091
|
const m = /^\s*(?:export\s+)?DATABASE_URL\s*=\s*(.*)$/m.exec(raw);
|
|
9715
10092
|
const value = (m?.[1] ?? "").trim().replace(/^['"]|['"]$/g, "");
|
|
@@ -9725,11 +10102,11 @@ function isLocalDatabase(url) {
|
|
|
9725
10102
|
}
|
|
9726
10103
|
}
|
|
9727
10104
|
function prismaPackages(worktree, io) {
|
|
9728
|
-
const exists = io.exists ??
|
|
10105
|
+
const exists = io.exists ?? existsSync11;
|
|
9729
10106
|
const readText = io.readText ?? safeReadText;
|
|
9730
10107
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
9731
10108
|
const dirs = [worktree, ...workspaceDirs(worktree, workspaceGlobs(worktree, readText), { exists, listDirs })];
|
|
9732
|
-
return dirs.filter((dir) => exists(
|
|
10109
|
+
return dirs.filter((dir) => exists(join19(dir, "prisma", "schema.prisma")) && listDirs(join19(dir, "prisma", "migrations")).length > 0);
|
|
9733
10110
|
}
|
|
9734
10111
|
function jsExecBin(pm, command) {
|
|
9735
10112
|
switch (pm) {
|
|
@@ -9762,7 +10139,7 @@ function planPrepare(worktree, io = {}) {
|
|
|
9762
10139
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
9763
10140
|
}
|
|
9764
10141
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
9765
|
-
const exists = io.exists ??
|
|
10142
|
+
const exists = io.exists ?? existsSync11;
|
|
9766
10143
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
9767
10144
|
const approve = io.approve ?? (() => true);
|
|
9768
10145
|
const plan = planPrepare(worktree, io);
|
|
@@ -9947,6 +10324,43 @@ function pruneDefers(episodes, live) {
|
|
|
9947
10324
|
for (const key of episodes.keys()) if (!live.has(key)) episodes.delete(key);
|
|
9948
10325
|
}
|
|
9949
10326
|
|
|
10327
|
+
// src/spawnAttempts.ts
|
|
10328
|
+
init_src();
|
|
10329
|
+
var PRE_START_ATTEMPT_LIMIT = 3;
|
|
10330
|
+
var PRE_START_MARK = "\u0434\u0440\u043E\u043D \u043D\u0435 \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u0430\u043B";
|
|
10331
|
+
var SPAWN_PARK_MARK = "\u0437\u0430\u0434\u0430\u0447\u0430 \u043E\u0442\u043B\u043E\u0436\u0435\u043D\u0430 \u0440\u043E\u0435\u043C";
|
|
10332
|
+
function isPreStartFailure(run) {
|
|
10333
|
+
return run.status === "failed" && (run.stop_reason ?? "").includes(PRE_START_MARK);
|
|
10334
|
+
}
|
|
10335
|
+
function isSpawnParked(run) {
|
|
10336
|
+
return (run.stop_reason ?? "").includes(SPAWN_PARK_MARK);
|
|
10337
|
+
}
|
|
10338
|
+
var SPAWN_ATTEMPT_WINDOW_MS = 60 * 6e4;
|
|
10339
|
+
function preStartAttempts(runs, taskSlug, opts = {}) {
|
|
10340
|
+
const windowMs = opts.windowMs ?? SPAWN_ATTEMPT_WINDOW_MS;
|
|
10341
|
+
const now = opts.now ?? Date.now();
|
|
10342
|
+
const own = runs.filter((r) => r.task_slug === taskSlug && r.reviews_run_id == null && r.resolves_run_id == null && r.verifies_run_id == null).sort((a, b) => b.id - a.id);
|
|
10343
|
+
let n = 0;
|
|
10344
|
+
for (const run of own) {
|
|
10345
|
+
if (run.status === "queued" || run.status === "running") continue;
|
|
10346
|
+
if (isSpawnParked(run) && run.status !== "failed") continue;
|
|
10347
|
+
if (!isPreStartFailure(run)) break;
|
|
10348
|
+
const at = run.created_at ? stampMs(run.created_at) : Number.NaN;
|
|
10349
|
+
if (Number.isFinite(at) && now - at >= windowMs) break;
|
|
10350
|
+
n += 1;
|
|
10351
|
+
}
|
|
10352
|
+
return n;
|
|
10353
|
+
}
|
|
10354
|
+
function unresolvedRepoReason(repo, served) {
|
|
10355
|
+
const raw = repo.trim();
|
|
10356
|
+
if (!raw || raw.startsWith("/") || raw.startsWith("~") || /^[a-z]:[\\/]/i.test(raw)) return null;
|
|
10357
|
+
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)";
|
|
10358
|
+
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.`;
|
|
10359
|
+
}
|
|
10360
|
+
function parkReason(attempts, reason) {
|
|
10361
|
+
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}`;
|
|
10362
|
+
}
|
|
10363
|
+
|
|
9950
10364
|
// src/overmind.ts
|
|
9951
10365
|
init_git();
|
|
9952
10366
|
|
|
@@ -10210,6 +10624,7 @@ init_attachments();
|
|
|
10210
10624
|
init_shotUpload();
|
|
10211
10625
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10212
10626
|
import { z } from "zod";
|
|
10627
|
+
init_git();
|
|
10213
10628
|
init_src();
|
|
10214
10629
|
|
|
10215
10630
|
// src/reviewCycle.ts
|
|
@@ -10586,10 +11001,57 @@ ${notFoundText}` : notFoundText }],
|
|
|
10586
11001
|
),
|
|
10587
11002
|
screenshots: z.array(z.string()).optional().describe(
|
|
10588
11003
|
`\u041F\u0443\u0442\u0438 \u043A \u0441\u043D\u044F\u0442\u044B\u043C \u0441\u043A\u0440\u0438\u043D\u0430\u043C (png/jpg/webp/gif) \u2014 \u0438\u0445 \u043F\u0440\u0438\u043B\u043E\u0436\u0430\u0442 \u043A \u0442\u0432\u043E\u0435\u043C\u0443 \u043E\u0442\u0447\u0451\u0442\u0443 \u0412 \u0417\u0410\u0414\u0410\u0427\u0415, \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u0442\u0440\u0435\u043A\u0435\u0440, \u0430 \u043D\u0435 \u0433\u0438\u0442\u0445\u0430\u0431. \u041F\u0443\u0442\u044C \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 (\u043E\u0442 worktree) \u0438\u043B\u0438 \u0430\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u044B\u0439, \u0434\u043E ${SHOT_MAX_FILES} \u0444\u0430\u0439\u043B\u043E\u0432. \u041C\u0435\u043D\u044F\u043B \u0432\u0438\u0434\u0438\u043C\u044B\u0439 UI \u2014 \u043F\u0440\u0438\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0439 \u043E\u0431\u0430 \u0432\u044C\u044E\u043F\u043E\u0440\u0442\u0430 (desktop+mobile), \u044D\u0442\u043E \u0438 \u0435\u0441\u0442\u044C \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u0440\u0430\u0431\u043E\u0442\u044B.`
|
|
11004
|
+
),
|
|
11005
|
+
// TASWM-431: правило о репозитории — то, обо что дрон СПОТКНУЛСЯ. Форму значения
|
|
11006
|
+
// проверяет прод (там живёт схема разметки), здесь она намеренно свободная: гейт
|
|
11007
|
+
// на форму в двух местах разъехался бы, а отказ прода дрон и так увидит в ответе.
|
|
11008
|
+
rules: z.array(
|
|
11009
|
+
z.object({
|
|
11010
|
+
field: z.enum([...RULE_FIELDS]).describe("\u043A\u0430\u043A\u043E\u0435 \u043F\u043E\u043B\u0435 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u044D\u0442\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u043E: verify | conventions | brief | env | zones | spine"),
|
|
11011
|
+
value: z.unknown().describe("\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \u2014 \u0442\u043E\u0439 \u0436\u0435 \u0444\u043E\u0440\u043C\u044B, \u0447\u0442\u043E \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0435 (verify: {commands,green}, env: {database,migrations,vars}, conventions: {commitLanguage,slugInCommit,baseBranch}, brief: \u0441\u0442\u0440\u043E\u043A\u0430, zones: \u043C\u0430\u0441\u0441\u0438\u0432 \u0437\u043E\u043D, spine: \u043C\u0430\u0441\u0441\u0438\u0432 \u043F\u0443\u0442\u0435\u0439)"),
|
|
11012
|
+
why: z.string().describe("\u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0435\u0433\u043E \u044D\u0442\u043E \u0441\u0442\u043E\u0438\u043B\u043E \u2014 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0438 \u043E\u0442\u043B\u0438\u0447\u0438\u0442 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E\u0442 \u043E\u0442\u043F\u0438\u0441\u043A\u0438")
|
|
11013
|
+
})
|
|
11014
|
+
).optional().describe(
|
|
11015
|
+
`\u041F\u0420\u0410\u0412\u0418\u041B\u041E \u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u0418, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u0432 \u043D\u0451\u043C \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E (TASWM-431) \u2014 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${DRONE_RULES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0442\u044B \u0420\u0415\u0410\u041B\u042C\u041D\u041E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u043F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u0432\u0440\u0435\u043C\u044F (\xAB\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u0436\u0434\u0443\u0442 \u043F\u043E\u0441\u0442\u0433\u0440\u0435\u0441 \u043D\u0430 5433\xBB). \u041E\u0431\u0449\u0435\u0435 \u043C\u0435\u0441\u0442\u043E (\xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 pnpm test\xBB) \u043F\u0440\u0430\u0432\u0438\u043B\u043E\u043C \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F. \u0423\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E. \u0420\u0435\u0448\u0430\u0435\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432 \u0441\u0432\u043E\u0451\u043C \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0435.`
|
|
11016
|
+
),
|
|
11017
|
+
rule_verdicts: z.array(z.object({ field: z.enum([...RULE_FIELDS]), apply: z.boolean() })).optional().describe(
|
|
11018
|
+
"\u0422\u041E\u041B\u042C\u041A\u041E \u0414\u041B\u042F \u0420\u0415\u0412\u042C\u042E\u0415\u0420\u0410 (TASWM-431): \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u0440\u0438\u043D\u0451\u0441 \u0434\u0440\u043E\u043D (\u043E\u043D\u0438 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u044B \u0432 \u0442\u0432\u043E\u0451\u043C \u043F\u0440\u043E\u043C\u043F\u0442\u0435). apply=true \u2014 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0435, apply=false \u2014 \u043E\u0442\u043F\u0438\u0441\u043A\u0430, \u0441\u043D\u044F\u0442\u044C. verify/conventions/brief \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u0441\u044F \u0432 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443 \u0441\u0440\u0430\u0437\u0443, env/zones/spine \u0443\u0439\u0434\u0443\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0432 \u043B\u044E\u0431\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435."
|
|
10589
11019
|
)
|
|
10590
11020
|
},
|
|
10591
|
-
guard(async ({ pr_url, verdict, summary, screenshots }) => {
|
|
11021
|
+
guard(async ({ pr_url, verdict, summary, screenshots, rules, rule_verdicts }) => {
|
|
10592
11022
|
const run = await store.myRun();
|
|
11023
|
+
let repoFullNameCache = null;
|
|
11024
|
+
const repoFullNameOnce = () => repoFullNameCache ??= remoteRepoFullName(opts.worktree ?? process.cwd()).catch(() => null);
|
|
11025
|
+
const offerRules = async (taskSlug) => {
|
|
11026
|
+
if (!rules?.length) return "";
|
|
11027
|
+
if (!opts.taskless?.proposeRepoRules) return "\n\n\u26A0\uFE0F \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D.";
|
|
11028
|
+
const repoFullName = await repoFullNameOnce();
|
|
11029
|
+
if (!repoFullName) {
|
|
11030
|
+
await store.addEvent(null, "step", "\u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u043F\u043E origin");
|
|
11031
|
+
return "\n\n\u26A0\uFE0F \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u2014 \u0441\u043A\u0430\u0436\u0438 \u044D\u0442\u043E \u0432 PR.";
|
|
11032
|
+
}
|
|
11033
|
+
const text = await opts.taskless.proposeRepoRules({ repoFullName, rules, ...run?.name ? { by: run.name } : {}, ...taskSlug ? { taskSlug } : {} }).catch((e) => `\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E: ${e instanceof Error ? e.message : String(e)}`);
|
|
11034
|
+
const line = text ?? "\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E (\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0438\u043B\u0438 \u0442\u0443\u043B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D)";
|
|
11035
|
+
await store.addEvent(null, "step", `\u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043E\u0442 \u0434\u0440\u043E\u043D\u0430: ${line}`);
|
|
11036
|
+
return `
|
|
11037
|
+
|
|
11038
|
+
\u{1F4CC} ${line}`;
|
|
11039
|
+
};
|
|
11040
|
+
const decideRules = async (taskSlug) => {
|
|
11041
|
+
if (!rule_verdicts?.length) return "";
|
|
11042
|
+
if (!opts.taskless?.decideRepoRules) return "";
|
|
11043
|
+
const repoFullName = await repoFullNameOnce();
|
|
11044
|
+
if (!repoFullName) {
|
|
11045
|
+
await store.addEvent(null, "step", "\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo \u043F\u043E origin");
|
|
11046
|
+
return "\n\n\u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u041D\u0415 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C owner/repo.";
|
|
11047
|
+
}
|
|
11048
|
+
const text = await opts.taskless.decideRepoRules({ repoFullName, decisions: rule_verdicts, ...run?.name ? { by: run.name } : {}, ...taskSlug ? { taskSlug } : {} }).catch((e) => `\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${e instanceof Error ? e.message : String(e)}`);
|
|
11049
|
+
const line = text ?? "\u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D (\u043F\u0440\u043E\u0434 \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0438\u043B\u0438 \u0442\u0443\u043B \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D)";
|
|
11050
|
+
await store.addEvent(null, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F: ${line}`);
|
|
11051
|
+
return `
|
|
11052
|
+
|
|
11053
|
+
\u{1F4CC} ${line}`;
|
|
11054
|
+
};
|
|
10593
11055
|
const reportSlug = (slug) => opts.expectedTaskSlug ?? slug;
|
|
10594
11056
|
const uploadShots = async () => {
|
|
10595
11057
|
if (!screenshots?.length) return { attachments: [], warning: "" };
|
|
@@ -10675,13 +11137,14 @@ ${notFoundText}` : notFoundText }],
|
|
|
10675
11137
|
commentWarning = opts.taskless ? "\u0437\u0430\u0434\u0430\u0447\u0430 \u0440\u0435\u0432\u044C\u044E \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430" : "\u043A\u043B\u0438\u0435\u043D\u0442 Taskless \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D";
|
|
10676
11138
|
await store.addEvent(run.reviews_run_id, "step", `\u0432\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: ${commentWarning}`);
|
|
10677
11139
|
}
|
|
11140
|
+
const rulesNote = await decideRules(reviewedSlug);
|
|
10678
11141
|
const trustedPrUrl = reviewed?.pr_url ?? pr_url;
|
|
10679
11142
|
await store.updateRun(null, { status: "review", pr_url: trustedPrUrl });
|
|
10680
11143
|
await store.addEvent(null, "pr_opened", trustedPrUrl);
|
|
10681
11144
|
const commentNote = commentWarning ? `
|
|
10682
11145
|
|
|
10683
11146
|
\u26A0\uFE0F \u0412\u0435\u0440\u0434\u0438\u043A\u0442-\u043A\u043E\u043C\u043C\u0435\u043D\u0442 \u0432 \u0437\u0430\u0434\u0430\u0447\u0443 \u041D\u0415 \u0443\u0435\u0445\u0430\u043B (${commentWarning}) \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0435\u0441\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0435. \u041F\u0440\u043E\u0434\u0443\u0431\u043B\u0438\u0440\u0443\u0439 \u0435\u0433\u043E \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR: \`gh pr comment ${trustedPrUrl} --body "${verdictText} \u2026"\`.` : "";
|
|
10684
|
-
return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E${shotsNote(shots)}${commentNote}` }] };
|
|
11147
|
+
return { content: [{ type: "text", text: `\u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D, \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E${shotsNote(shots)}${commentNote}${rulesNote}` }] };
|
|
10685
11148
|
}
|
|
10686
11149
|
if (iAmVerifier(run) && run?.verifies_run_id == null) {
|
|
10687
11150
|
return {
|
|
@@ -10788,22 +11251,24 @@ ${report}${shots.warning}`,
|
|
|
10788
11251
|
await store.updateRun(null, { ...t.patch, ...branchFix?.patch ?? {} });
|
|
10789
11252
|
await store.addEvent(null, "pr_opened", pr_url);
|
|
10790
11253
|
await postReport(reportSlug(run.task_slug), t.cycle ?? run.review_cycle ?? 0);
|
|
11254
|
+
const rulesNote = await offerRules(reportSlug(run.task_slug));
|
|
10791
11255
|
if (t.isFixIteration) {
|
|
10792
11256
|
await store.addEvent(
|
|
10793
11257
|
null,
|
|
10794
11258
|
"step",
|
|
10795
11259
|
`\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F: \u043D\u043E\u0432\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043F\u043E\u0441\u043B\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \xAB\u043D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438\xBB \u2014 \u043E\u0442\u043A\u0440\u044B\u0442 \u0446\u0438\u043A\u043B \u0440\u0435-\u0440\u0435\u0432\u044C\u044E #${t.cycle}`
|
|
10796
11260
|
);
|
|
10797
|
-
return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F)${shotsNote(shots)}` }] };
|
|
11261
|
+
return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435-\u0440\u0435\u0432\u044C\u044E (\u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u044F)${shotsNote(shots)}${rulesNote}` }] };
|
|
10798
11262
|
}
|
|
10799
|
-
return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}` }] };
|
|
11263
|
+
return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${rulesNote}` }] };
|
|
10800
11264
|
}
|
|
10801
11265
|
await store.updateRun(null, { status: "review", pr_url, ...branchFix?.patch ?? {} });
|
|
10802
11266
|
await store.addEvent(null, "pr_opened", pr_url);
|
|
10803
11267
|
const fallbackSlug = reportSlug(void 0);
|
|
10804
11268
|
if (fallbackSlug) await postReport(fallbackSlug, void 0);
|
|
10805
11269
|
else await store.addEvent(null, "step", `\u043E\u0442\u0447\u0451\u0442 \u0434\u0440\u043E\u043D\u0430 (\u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432 \u0441\u0442\u043E\u0440\u0435): ${report}`);
|
|
10806
|
-
|
|
11270
|
+
const fallbackRulesNote = await offerRules(fallbackSlug);
|
|
11271
|
+
return { content: [{ type: "text", text: `\u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u0443\u0448\u043B\u043E \u043D\u0430 \u0440\u0435\u0432\u044C\u044E${shotsNote(shots)}${fallbackRulesNote}` }] };
|
|
10807
11272
|
}
|
|
10808
11273
|
await store.updateRun(null, { status: "done" });
|
|
10809
11274
|
await store.addEvent(null, "done", pr_url ? `\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0431\u0435\u0437 GitHub-PR (${pr_url})` : "\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0431\u0435\u0437 GitHub-PR");
|
|
@@ -11204,7 +11669,7 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
11204
11669
|
};
|
|
11205
11670
|
const closedBeforeStart = (run) => opts.daemonStartedAt != null && sqliteMs(run.updated_at) < opts.daemonStartedAt;
|
|
11206
11671
|
const recentFailed = allRuns.filter(
|
|
11207
|
-
(r) => r.status === "failed" && ageMs(now, r.updated_at) <= thresholds.failedSpikeWindowMs && !killedByOperator(r) && !closedBeforeStart(r)
|
|
11672
|
+
(r) => r.status === "failed" && ageMs(now, r.updated_at) <= thresholds.failedSpikeWindowMs && !killedByOperator(r) && !closedBeforeStart(r) && !isPreStartFailure(r)
|
|
11208
11673
|
);
|
|
11209
11674
|
if (recentFailed.length >= thresholds.failedSpikeCount) {
|
|
11210
11675
|
candidates.push({
|
|
@@ -11263,6 +11728,7 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
11263
11728
|
if (ageMs(now, run.updated_at) > thresholds.runDownWindowMs) continue;
|
|
11264
11729
|
if (killedByOperator(run)) continue;
|
|
11265
11730
|
if (closedBeforeStart(run)) continue;
|
|
11731
|
+
if (isPreStartFailure(run)) continue;
|
|
11266
11732
|
const repeat = isRepeatDown(run);
|
|
11267
11733
|
candidates.push({
|
|
11268
11734
|
key: `run_failed:${run.id}`,
|
|
@@ -11277,6 +11743,23 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
11277
11743
|
})
|
|
11278
11744
|
});
|
|
11279
11745
|
}
|
|
11746
|
+
for (const run of allRuns) {
|
|
11747
|
+
if (run.status !== "failed" || !isSpawnParked(run)) continue;
|
|
11748
|
+
if (ageMs(now, run.updated_at) > thresholds.runDownWindowMs) continue;
|
|
11749
|
+
if (closedBeforeStart(run)) continue;
|
|
11750
|
+
candidates.push({
|
|
11751
|
+
key: `spawn_parked:${run.id}`,
|
|
11752
|
+
minDurationMs: 0,
|
|
11753
|
+
// серия попыток УЖЕ прошла свой PRE_START_ATTEMPT_LIMIT — отлёживать нечего
|
|
11754
|
+
build: () => ({
|
|
11755
|
+
runId: run.id,
|
|
11756
|
+
level: "decision",
|
|
11757
|
+
title: runTitle(run, "\u0440\u043E\u0439 \u043D\u0435 \u0441\u043C\u043E\u0433 \u0432\u0437\u044F\u0442\u044C\u0441\u044F \u0437\u0430 \u0437\u0430\u0434\u0430\u0447\u0443"),
|
|
11758
|
+
body: `${run.task_slug}: ${run.stop_reason ?? SPAWN_PARK_MARK}`,
|
|
11759
|
+
...hrefOf(run.task_url)
|
|
11760
|
+
})
|
|
11761
|
+
});
|
|
11762
|
+
}
|
|
11280
11763
|
const queuedCount = allRuns.filter((r) => r.status === "queued").length;
|
|
11281
11764
|
const activeDroneCount2 = allRuns.filter((r) => r.kind !== "hero" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
|
|
11282
11765
|
if (queuedCount > 0 && activeDroneCount2 === 0) {
|
|
@@ -11294,6 +11777,31 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
11294
11777
|
})
|
|
11295
11778
|
});
|
|
11296
11779
|
}
|
|
11780
|
+
const GROUP_DEP_MISSING = {
|
|
11781
|
+
id: "dep_missing",
|
|
11782
|
+
title: (n) => `${n} ${plural2(n, ["\u043F\u0440\u043E\u0433\u043E\u043D \u0436\u0434\u0451\u0442", "\u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0436\u0434\u0443\u0442", "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0436\u0434\u0443\u0442"])} \u0431\u043B\u043E\u043A\u0435\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435\u0442`
|
|
11783
|
+
};
|
|
11784
|
+
for (const run of allRuns) {
|
|
11785
|
+
if (run.status !== "queued") continue;
|
|
11786
|
+
if (!run.depends_on || run.depends_on.length === 0) continue;
|
|
11787
|
+
const lost = db.depVerdicts(run.depends_on).filter((d) => d.outcome === "unknown");
|
|
11788
|
+
if (!lost.length) continue;
|
|
11789
|
+
candidates.push({
|
|
11790
|
+
key: `dep_missing:${run.task_slug}`,
|
|
11791
|
+
// по СЛАГУ, а не по id зеркала: тот меняется на рестарте (TAS-485)
|
|
11792
|
+
minDurationMs: thresholds.depMissingMs,
|
|
11793
|
+
group: GROUP_DEP_MISSING,
|
|
11794
|
+
build: () => ({
|
|
11795
|
+
runId: run.id,
|
|
11796
|
+
// Пуш: рой сам из этого не выйдет НИКОГДА, а лечение — одно действие человека.
|
|
11797
|
+
// Ровно тот случай, ради которого уровень decision и заведён.
|
|
11798
|
+
level: "decision",
|
|
11799
|
+
title: runTitle(run, "\u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0435\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435\u0442"),
|
|
11800
|
+
body: `${droneOf(run)}: \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0442\u043E\u0438\u0442 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0441 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C\u044E ${joinCapped(lost.map((d) => d.slug))}, \u043D\u043E \u043F\u043E \u044D\u0442\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u0435 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u2014 \u043D\u0438 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435, \u043D\u0438 \u0432 \u043F\u0440\u043E\u0434\u0435. \u0421\u0430\u043C \u043E\u043D \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442: \u0441\u043D\u0438\u043C\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C (swarm_run_upsert{runId, dependsOn:[]}) \u0438\u043B\u0438 \u0437\u0430\u0432\u0435\u0434\u0438 \u0431\u043B\u043E\u043A\u0435\u0440 \u0437\u0430\u043D\u043E\u0432\u043E.`,
|
|
11801
|
+
...hrefOf(run.task_url)
|
|
11802
|
+
})
|
|
11803
|
+
});
|
|
11804
|
+
}
|
|
11297
11805
|
if (opts.gateReason && queuedCount > 0) {
|
|
11298
11806
|
const reason = opts.gateReason;
|
|
11299
11807
|
candidates.push({
|
|
@@ -11378,6 +11886,7 @@ function buildAnomalyCandidates(db, now, thresholds, opts) {
|
|
|
11378
11886
|
for (const q of db.openQuestions()) {
|
|
11379
11887
|
const run = runsById.get(q.run_id);
|
|
11380
11888
|
if (!run) continue;
|
|
11889
|
+
if (isTerminalStatus(run.status)) continue;
|
|
11381
11890
|
candidates.push({
|
|
11382
11891
|
key: `question_unanswered:${q.id}`,
|
|
11383
11892
|
minDurationMs: thresholds.questionUnansweredMs,
|
|
@@ -11790,13 +12299,35 @@ function makeExclusiveReader(opts) {
|
|
|
11790
12299
|
});
|
|
11791
12300
|
}
|
|
11792
12301
|
function makeAutoReviewReader(opts) {
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
12302
|
+
const ttl = opts.ttlMs ?? 6e4;
|
|
12303
|
+
const now = opts.now ?? Date.now;
|
|
12304
|
+
const log = opts.log ?? ((msg) => console.warn(msg));
|
|
12305
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12306
|
+
const lastSource = /* @__PURE__ */ new Map();
|
|
12307
|
+
const say = (repo, source, msg) => {
|
|
12308
|
+
if (lastSource.get(repo) === source) return;
|
|
12309
|
+
lastSource.set(repo, source);
|
|
12310
|
+
log(msg);
|
|
12311
|
+
};
|
|
12312
|
+
return async (repo) => {
|
|
12313
|
+
const t = now();
|
|
12314
|
+
const hit = cache.get(repo);
|
|
12315
|
+
if (hit && t - hit.at < ttl) return hit.value;
|
|
12316
|
+
const live = await opts.fetch(repo).catch(() => null);
|
|
12317
|
+
if (typeof live === "boolean") {
|
|
12318
|
+
const changed = hit?.value !== live;
|
|
12319
|
+
cache.set(repo, { value: live, at: t });
|
|
12320
|
+
if (changed) lastSource.delete(repo);
|
|
12321
|
+
say(repo, "live", `[autoReview] ${repo}: \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u0430 \u2014 autoReview=${live}`);
|
|
12322
|
+
return live;
|
|
12323
|
+
}
|
|
12324
|
+
if (hit) {
|
|
12325
|
+
say(repo, "stale", `[autoReview] ${repo}: \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E \u2014 \u0434\u0435\u0440\u0436\u0443 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u043E\u0435 autoReview=${hit.value}`);
|
|
12326
|
+
return hit.value;
|
|
12327
|
+
}
|
|
12328
|
+
say(repo, "env", `[autoReview] ${repo}: \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u0430 \u043D\u0438 \u0440\u0430\u0437\u0443 \u2014 \u0431\u0435\u0440\u0443 env SWARM_AUTO_REVIEW=${opts.fallback}`);
|
|
12329
|
+
return opts.fallback;
|
|
12330
|
+
};
|
|
11800
12331
|
}
|
|
11801
12332
|
function makeDroneCapReader(opts) {
|
|
11802
12333
|
const read = makeRepoValueReader({
|
|
@@ -11860,22 +12391,22 @@ function makeMergeGateResolver(deps) {
|
|
|
11860
12391
|
}
|
|
11861
12392
|
|
|
11862
12393
|
// src/envGate.ts
|
|
11863
|
-
import { existsSync as
|
|
11864
|
-
import { join as
|
|
12394
|
+
import { existsSync as existsSync14 } from "fs";
|
|
12395
|
+
import { join as join25 } from "path";
|
|
11865
12396
|
import { createConnection } from "net";
|
|
11866
12397
|
import { execa as execa2 } from "execa";
|
|
11867
12398
|
|
|
11868
12399
|
// src/platform.ts
|
|
11869
|
-
import { existsSync as
|
|
11870
|
-
import { delimiter, join as
|
|
12400
|
+
import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
|
|
12401
|
+
import { delimiter, join as join24 } from "path";
|
|
11871
12402
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
11872
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
12403
|
+
if (name.includes("/") || name.includes("\\")) return existsSync13(name) ? name : null;
|
|
11873
12404
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
11874
12405
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
11875
12406
|
for (const dir of dirs) {
|
|
11876
12407
|
for (const ext of exts) {
|
|
11877
|
-
const candidate =
|
|
11878
|
-
if (
|
|
12408
|
+
const candidate = join24(dir, name + ext);
|
|
12409
|
+
if (existsSync13(candidate)) return candidate;
|
|
11879
12410
|
}
|
|
11880
12411
|
}
|
|
11881
12412
|
return null;
|
|
@@ -11958,8 +12489,8 @@ var COMPOSE_MARKERS = [
|
|
|
11958
12489
|
"compose.yaml"
|
|
11959
12490
|
];
|
|
11960
12491
|
function detectRequirements(repo, io = {}) {
|
|
11961
|
-
const exists = io.exists ??
|
|
11962
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
12492
|
+
const exists = io.exists ?? existsSync14;
|
|
12493
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join25(repo, f)));
|
|
11963
12494
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
11964
12495
|
}
|
|
11965
12496
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -12095,8 +12626,8 @@ import { basename as basename3 } from "path";
|
|
|
12095
12626
|
import { serve as serve2 } from "@hono/node-server";
|
|
12096
12627
|
|
|
12097
12628
|
// src/diff.ts
|
|
12098
|
-
import { statSync as
|
|
12099
|
-
import { join as
|
|
12629
|
+
import { statSync as statSync10 } from "fs";
|
|
12630
|
+
import { join as join26 } from "path";
|
|
12100
12631
|
import { $ as $4 } from "execa";
|
|
12101
12632
|
var MAX_FILES = 200;
|
|
12102
12633
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -12239,7 +12770,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
12239
12770
|
}
|
|
12240
12771
|
function mtime(path) {
|
|
12241
12772
|
try {
|
|
12242
|
-
return
|
|
12773
|
+
return statSync10(path).mtimeMs;
|
|
12243
12774
|
} catch {
|
|
12244
12775
|
return 0;
|
|
12245
12776
|
}
|
|
@@ -12264,7 +12795,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
12264
12795
|
files.push(pf);
|
|
12265
12796
|
}
|
|
12266
12797
|
}
|
|
12267
|
-
files.sort((a, b) => mtime(
|
|
12798
|
+
files.sort((a, b) => mtime(join26(worktree, b.path)) - mtime(join26(worktree, a.path)));
|
|
12268
12799
|
const truncatedFiles = files.length > MAX_FILES;
|
|
12269
12800
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
12270
12801
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -12297,7 +12828,7 @@ async function changedPaths(worktree, opts = {}) {
|
|
|
12297
12828
|
// package.json
|
|
12298
12829
|
var package_default = {
|
|
12299
12830
|
name: "@taskless-app/swarm",
|
|
12300
|
-
version: "0.6.
|
|
12831
|
+
version: "0.6.16",
|
|
12301
12832
|
type: "module",
|
|
12302
12833
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
12303
12834
|
keywords: [
|
|
@@ -12347,7 +12878,7 @@ var package_default = {
|
|
|
12347
12878
|
execa: "^10.0.0",
|
|
12348
12879
|
zod: "3.25.76"
|
|
12349
12880
|
},
|
|
12350
|
-
gitHead: "
|
|
12881
|
+
gitHead: "785b1478f2b81406c9b6524913939707b6c096fb"
|
|
12351
12882
|
};
|
|
12352
12883
|
|
|
12353
12884
|
// src/version.ts
|
|
@@ -13980,10 +14511,10 @@ async function checkDaemonVersion(start, opts = {}) {
|
|
|
13980
14511
|
|
|
13981
14512
|
// src/hostCommands.ts
|
|
13982
14513
|
init_src();
|
|
13983
|
-
import { readFileSync as readFileSync18, renameSync as
|
|
13984
|
-
import { join as
|
|
14514
|
+
import { readFileSync as readFileSync18, renameSync as renameSync9, writeFileSync as writeFileSync13 } from "fs";
|
|
14515
|
+
import { join as join27 } from "path";
|
|
13985
14516
|
function hostCommandStatePath(home) {
|
|
13986
|
-
return
|
|
14517
|
+
return join27(home, "host-command.json");
|
|
13987
14518
|
}
|
|
13988
14519
|
function readState(path) {
|
|
13989
14520
|
try {
|
|
@@ -13995,7 +14526,7 @@ function readState(path) {
|
|
|
13995
14526
|
function writeState(path, state) {
|
|
13996
14527
|
try {
|
|
13997
14528
|
writeFileSync13(`${path}.tmp`, JSON.stringify(state));
|
|
13998
|
-
|
|
14529
|
+
renameSync9(`${path}.tmp`, path);
|
|
13999
14530
|
} catch {
|
|
14000
14531
|
}
|
|
14001
14532
|
}
|
|
@@ -14157,8 +14688,8 @@ async function doRestart(cmd, opts, note) {
|
|
|
14157
14688
|
|
|
14158
14689
|
// src/selfUpdate.ts
|
|
14159
14690
|
init_git();
|
|
14160
|
-
import { existsSync as
|
|
14161
|
-
import { join as
|
|
14691
|
+
import { existsSync as existsSync15 } from "fs";
|
|
14692
|
+
import { join as join28 } from "path";
|
|
14162
14693
|
import { $ as $5 } from "execa";
|
|
14163
14694
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
14164
14695
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -14171,7 +14702,7 @@ function tail(text, max = 400) {
|
|
|
14171
14702
|
}
|
|
14172
14703
|
async function runSelfUpdate(io) {
|
|
14173
14704
|
const run = io.run ?? defaultRun;
|
|
14174
|
-
const exists = io.exists ??
|
|
14705
|
+
const exists = io.exists ?? existsSync15;
|
|
14175
14706
|
const repo = io.repo;
|
|
14176
14707
|
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
14177
14708
|
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
@@ -14200,7 +14731,7 @@ async function runSelfUpdate(io) {
|
|
|
14200
14731
|
};
|
|
14201
14732
|
}
|
|
14202
14733
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
14203
|
-
if (!exists(
|
|
14734
|
+
if (!exists(join28(repo, "pnpm-lock.yaml"))) {
|
|
14204
14735
|
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" };
|
|
14205
14736
|
}
|
|
14206
14737
|
const installed = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -14258,6 +14789,47 @@ function makeOvermindNarrator() {
|
|
|
14258
14789
|
};
|
|
14259
14790
|
}
|
|
14260
14791
|
|
|
14792
|
+
// src/orphanQuestions.ts
|
|
14793
|
+
init_src();
|
|
14794
|
+
var ORPHAN_NOTE_WINDOW_MS = DAY_MS;
|
|
14795
|
+
var ORPHAN_NOTE_PREFIX = "\u0412\u043E\u043F\u0440\u043E\u0441 \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 \u2014 \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u0440\u0430\u043D\u044C\u0448\u0435";
|
|
14796
|
+
function orphanWithdrawReason(status) {
|
|
14797
|
+
return `\u0441\u043D\u044F\u0442 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438: \u043F\u0440\u043E\u0433\u043E\u043D \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D (${status}), \u043E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u043D\u0435\u043A\u043E\u043C\u0443`;
|
|
14798
|
+
}
|
|
14799
|
+
function reapOrphanedQuestions(db, now, windowMs = ORPHAN_NOTE_WINDOW_MS) {
|
|
14800
|
+
const result = { withdrawn: 0, noted: 0 };
|
|
14801
|
+
let open;
|
|
14802
|
+
let runsById;
|
|
14803
|
+
try {
|
|
14804
|
+
open = db.openQuestions();
|
|
14805
|
+
if (open.length === 0) return result;
|
|
14806
|
+
runsById = new Map(db.listRuns().map((r) => [r.id, r]));
|
|
14807
|
+
} catch {
|
|
14808
|
+
return result;
|
|
14809
|
+
}
|
|
14810
|
+
for (const q of open) {
|
|
14811
|
+
const run = runsById.get(q.run_id);
|
|
14812
|
+
if (!run) continue;
|
|
14813
|
+
if (!isTerminalStatus(run.status) && run.superseded_by == null) continue;
|
|
14814
|
+
try {
|
|
14815
|
+
if (now - sqliteMs(run.updated_at) <= windowMs) {
|
|
14816
|
+
db.addEvent(run.id, NOTE_KIND, orphanNoteText(run, q), NOTE_ACTOR);
|
|
14817
|
+
result.noted++;
|
|
14818
|
+
}
|
|
14819
|
+
db.withdrawQuestion(q.id, orphanWithdrawReason(run.status));
|
|
14820
|
+
result.withdrawn++;
|
|
14821
|
+
} catch (e) {
|
|
14822
|
+
console.error(`[orphan-questions] \u0432\u043E\u043F\u0440\u043E\u0441 #${q.id} \u043D\u0435 \u0441\u043D\u044F\u0442, \u043F\u043E\u0432\u0442\u043E\u0440\u044E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0442\u0438\u043A\u043E\u043C:`, e);
|
|
14823
|
+
}
|
|
14824
|
+
}
|
|
14825
|
+
return result;
|
|
14826
|
+
}
|
|
14827
|
+
var NOTE_KIND = "note";
|
|
14828
|
+
var NOTE_ACTOR = "orchestrator";
|
|
14829
|
+
function orphanNoteText(run, q) {
|
|
14830
|
+
return `${ORPHAN_NOTE_PREFIX} (${run.status}). \u041E\u0442\u0432\u0435\u0447\u0430\u0442\u044C \u043D\u0435\u043A\u043E\u043C\u0443, \u0437\u0432\u0430\u0442\u044C \u043D\u0438\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u043C \u2014 \u0442\u0435\u043A\u0441\u0442 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u044E: \xAB${q.text}\xBB`;
|
|
14831
|
+
}
|
|
14832
|
+
|
|
14261
14833
|
// src/probe.ts
|
|
14262
14834
|
init_executors();
|
|
14263
14835
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -14318,18 +14890,18 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
14318
14890
|
}
|
|
14319
14891
|
|
|
14320
14892
|
// src/anomalyState.ts
|
|
14321
|
-
import { existsSync as
|
|
14322
|
-
import { join as
|
|
14893
|
+
import { existsSync as existsSync16, readFileSync as readFileSync19, renameSync as renameSync10, writeFileSync as writeFileSync14 } from "fs";
|
|
14894
|
+
import { join as join29 } from "path";
|
|
14323
14895
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
14324
14896
|
var MAX_RESOLVED = 200;
|
|
14325
14897
|
function emptyState() {
|
|
14326
14898
|
return { since: {}, lastFiredAt: {}, fires: {}, resolved: [], stopQuiet: null };
|
|
14327
14899
|
}
|
|
14328
14900
|
function anomalyStatePath(home) {
|
|
14329
|
-
return
|
|
14901
|
+
return join29(home, "anomaly-state.json");
|
|
14330
14902
|
}
|
|
14331
14903
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
14332
|
-
const exists = io.exists ??
|
|
14904
|
+
const exists = io.exists ?? existsSync16;
|
|
14333
14905
|
const readFile2 = io.readFile ?? ((p) => readFileSync19(p, "utf8"));
|
|
14334
14906
|
const path = anomalyStatePath(home);
|
|
14335
14907
|
if (!exists(path)) return emptyState();
|
|
@@ -14361,7 +14933,7 @@ function saveAnomalyState(home, state, io = {}) {
|
|
|
14361
14933
|
const path = anomalyStatePath(home);
|
|
14362
14934
|
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
14363
14935
|
writeFileSync14(`${p}.tmp`, data);
|
|
14364
|
-
|
|
14936
|
+
renameSync10(`${p}.tmp`, p);
|
|
14365
14937
|
});
|
|
14366
14938
|
writeFile3(path, JSON.stringify(state, null, 2));
|
|
14367
14939
|
}
|
|
@@ -14687,6 +15259,9 @@ function short(files, n = 3) {
|
|
|
14687
15259
|
return files.slice(0, n).join(", ") + (files.length > n ? ` \u0438 \u0435\u0449\u0451 ${files.length - n}` : "");
|
|
14688
15260
|
}
|
|
14689
15261
|
var SPINE_PREDICTION_WEIGHT = 0.7;
|
|
15262
|
+
function spineOfPrediction(spine, predicted) {
|
|
15263
|
+
return predicted.filter((p) => spine.has(p.path) && p.weight >= SPINE_PREDICTION_WEIGHT).map((p) => p.path);
|
|
15264
|
+
}
|
|
14690
15265
|
function decideConflict({ model, candidate, active }) {
|
|
14691
15266
|
if ((candidate.kind ?? "drone") !== "drone") return { kind: "clear" };
|
|
14692
15267
|
if (!hasZoneModel(model)) return { kind: "unknown" };
|
|
@@ -14694,23 +15269,16 @@ function decideConflict({ model, candidate, active }) {
|
|
|
14694
15269
|
if (!predicted.length) return { kind: "unknown" };
|
|
14695
15270
|
if (!active.length) return { kind: "clear" };
|
|
14696
15271
|
const spine = new Set(model.spine);
|
|
14697
|
-
const candidateSpine =
|
|
14698
|
-
if (candidateSpine.
|
|
14699
|
-
const
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
message: `\u0437\u0430\u0434\u0430\u0447\u0430 \u0442\u0440\u043E\u0433\u0430\u0435\u0442 \u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${short(candidateSpine)}) \u2014 \u0442\u0430\u043A\u0438\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B; \u0436\u0434\u0443 ${blocker.run.task_slug} (#${blocker.run.id})`
|
|
14704
|
-
};
|
|
14705
|
-
}
|
|
14706
|
-
for (const w of active) {
|
|
14707
|
-
if (!w.actual) continue;
|
|
14708
|
-
const theirSpine = w.files.filter((f) => spine.has(f));
|
|
14709
|
-
if (theirSpine.length && zoneForFiles(model, w.files)?.zone === SPINE_ZONE) {
|
|
15272
|
+
const candidateSpine = new Set(spineOfPrediction(spine, predicted));
|
|
15273
|
+
if (candidateSpine.size) {
|
|
15274
|
+
for (const w of active) {
|
|
15275
|
+
const theirSpine = w.actual ? w.files.filter((f) => spine.has(f)) : spineOfPrediction(spine, predictForRun(model, w.run));
|
|
15276
|
+
const shared = theirSpine.filter((f) => candidateSpine.has(f));
|
|
15277
|
+
if (!shared.length) continue;
|
|
14710
15278
|
return {
|
|
14711
15279
|
kind: "wait",
|
|
14712
15280
|
blockedBy: w.run,
|
|
14713
|
-
message:
|
|
15281
|
+
message: `\u044D\u0442\u0438 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0440\u043E\u0433\u0430\u044E\u0442 \u043E\u0434\u043D\u0438 \u0444\u0430\u0439\u043B\u044B \u043E\u0431\u0449\u0435\u0433\u043E \u0445\u0440\u0435\u0431\u0442\u0430 (${short(shared)}) \u2014 \u0442\u0430\u043A\u0438\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B: \u0436\u0434\u0443 ${w.run.task_slug} (#${w.run.id})` + (w.actual ? "" : " (\u043F\u043E \u043F\u0440\u0435\u0434\u0441\u043A\u0430\u0437\u0430\u043D\u0438\u044E \u2014 \u0434\u0440\u043E\u043D \u0435\u0449\u0451 \u043D\u0435 \u043D\u0430\u0447\u0430\u043B \u043F\u0438\u0441\u0430\u0442\u044C \u043A\u043E\u0434)")
|
|
14714
15282
|
};
|
|
14715
15283
|
}
|
|
14716
15284
|
}
|
|
@@ -15194,6 +15762,7 @@ async function spawnVerifiersForOpenPrs(deps, stormActive) {
|
|
|
15194
15762
|
}
|
|
15195
15763
|
if (deps.resourcesOk && !deps.resourcesOk()) continue;
|
|
15196
15764
|
if (deps.reviewCap !== void 0 && countActiveVerifiers(deps.db) >= deps.reviewCap) continue;
|
|
15765
|
+
if (!await projectWantsChecks(deps, run)) continue;
|
|
15197
15766
|
const st = await deps.prStatus(run.pr_url).catch(() => null);
|
|
15198
15767
|
if (prLiveness(st) !== "open") continue;
|
|
15199
15768
|
if (st?.isDraft || st?.conflicting) continue;
|
|
@@ -15311,6 +15880,12 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
15311
15880
|
"4b. \u0421\u041D\u0418\u041C\u0410\u0419 \u041F\u0420\u041E\u0414\u0423\u041A\u0422, \u0410 \u041D\u0415 \u041F\u0420\u0415\u0412\u042C\u042E-\u0421\u0422\u0420\u0410\u041D\u0418\u0426\u0423 (TASWM-404). \u0414\u0435\u043C\u043E-\u0440\u043E\u0443\u0442 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u0430 (`/dev/*`, \u043F\u0435\u0441\u043E\u0447\u043D\u0438\u0446\u0430, \u0441\u0442\u043E\u0440\u0438 \u0432 \u043C\u043E\u0431\u0438\u043B\u044C\u043D\u043E\u043C \u0444\u0440\u0435\u0439\u043C\u0435) \u2014 \u041D\u0415 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E \u043C\u043E\u0431\u0438\u043B\u044C\u043D\u043E\u0439 \u0432\u0451\u0440\u0441\u0442\u043A\u0438: \u0443 \u0442\u0430\u043A\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0441\u0432\u043E\u044F \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0430, \u0435\u0451 \u043C\u0435\u043D\u044E \u043E\u0431\u044B\u0447\u043D\u043E \u043D\u0435 \u0441\u0432\u043E\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u0443\u0437\u043A\u043E\u043C \u044D\u043A\u0440\u0430\u043D\u0435, \u0438 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u043C\u0443 \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043F\u043E\u043B\u043E\u0441\u0430 \u0432 \u043F\u0430\u0440\u0443 \u0441\u043E\u0442\u0435\u043D \u043F\u0438\u043A\u0441\u0435\u043B\u0435\u0439 \u0432\u043C\u0435\u0441\u0442\u043E \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0435\u0439 \u0448\u0438\u0440\u0438\u043D\u044B. \u041F\u043E \u0442\u0430\u043A\u043E\u043C\u0443 \u0441\u043D\u0438\u043C\u043A\u0443 \u043D\u0435 \u0432\u0438\u0434\u043D\u043E \u043D\u0438 \u0448\u0438\u0440\u0438\u043D\u044B \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432, \u043D\u0438 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u044F \u0448\u0430\u043F\u043A\u0438, \u043D\u0438 \u043D\u0430\u043F\u043E\u043B\u0437\u0430\u043D\u0438\u044F \u043B\u0438\u043F\u043A\u0438\u0445 \u043F\u043E\u0434\u043F\u0438\u0441\u0435\u0439 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u0412\u0421\u0415\u0413\u0414\u0410 \u0438 \u043D\u0435 \u043B\u043E\u0432\u0438\u0442 \u043D\u0438\u0447\u0435\u0433\u043E (\u0442\u0430\u043A \u0441\u0434\u0430\u043B\u0438 \u0441\u043B\u043E\u043C\u0430\u043D\u043D\u0443\u044E \u043C\u043E\u0431\u0438\u043B\u0443 \u0432 TASWM-403, \u0441\u043A\u0440\u0438\u043D \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u0431\u044B\u043B \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D). \u0421\u043D\u0438\u043C\u0430\u0439 \u0420\u0415\u0410\u041B\u042C\u041D\u042B\u0419 \u0440\u043E\u0443\u0442 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043F\u043E\u0434 \u0436\u0438\u0432\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439; \u043A\u0430\u043A \u044D\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0432 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u0432 \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u0430\u0445. \u041F\u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043A\u043E\u043D\u043D\u043E \u0440\u043E\u0432\u043D\u043E \u0442\u043E\u0433\u0434\u0430, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u0430\u0432\u0438\u0448\u044C \u0441\u0430\u043C \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442 \u0432\u043D\u0435 \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F.",
|
|
15312
15881
|
"\u0427\u0438\u0441\u0442\u043E \u0431\u044D\u043A\u0435\u043D\u0434/\u043B\u043E\u0433\u0438\u043A\u0443/API/\u0434\u0435\u043C\u043E\u043D/CI/\u0442\u0438\u043F\u044B \u2014 \u0448\u0430\u0433\u0438 3\u20134 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438 (\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u041D\u0415 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 \u0438 \u0441\u043A\u0440\u0438\u043D\u044B \u043D\u0435 \u0441\u043D\u0438\u043C\u0430\u0439): \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B (\u0448\u0430\u0433 1) + e2e \u0432 CI \u044D\u0442\u043E \u0443\u0436\u0435 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u044E\u0442; \u044D\u043A\u043E\u043D\u043E\u043C\u0438\u043C \u0432\u0440\u0435\u043C\u044F \u0438 \u0442\u043E\u043A\u0435\u043D\u044B.",
|
|
15313
15882
|
"\u0422\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E \u0437\u043E\u0432\u0438 done(pr_url), \u0438 \u0432 \u0441\u0434\u0430\u0447\u0435 \u043F\u043E\u043A\u0430\u0436\u0438 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E (\u0437\u0435\u043B\u0451\u043D\u044B\u0439 \u0432\u044B\u0432\u043E\u0434/\u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435), \u0430 \u043D\u0435 \xAB\u0434\u043E\u043B\u0436\u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C\xBB.",
|
|
15883
|
+
// TASWM-431: половина правил репозитория в коде не написана, и выясняет их ДРОН —
|
|
15884
|
+
// а закрытая задача уносила это знание с собой. Планка намеренно высокая: «не
|
|
15885
|
+
// больше двух» и «обо что споткнулся» — единственное, что отделяет правило от
|
|
15886
|
+
// пересказа README, а лишнее правило дороже отсутствующего (его читает каждый
|
|
15887
|
+
// следующий дрон).
|
|
15888
|
+
`\u{1F4CC} \u0421\u041F\u041E\u0422\u041A\u041D\u0423\u041B\u0421\u042F \u041E \u041D\u0415\u0417\u0410\u041F\u0418\u0421\u0410\u041D\u041D\u041E\u0415 \u2014 \u041F\u0420\u0415\u0414\u041B\u041E\u0416\u0418 \u041F\u0420\u0410\u0412\u0418\u041B\u041E. \u041F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u0432\u0440\u0435\u043C\u044F \u043D\u0430 \u0442\u043E, \u0447\u0435\u0433\u043E \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0438\u0433\u0434\u0435 \u043D\u0435 \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u043E (\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u0442\u0435\u0441\u0442\u044B \u0436\u0434\u0443\u0442 \u043F\u043E\u0441\u0442\u0433\u0440\u0435\u0441 \u043D\u0430 5433; \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u043D\u0430\u043A\u0430\u0442\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u043D\u0435 \u0442\u0435\u043C, \u0447\u0435\u043C \u043A\u0430\u0436\u0435\u0442\u0441\u044F; \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0437\u0435\u043B\u0451\u043D\u0430\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0441 \u0442\u0430\u043A\u0438\u043C \u0444\u043B\u0430\u0433\u043E\u043C) \u2014 \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u044D\u0442\u043E \u0432 done: \`rules: [{ field: "verify"|"conventions"|"brief"|"env"|"zones"|"spine", value: <\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044F \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438>, why: "\u043E\u0431\u043E \u0447\u0442\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F \u0438 \u0447\u0435\u0433\u043E \u044D\u0442\u043E \u0441\u0442\u043E\u0438\u043B\u043E" }]\`. \u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${DRONE_RULES_PER_RUN_MAX} \u0437\u0430 \u043F\u0440\u043E\u0433\u043E\u043D \u0438 \u0422\u041E\u041B\u042C\u041A\u041E \u0442\u043E, \u043E\u0431\u043E \u0447\u0442\u043E \u0440\u0435\u0430\u043B\u044C\u043D\u043E \u0441\u043F\u043E\u0442\u043A\u043D\u0443\u043B\u0441\u044F: \xAB\u0442\u0435\u0441\u0442\u044B \u0433\u043E\u043D\u044F\u044E\u0442\u0441\u044F \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439, \u043E\u043D\u0430 \u0432 README\xBB \u2014 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E, \u0430 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 README. \u0423\u0436\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043D\u043E\u0435 \u043E\u0442\u0441\u0435\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u043E; \u0440\u0435\u0448\u0438\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u043C. \u041D\u0438\u0447\u0435\u0433\u043E \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u043F\u0438\u0441\u043E\u043A \u044D\u0442\u043E \u043D\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u0441\u0445\u043E\u0434 \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`,
|
|
15314
15889
|
"\u0421\u043A\u0438\u043B\u043B\u044B \u043F\u0440\u0438\u0445\u043E\u0434\u044F\u0442 \u043A \u0442\u0435\u0431\u0435 \u0414\u0412\u0423\u041C\u042F \u043D\u0430\u0431\u043E\u0440\u0430\u043C\u0438, \u0438 \u043E\u0431\u0430 \u0443\u0436\u0435 \u043F\u043E\u0434\u0445\u0432\u0430\u0447\u0435\u043D\u044B (\u0441\u043F\u0430\u0432\u043D \u0438\u0437\u043E\u043B\u0438\u0440\u043E\u0432\u0430\u043D \u043E\u0442 \u044E\u0437\u0435\u0440\u0441\u043A\u043E\u0433\u043E ~/.claude, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u041B\u0418\u0427\u041D\u042B\u0415 \u0441\u043A\u0438\u043B\u043B\u044B \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0430 \u0442\u0435\u0431\u0435 \u043D\u0435 \u0432\u0438\u0434\u043D\u044B). \u2460 \u0420\u043E\u0439\u043D\u044B\u0435 `swarm-drone:*` \u0435\u0434\u0443\u0442 \u0441 \u0420\u041E\u0415\u041C \u0432 \u043B\u044E\u0431\u043E\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u2014 \u0434\u0438\u0441\u0446\u0438\u043F\u043B\u0438\u043D\u0430, \u0432\u0435\u0440\u043D\u0430\u044F \u0432\u0435\u0437\u0434\u0435: swarm-save-often (\u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0438\u0440\u0430\u0435\u0442 \u0441 \u0434\u0440\u043E\u043D\u043E\u043C), swarm-merge-main (\u0431\u0430\u0437\u043E\u0432\u0443\u044E \u0432\u0435\u0442\u043A\u0443 \u0432\u043B\u0438\u0432\u0430\u0442\u044C, \u0430 \u043D\u0435 \u0440\u0435\u0431\u0435\u0439\u0437\u0438\u0442\u044C \u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u0443\u044E), swarm-await-process (\u0436\u0434\u0430\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u0435, \u043D\u0435 sleep), swarm-verify-green (\u0447\u0442\u043E \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0437\u0435\u043B\u0451\u043D\u044B\u043C), systematic-debugging (\u043F\u0435\u0440\u0432\u043E\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0434\u043E \u0444\u0438\u043A\u0441\u0430). \u2461 \u0421\u043A\u0438\u043B\u043B\u044B \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F (.claude/skills, \u0441\u043E\u0441\u0442\u0430\u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430) \u2014 \u0437\u043D\u0430\u043D\u0438\u044F \u043E\u0431 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435: \u043A\u0430\u043A \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0441\u0442\u0435\u043A, \u043A\u0430\u043A \u0433\u043E\u043D\u044F\u0442\u044C \u0442\u0435\u0441\u0442\u044B \u0438 \u0447\u0442\u043E \u0437\u0434\u0435\u0441\u044C \u0437\u0435\u043B\u0451\u043D\u043E\u0435, UI-\u043F\u0440\u0430\u0432\u0438\u043B\u0430. \u041F\u043E\u043B\u044C\u0437\u0443\u0439\u0441\u044F \u043E\u0431\u043E\u0438\u043C\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u0438\u0437\u043E\u0431\u0440\u0435\u0442\u0430\u0439; \u043D\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u043D\u0438\u044F \u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u2014 \u044D\u0442\u043E \u043F\u0440\u043E\u0431\u0435\u043B \u0435\u0433\u043E \u0441\u043A\u0438\u043B\u043B\u043E\u0432, \u0441\u043A\u0430\u0436\u0438 note(), \u0430 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B."
|
|
15315
15890
|
];
|
|
15316
15891
|
if (run.decision) {
|
|
@@ -15322,7 +15897,7 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
15322
15897
|
}
|
|
15323
15898
|
return parts.join("\n");
|
|
15324
15899
|
}
|
|
15325
|
-
var HERO_CHARTER_PATH =
|
|
15900
|
+
var HERO_CHARTER_PATH = join30(dirname4(THIS_FILE), "..", "docs", "HERO.md");
|
|
15326
15901
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
15327
15902
|
const name = run.name || `#${run.id}`;
|
|
15328
15903
|
const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
|
|
@@ -15461,8 +16036,9 @@ var REVIEW_MISSION_BLOCK = [
|
|
|
15461
16036
|
"\u2022 \u041D\u0430\u0445\u043E\u0434\u043A\u0430 \u0434\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0421\u0426\u0415\u041D\u0410\u0420\u0418\u0415\u041C: \xAB\u0442\u0430\u043A\u043E\u0439 \u0432\u0445\u043E\u0434 \u0438\u043B\u0438 \u0442\u0430\u043A\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u2192 \u0432\u043E\u0442 \u0442\u0430\u043A\u043E\u0439 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\xBB. \xAB\u0412\u044B\u0433\u043B\u044F\u0434\u0438\u0442 \u043D\u0435\u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\xBB, \xAB\u0441\u0442\u043E\u0438\u0442 \u043F\u0435\u0440\u0435\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C\xBB, \xAB\u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043F\u043E\u043A\u0440\u044B\u0442\u044C \u0442\u0435\u0441\u0442\u0430\u043C\u0438\xBB \u2014 \u043D\u0435 \u043D\u0430\u0445\u043E\u0434\u043A\u0438; \u0442\u0430\u043A\u043E\u0435 \u043D\u0435 \u043F\u0438\u0448\u0438.",
|
|
15462
16037
|
"\u2022 \u041D\u043E\u043B\u044C \u043D\u0430\u0445\u043E\u0434\u043E\u043A \u2014 \u0437\u0430\u043A\u043E\u043D\u043D\u044B\u0439 \u0432\u0435\u0440\u0434\u0438\u043A\u0442, \u043D\u043E \u0442\u043E\u0433\u0434\u0430 \u043D\u0430\u0437\u043E\u0432\u0438, \u0427\u0422\u041E \u0438\u043C\u0435\u043D\u043D\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u043A\u0430\u043A\u0438\u043C \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0435\u043C. APPROVE \u0431\u0435\u0437 \u044D\u0442\u043E\u0433\u043E \u0440\u043E\u0439 \u0447\u0438\u0442\u0430\u0435\u0442 \u043A\u0430\u043A \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0437, \u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 PR \u0437\u0430\u043D\u043E\u0432\u043E \u0432\u0440\u0443\u0447\u043D\u0443\u044E."
|
|
15463
16038
|
].join("\n");
|
|
15464
|
-
function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY) {
|
|
16039
|
+
function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachmentsBlock, verifyMode = SWARM_VERIFY, pendingRules = []) {
|
|
15465
16040
|
const name = reviewer.name || `#${reviewer.id}`;
|
|
16041
|
+
const rulesBlock = renderPendingRulesBlock(pendingRules);
|
|
15466
16042
|
const prUrl = reviewRun.pr_url ?? "(\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PR \u2014 \u0441\u043C. \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430)";
|
|
15467
16043
|
const classesStep = reviewRun.review_cycle > 0 ? "" : "\u043F\u0440\u043E\u0439\u0434\u0438 \u043A\u043B\u0430\u0441\u0441\u044B \u0434\u0435\u0444\u0435\u043A\u0442\u043E\u0432 (\u043F\u043E \u043A\u0430\u0436\u0434\u043E\u043C\u0443 \u2014 \u043E\u0442\u0432\u0435\u0442 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \xAB\u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043C\u043E\xBB \u0442\u043E\u0436\u0435), ";
|
|
15468
16044
|
return [
|
|
@@ -15504,6 +16080,7 @@ function buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments
|
|
|
15504
16080
|
`2. \u0427\u0435\u043A\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0438\u0441\u044C (\u0437\u0435\u043B\u0451\u043D\u044B\u0435 \u0438\u043B\u0438 \u0441 \u043A\u0440\u0430\u0441\u043D\u044B\u043C) \u0438\u043B\u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0438\u0445 \u0432\u043E\u0432\u0441\u0435 \u043D\u0435\u0442 \u2014 \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0430 \u0441\u0430\u043C PR: \`gh pr comment ${prUrl} --body "..."\` (\u041D\u0415 \`gh pr review\` \u2014 GitHub \u0435\u0433\u043E \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C PR). \u041D\u0430\u0447\u043D\u0438 \u0441 **APPROVE** \u0438\u043B\u0438 **REQUEST CHANGES** \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u043C\u0438, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0438\u043A\u0430: \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B, \u0447\u0442\u043E \u043D\u0430\u0448\u0451\u043B; \u0434\u043B\u044F REQUEST CHANGES \u2014 \u0442\u043E\u0447\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B/\u0441\u0442\u0440\u043E\u043A\u0438 \u0438 \u0447\u0442\u043E \u0447\u0438\u043D\u0438\u0442\u044C.`
|
|
15505
16081
|
],
|
|
15506
16082
|
`3. \u0412\u044B\u0437\u043E\u0432\u0438 done(${prUrl}, verdict: "approve"|"changes", summary: "\u043A\u043E\u0440\u043E\u0442\u043A\u043E \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B") \u2014 \u0440\u043E\u0439 \u0441\u0430\u043C \u0440\u0430\u0437\u043D\u0435\u0441\u0451\u0442 summary \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 \u0437\u0430\u0434\u0430\u0447\u0443-\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A, \u0430 verdict \u0437\u0430\u043F\u0438\u0448\u0435\u0442 \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D (\u0431\u0435\u0439\u0434\u0436 \u0432 \u0432\u0435\u0431\u0435). \u0422\u044B \u0441\u0430\u043C \u0440\u0435\u0432\u044C\u044E \u041D\u0415 \u043F\u043E\u0434\u043B\u0435\u0436\u0438\u0448\u044C \u2014 \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0438 \u043D\u0435\u0442.`,
|
|
16083
|
+
...rulesBlock ? ["", rulesBlock] : [],
|
|
15507
16084
|
'\u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D: \u0442\u0440\u0435\u0442\u044C\u0435\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u0430 \u043D\u0435\u0442, \u0438 \u0443\u0441\u043F\u0435\u0445\u043E\u043C \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F (TASWM-375) \u2014 done() \u0431\u0435\u0437 verdict \u043E\u0442\u043A\u0430\u0436\u0435\u0442, \u0430 \u043F\u0440\u043E\u0433\u043E\u043D, \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044F \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430, \u0440\u043E\u0439 \u043F\u043E\u043C\u0435\u0442\u0438\u0442 "\u0443\u043F\u0430\u043B" \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442 \u0440\u0435\u0432\u044C\u044E \u0437\u0430\u043D\u043E\u0432\u043E. \u041E\u0442\u043A\u0430\u0437\u0430\u043B done() \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043E\u0441\u0442\u0430\u0432\u044C \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u043E\u043C \u0432 PR (\u0448\u0430\u0433 2): \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u0438\u0442 \u0435\u0433\u043E \u043E\u0442\u0442\u0443\u0434\u0430.'
|
|
15508
16085
|
].join("\n");
|
|
15509
16086
|
}
|
|
@@ -15622,7 +16199,7 @@ async function wakeRun(deps, runId) {
|
|
|
15622
16199
|
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` };
|
|
15623
16200
|
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` };
|
|
15624
16201
|
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` };
|
|
15625
|
-
const worktreeExists = deps.worktreeExists ??
|
|
16202
|
+
const worktreeExists = deps.worktreeExists ?? existsSync17;
|
|
15626
16203
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
15627
16204
|
const wt = run.worktree_path;
|
|
15628
16205
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -15707,9 +16284,9 @@ function repoHasCi(deps, run) {
|
|
|
15707
16284
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
15708
16285
|
for (const base3 of [worktreePath, repo]) {
|
|
15709
16286
|
if (!base3) continue;
|
|
15710
|
-
const dir =
|
|
16287
|
+
const dir = join30(base3, ".github", "workflows");
|
|
15711
16288
|
try {
|
|
15712
|
-
if (
|
|
16289
|
+
if (readdirSync13(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
15713
16290
|
} catch {
|
|
15714
16291
|
}
|
|
15715
16292
|
}
|
|
@@ -15818,7 +16395,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
15818
16395
|
if (stats.count >= CI_WAKE_LIMIT || !canContinue) {
|
|
15819
16396
|
const why = !canContinue ? "worktree/\u0441\u0435\u0441\u0441\u0438\u044F \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : `${CI_WAKE_LIMIT} \u043F\u043E\u0431\u0443\u0434(\u043E\u043A) \u043F\u043E \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 CI \u043D\u0435 \u0445\u0432\u0430\u0442\u0438\u043B\u043E`;
|
|
15820
16397
|
deps.db.updateRun(reviewer.id, { status: "stalled" });
|
|
15821
|
-
const freshWanted = await
|
|
16398
|
+
const freshWanted = await projectWantsChecks(deps, run);
|
|
15822
16399
|
const freshPlan = freshWanted ? "\u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 PR" : "\u0441\u0432\u0435\u0436\u0435\u0433\u043E \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E: \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E";
|
|
15823
16400
|
deps.db.addEvent(reviewer.id, "stalled", `CI \u0433\u043E\u0442\u043E\u0432, \u043D\u043E \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E: ${why} \u2014 ${freshPlan}`);
|
|
15824
16401
|
narrate(deps, `${droneLabel(reviewer)} \u0436\u0434\u0430\u043B CI, \u043D\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${why}) \u2014 ${freshPlan}.`);
|
|
@@ -15858,13 +16435,16 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
15858
16435
|
return true;
|
|
15859
16436
|
}
|
|
15860
16437
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
15861
|
-
const exists = deps.worktreeExists ??
|
|
16438
|
+
const exists = deps.worktreeExists ?? existsSync17;
|
|
15862
16439
|
const transcript = deps.hasTranscript ?? executorHasSession;
|
|
15863
16440
|
return exists(worktree) && transcript(worktree);
|
|
15864
16441
|
}
|
|
15865
|
-
async function
|
|
16442
|
+
async function projectWantsChecks(deps, run) {
|
|
15866
16443
|
if (!deps.autoReviewForRepo) return true;
|
|
15867
|
-
return await deps.autoReviewForRepo(effectiveRepo(deps, run)).catch(() =>
|
|
16444
|
+
return await deps.autoReviewForRepo(effectiveRepo(deps, run)).catch((e) => {
|
|
16445
|
+
console.warn(`[overmind] autoReview \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (${effectiveRepo(deps, run)}) \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u043A\u0430\u043A \u0440\u0430\u043D\u044C\u0448\u0435:`, e);
|
|
16446
|
+
return true;
|
|
16447
|
+
});
|
|
15868
16448
|
}
|
|
15869
16449
|
async function spawnFreshReviewerAfterStall(deps, run, why) {
|
|
15870
16450
|
if (!deps.spawnReviewer) return;
|
|
@@ -16161,6 +16741,15 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
16161
16741
|
// причина одна на машину — в лог уходит одной строкой, а не по строке на прогон
|
|
16162
16742
|
});
|
|
16163
16743
|
}
|
|
16744
|
+
const spentAttempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
|
|
16745
|
+
if (spentAttempts >= PRE_START_ATTEMPT_LIMIT) {
|
|
16746
|
+
const why = `${PRE_START_MARK} ${spentAttempts} \u0440\u0430\u0437\u0430 \u043F\u043E\u0434\u0440\u044F\u0434 \u2014 ${SPAWN_PARK_MARK}, \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u043E\u0433\u043E \u0437\u0430\u0445\u043E\u0434\u0430 \u043D\u0435 \u0434\u0435\u043B\u0430\u044E (TASWM-422)`;
|
|
16747
|
+
deps.db.updateRun(run.id, { status: "stopped", stop_reason: why });
|
|
16748
|
+
deps.db.addEvent(run.id, "step", why);
|
|
16749
|
+
const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), `pre-start-exhausted:${run.task_slug}`, why, GATE_LOG_HEARTBEAT_TICKS);
|
|
16750
|
+
if (d.log) console.error(`[overmind] ${run.task_slug}: ${why}${d.changed ? "" : ` \u2014 \u0435\u0449\u0451 \u0432\u0438\u0441\u0438\u0442, \u0442\u0438\u043A ${d.ticks}`}`);
|
|
16751
|
+
return "closed";
|
|
16752
|
+
}
|
|
16164
16753
|
const mergedUrl = doneMergedPrForSlug(deps.db, run.task_slug) ?? (deps.mergedPrForBranch ? await deps.mergedPrForBranch(run.task_slug, run.repo || "").catch(() => null) : null);
|
|
16165
16754
|
if (mergedUrl) {
|
|
16166
16755
|
await closeDuplicateTaskRun(deps, run, mergedUrl);
|
|
@@ -16171,7 +16760,11 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
16171
16760
|
}
|
|
16172
16761
|
const depVerdicts = deps.db.depVerdicts(run.depends_on);
|
|
16173
16762
|
const unmet = depVerdicts.filter((d) => !d.met);
|
|
16174
|
-
if (unmet.length)
|
|
16763
|
+
if (unmet.length) {
|
|
16764
|
+
const lost = unmet.filter((d) => d.outcome === "unknown");
|
|
16765
|
+
const why = lost.length ? `\u0436\u0434\u0451\u0442 \u0431\u043B\u043E\u043A\u0435\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u043E\u0433\u043E \u041D\u0415\u0422: ${lost.map((d) => d.slug).join(", ")} \u2014 \u043F\u043E \u044D\u0442\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u0435 \u043D\u0435\u0442 \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430; \u0441\u0430\u043C \u043D\u0435 \u043F\u043E\u0435\u0434\u0435\u0442, \u0441\u043D\u0438\u043C\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C (swarm_run_upsert{dependsOn:[]}) \u0438\u043B\u0438 \u0437\u0430\u0432\u0435\u0434\u0438 \u0431\u043B\u043E\u043A\u0435\u0440 \u0437\u0430\u043D\u043E\u0432\u043E (TASWM-399)` : `\u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438: ${unmet.map((d) => d.slug).join(", ")}`;
|
|
16766
|
+
return deferSpawn(deps, run, why);
|
|
16767
|
+
}
|
|
16175
16768
|
if (!await envGateAllows(deps, run)) return "skipped";
|
|
16176
16769
|
if (!await projectQuotaGateAllows(deps, run)) return "skipped";
|
|
16177
16770
|
const prodOverride = await prodOverrideReason(deps, run, ["queued"]);
|
|
@@ -16181,6 +16774,8 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
16181
16774
|
}
|
|
16182
16775
|
noteExhaustedDeps(deps, run, depVerdicts);
|
|
16183
16776
|
await fillSuggestedArea(deps, run);
|
|
16777
|
+
const repoGap = unresolvedRepoReason(run.repo, deps.reposServed ? deps.reposServed() : []);
|
|
16778
|
+
if (repoGap) return await failBeforeStart(deps, run, repoGap);
|
|
16184
16779
|
try {
|
|
16185
16780
|
await deps.spawn(run);
|
|
16186
16781
|
if (deps.spawnDefer) clearDefer(deps.spawnDefer, `run:${run.id}`);
|
|
@@ -16197,10 +16792,32 @@ async function trySpawnQueuedRun(deps, run) {
|
|
|
16197
16792
|
return "skipped";
|
|
16198
16793
|
}
|
|
16199
16794
|
if (e instanceof AreaBusyError) return deferSpawn(deps, run, e.message);
|
|
16200
|
-
deps
|
|
16201
|
-
|
|
16795
|
+
return await failBeforeStart(deps, run, String(e));
|
|
16796
|
+
}
|
|
16797
|
+
}
|
|
16798
|
+
async function failBeforeStart(deps, run, reason) {
|
|
16799
|
+
deps.db.updateRun(run.id, { status: "failed", stop_reason: `${PRE_START_MARK}: ${reason}` });
|
|
16800
|
+
deps.db.addEvent(run.id, "failed", `${PRE_START_MARK} \u2014 ${reason}`);
|
|
16801
|
+
const attempts = preStartAttempts(deps.db.listRuns(), run.task_slug);
|
|
16802
|
+
if (attempts < PRE_START_ATTEMPT_LIMIT) {
|
|
16803
|
+
narrate(deps, `${droneLabel(run)} \u2014 ${PRE_START_MARK} (\u043F\u043E\u043F\u044B\u0442\u043A\u0430 ${attempts} \u0438\u0437 ${PRE_START_ATTEMPT_LIMIT}): ${reason}`);
|
|
16202
16804
|
return "failed";
|
|
16203
16805
|
}
|
|
16806
|
+
const parked = parkReason(attempts, reason);
|
|
16807
|
+
deps.db.updateRun(run.id, { stop_reason: parked });
|
|
16808
|
+
deps.db.addEvent(run.id, "step", parked);
|
|
16809
|
+
narrate(deps, `${run.task_slug}: ${parked}`);
|
|
16810
|
+
if (deps.parkTask) {
|
|
16811
|
+
const ok = await deps.parkTask(run, parked).catch(() => false);
|
|
16812
|
+
if (!ok) {
|
|
16813
|
+
deps.db.addEvent(
|
|
16814
|
+
run.id,
|
|
16815
|
+
"step",
|
|
16816
|
+
`${SPAWN_PARK_MARK}: \u0443\u0432\u0435\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (Taskless \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0438\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0440\u0435\u0437\u043E\u043B\u0432\u0438\u0442\u0441\u044F) \u2014 \u0441\u043F\u0430\u0432\u043D\u0438\u0442\u044C \u0435\u0451 \u044D\u0442\u0430 \u043C\u0430\u0448\u0438\u043D\u0430 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043D\u0435 \u0431\u0443\u0434\u0435\u0442, \u043D\u043E \u0443\u0431\u0440\u0430\u0442\u044C \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043F\u0440\u0438\u0434\u0451\u0442\u0441\u044F \u0440\u0443\u043A\u0430\u043C\u0438`
|
|
16817
|
+
);
|
|
16818
|
+
}
|
|
16819
|
+
}
|
|
16820
|
+
return "failed";
|
|
16204
16821
|
}
|
|
16205
16822
|
async function batchSpawnQueue(deps, droneCap) {
|
|
16206
16823
|
for (const run of orderQueue(deps.db.listRuns("queued"))) {
|
|
@@ -16222,7 +16839,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
16222
16839
|
}
|
|
16223
16840
|
if (!candidate) return;
|
|
16224
16841
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
16225
|
-
const worktreeExists = deps.worktreeExists ??
|
|
16842
|
+
const worktreeExists = deps.worktreeExists ?? existsSync17;
|
|
16226
16843
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
16227
16844
|
const wt = candidate.worktree_path;
|
|
16228
16845
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -16391,7 +17008,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
16391
17008
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
16392
17009
|
if (!deps.respawn) return;
|
|
16393
17010
|
const wt = resumeCandidate.worktree_path;
|
|
16394
|
-
const worktreeExists = deps.worktreeExists ??
|
|
17011
|
+
const worktreeExists = deps.worktreeExists ?? existsSync17;
|
|
16395
17012
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
16396
17013
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
16397
17014
|
const mode = canContinue ? "continue" : "fresh";
|
|
@@ -16660,7 +17277,7 @@ function buildEscalationCandidates(db) {
|
|
|
16660
17277
|
}
|
|
16661
17278
|
async function tickOnce(deps) {
|
|
16662
17279
|
const now = deps.now ? deps.now() : Date.now();
|
|
16663
|
-
const worktreeExists = deps.worktreeExists ??
|
|
17280
|
+
const worktreeExists = deps.worktreeExists ?? existsSync17;
|
|
16664
17281
|
const hasTranscript = deps.hasTranscript ?? executorHasSession;
|
|
16665
17282
|
deps.sampleHost?.();
|
|
16666
17283
|
if (deps.rehydrate) {
|
|
@@ -16685,14 +17302,6 @@ async function tickOnce(deps) {
|
|
|
16685
17302
|
}
|
|
16686
17303
|
}
|
|
16687
17304
|
}
|
|
16688
|
-
if (deps.heartbeat) {
|
|
16689
|
-
const hb = deps.heartbeat;
|
|
16690
|
-
const sinceLast = hb.lastAt == null ? Infinity : now - hb.lastAt;
|
|
16691
|
-
if (sinceLast >= hb.intervalMs) {
|
|
16692
|
-
hb.lastAt = now;
|
|
16693
|
-
await hb.push();
|
|
16694
|
-
}
|
|
16695
|
-
}
|
|
16696
17305
|
if (deps.droneBridge) {
|
|
16697
17306
|
const br = deps.droneBridge;
|
|
16698
17307
|
const sinceLast = br.lastAt == null ? Infinity : now - br.lastAt;
|
|
@@ -16913,7 +17522,7 @@ async function tickOnce(deps) {
|
|
|
16913
17522
|
// не порядком веток в 900-строчной функции: увидели живого — значит закрывать его
|
|
16914
17523
|
// рано, спавнить второго нельзя.
|
|
16915
17524
|
liveOrWaitingReviewers(deps.db, run.id).length === 0;
|
|
16916
|
-
if (reviewerGatesOk && deps.spawnReviewer && await
|
|
17525
|
+
if (reviewerGatesOk && deps.spawnReviewer && await projectWantsChecks(deps, run)) {
|
|
16917
17526
|
if (run.review_cycle >= REVIEW_CYCLE_LIMIT) {
|
|
16918
17527
|
if (!deps.db.events(run.id).some((e) => e.kind === "step" && (e.text ?? "").includes(REVIEW_CYCLE_ESCALATION_MARK))) {
|
|
16919
17528
|
deps.db.addEvent(run.id, "step", `${REVIEW_CYCLE_ESCALATION_MARK}: ${run.review_cycle} \u0446\u0438\u043A\u043B(\u043E\u0432) \u0440\u0435\u0432\u044C\u044E-\u0444\u0438\u043A\u0441\u043E\u0432 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A`);
|
|
@@ -16991,6 +17600,13 @@ async function tickOnce(deps) {
|
|
|
16991
17600
|
liveChildPpids = null;
|
|
16992
17601
|
logTransientSpawnError("ps (livePpids)", e);
|
|
16993
17602
|
}
|
|
17603
|
+
if (deps.sweepCost) {
|
|
17604
|
+
try {
|
|
17605
|
+
deps.sweepCost();
|
|
17606
|
+
} catch (e) {
|
|
17607
|
+
console.error("[overmind] \u0441\u0432\u0438\u043F \u0443\u0447\u0451\u0442\u0430 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u0438 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
|
|
17608
|
+
}
|
|
17609
|
+
}
|
|
16994
17610
|
for (const run of [...deps.db.listRuns("running"), ...deps.db.listRuns("blocked")]) {
|
|
16995
17611
|
const alive = run.pid != null && deps.isAlive(run.pid);
|
|
16996
17612
|
if (!alive && deps.db.takeRun && !await deps.db.takeRun(run.id)) continue;
|
|
@@ -17216,6 +17832,16 @@ async function tickOnce(deps) {
|
|
|
17216
17832
|
if (deps.respawn && deps.resourcesOk && !stormActive) {
|
|
17217
17833
|
await resurrectStormDead(deps, worktreeExists, hasTranscript, { gateOpen: deps.resourcesOk(), cap: droneCap });
|
|
17218
17834
|
}
|
|
17835
|
+
try {
|
|
17836
|
+
const reaped = reapOrphanedQuestions(deps.db, now);
|
|
17837
|
+
if (reaped.withdrawn > 0) {
|
|
17838
|
+
console.log(
|
|
17839
|
+
`[overmind] \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B: \u0441\u043D\u044F\u0442\u043E ${reaped.withdrawn}, \u0437\u0430\u043C\u0435\u0442\u043A\u043E\u0439 \u0432 \u043B\u0435\u043D\u0442\u0443 \u0443\u0448\u043B\u043E ${reaped.noted}`
|
|
17840
|
+
);
|
|
17841
|
+
}
|
|
17842
|
+
} catch (e) {
|
|
17843
|
+
console.error("[overmind] \u0443\u0431\u043E\u0440\u043A\u0430 \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C:", e);
|
|
17844
|
+
}
|
|
17219
17845
|
if (deps.anomalies && !deps.control?.paused) {
|
|
17220
17846
|
const az = deps.anomalies;
|
|
17221
17847
|
let ready = [];
|
|
@@ -17224,7 +17850,7 @@ async function tickOnce(deps) {
|
|
|
17224
17850
|
if (deps.autoReviewForRepo && az.autoReviewEnabled) {
|
|
17225
17851
|
for (const r of deps.db.listRuns("review")) {
|
|
17226
17852
|
if (!r.pr_url || autoReviewSnapshot.has(r.repo)) continue;
|
|
17227
|
-
autoReviewSnapshot.set(r.repo, await
|
|
17853
|
+
autoReviewSnapshot.set(r.repo, await projectWantsChecks(deps, r));
|
|
17228
17854
|
}
|
|
17229
17855
|
}
|
|
17230
17856
|
const candidates = [
|
|
@@ -17367,7 +17993,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
17367
17993
|
model: reviewRun.model,
|
|
17368
17994
|
review_model: reviewRun.review_model
|
|
17369
17995
|
});
|
|
17370
|
-
const wt =
|
|
17996
|
+
const wt = join30(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
17371
17997
|
try {
|
|
17372
17998
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
17373
17999
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -17424,12 +18050,13 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
17424
18050
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
17425
18051
|
workspace_id: reviewRun.workspace_id
|
|
17426
18052
|
});
|
|
17427
|
-
const wt =
|
|
18053
|
+
const wt = join30(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
17428
18054
|
try {
|
|
17429
18055
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
17430
18056
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
17431
18057
|
const attachments = await deps.taskAttachments?.(reviewRun.task_slug, wt, reviewRun.workspace_id);
|
|
17432
|
-
const
|
|
18058
|
+
const pendingRules = await deps.pendingRepoRules?.(repo, reviewRun.task_slug).catch(() => []) ?? [];
|
|
18059
|
+
const prompt = buildReviewerPrompt(reviewRun, reviewer, checklist, branch, attachments, verifyMode, pendingRules);
|
|
17433
18060
|
const pid = deps.launch({ worktree: wt, run: reviewer, prompt });
|
|
17434
18061
|
deps.db.updateRun(reviewer.id, { status: "running", worktree_path: wt, branch, pid, pr_url: reviewRun.pr_url });
|
|
17435
18062
|
deps.db.addEvent(reviewRun.id, "step", `\u0430\u0432\u0442\u043E-\u0440\u0435\u0432\u044C\u044E: \u043F\u043E\u0434\u043D\u044F\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440 ${reviewer.name} (#${reviewer.id})`);
|
|
@@ -17499,7 +18126,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
|
|
|
17499
18126
|
// штамп цикла: фикс-итерацию проверяет свежий верификатор
|
|
17500
18127
|
workspace_id: verifiedRun.workspace_id
|
|
17501
18128
|
});
|
|
17502
|
-
const wt =
|
|
18129
|
+
const wt = join30(deps.worktreesDir, basename4(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
|
|
17503
18130
|
try {
|
|
17504
18131
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
17505
18132
|
await deps.git.setWorktreeIdentity(wt, verifier.name);
|
|
@@ -17537,7 +18164,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
17537
18164
|
]);
|
|
17538
18165
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
17539
18166
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
17540
|
-
if (!
|
|
18167
|
+
if (!existsSync17(wt)) {
|
|
17541
18168
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
17542
18169
|
const source = await addWorktree(repo, wt, branch, {
|
|
17543
18170
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -17564,7 +18191,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
17564
18191
|
deps.db.updateRun(run.id, { status: "running", pid });
|
|
17565
18192
|
}
|
|
17566
18193
|
async function syncCrashedOnStartup(deps) {
|
|
17567
|
-
const worktreeExists = deps.worktreeExists ??
|
|
18194
|
+
const worktreeExists = deps.worktreeExists ?? existsSync17;
|
|
17568
18195
|
let orphaned = 0;
|
|
17569
18196
|
let merged = 0;
|
|
17570
18197
|
let requeued = 0;
|
|
@@ -17653,10 +18280,12 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
17653
18280
|
roots: scanRootsList,
|
|
17654
18281
|
maxDepth: SWARM_SCAN_DEPTH,
|
|
17655
18282
|
cachePath: REPO_MAP_PATH,
|
|
18283
|
+
// TASWM-418: кэш обязан протухать — иначе новый клон на машине не находится никогда.
|
|
18284
|
+
scanTtlMs: SWARM_REPO_SCAN_TTL_MS,
|
|
17656
18285
|
state: repoMap,
|
|
17657
18286
|
log: (m) => console.log(m)
|
|
17658
18287
|
});
|
|
17659
|
-
|
|
18288
|
+
let repoRegistry = await rebuildRegistry();
|
|
17660
18289
|
if (repoRegistry.length === 0) {
|
|
17661
18290
|
console.warn(
|
|
17662
18291
|
`[overmind] \u041D\u0415 \u041D\u0410\u0428\u0401\u041B \u041D\u0418 \u041E\u0414\u041D\u041E\u0413\u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u2014 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0431\u0440\u0430\u0442\u044C\u0441\u044F \u043D\u0435 \u0431\u0443\u0434\u0443\u0442: \u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0438\u043C\u0435\u043D\u0435\u043C owner/repo, \u0430 \u0441\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u0441 \u0447\u0435\u043C. \u0418\u0441\u043A\u0430\u043B \u0432: ${scanRootsList.slice(0, 6).join(", ")}. ` + // TASWM-336: почему пусто — три РАЗНЫЕ причины, и лечатся они по-разному. Одна
|
|
@@ -17686,8 +18315,13 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
17686
18315
|
// Периодическая пересборка (шаг 0a4 тика): применяем новый реестр к ЖИВОМУ клиенту —
|
|
17687
18316
|
// и список для claim/heartbeat, и обратную карту owner/repo → путь.
|
|
17688
18317
|
refreshRepoMap: async () => {
|
|
17689
|
-
|
|
17690
|
-
|
|
18318
|
+
repoRegistry = await rebuildRegistry();
|
|
18319
|
+
client.setRepoRegistry(repoRegistry);
|
|
18320
|
+
},
|
|
18321
|
+
// TASWM-422: что машина обслуживает ПРЯМО СЕЙЧАС — читается из ЖИВОГО реестра (он
|
|
18322
|
+
// пересобирается тиком выше), чтобы причина «клона нет на этой машине» называла
|
|
18323
|
+
// актуальный список, а не снимок на старте демона.
|
|
18324
|
+
reposServed: () => repoRegistry.map((e) => e.repoFullName)
|
|
17691
18325
|
};
|
|
17692
18326
|
}
|
|
17693
18327
|
var crashGuardsInstalled = false;
|
|
@@ -17707,22 +18341,22 @@ async function migrateWorktreesAtStartup() {
|
|
|
17707
18341
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
17708
18342
|
targetDir: WORKTREES_DIR,
|
|
17709
18343
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
17710
|
-
exists: (d) =>
|
|
18344
|
+
exists: (d) => existsSync17(d),
|
|
17711
18345
|
isNonEmptyDir: (d) => {
|
|
17712
18346
|
try {
|
|
17713
|
-
return
|
|
18347
|
+
return readdirSync13(d).length > 0;
|
|
17714
18348
|
} catch {
|
|
17715
18349
|
return false;
|
|
17716
18350
|
}
|
|
17717
18351
|
},
|
|
17718
18352
|
liveDronesUnder: (d) => pidsWithCwdUnder(d).length > 0,
|
|
17719
|
-
rename: (from, to) =>
|
|
18353
|
+
rename: (from, to) => renameSync11(from, to),
|
|
17720
18354
|
repairMoved: (t) => repairMovedWorktrees(t),
|
|
17721
18355
|
log: (m) => console.log(m)
|
|
17722
18356
|
});
|
|
17723
18357
|
} catch (e) {
|
|
17724
18358
|
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);
|
|
17725
|
-
return
|
|
18359
|
+
return existsSync17(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
17726
18360
|
}
|
|
17727
18361
|
}
|
|
17728
18362
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -17742,6 +18376,14 @@ async function runDaemon(opts) {
|
|
|
17742
18376
|
const daemonStart = captureDaemonStart();
|
|
17743
18377
|
const latestRelease = fetchLatestVersion();
|
|
17744
18378
|
const worktreesDir = await migrateWorktreesAtStartup();
|
|
18379
|
+
{
|
|
18380
|
+
const swept = sweepLegacyRunLogs(RUN_LOGS_DIR);
|
|
18381
|
+
if (swept.moved || swept.failed) {
|
|
18382
|
+
console.log(
|
|
18383
|
+
`[overmind] run-logs: ${swept.moved} \u043A\u0430\u0442\u0430\u043B\u043E\u0433(\u043E\u0432) \u0441\u0442\u0430\u0440\u043E\u0439 \u0440\u0430\u0441\u043A\u043B\u0430\u0434\u043A\u0438 (\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044F \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u0430) \u0443\u0431\u0440\u0430\u043D\u043E \u0432 ${swept.legacyDir}` + (swept.failed ? `, \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u043D\u0435\u0441\u0442\u0438: ${swept.failed}` : "")
|
|
18384
|
+
);
|
|
18385
|
+
}
|
|
18386
|
+
}
|
|
17745
18387
|
if (!opts.repo) {
|
|
17746
18388
|
const remembered = effectiveRepoPaths("", loadRememberedRepos(SWARM_HOME))[0];
|
|
17747
18389
|
if (remembered) opts.repo = remembered;
|
|
@@ -17752,7 +18394,8 @@ async function runDaemon(opts) {
|
|
|
17752
18394
|
host,
|
|
17753
18395
|
repoMap: repoMapState,
|
|
17754
18396
|
refreshRepoMap: rebuildRepoMap,
|
|
17755
|
-
prodMcp
|
|
18397
|
+
prodMcp,
|
|
18398
|
+
reposServed
|
|
17756
18399
|
} = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
|
|
17757
18400
|
await preflightAuth();
|
|
17758
18401
|
const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
|
|
@@ -17798,6 +18441,8 @@ async function runDaemon(opts) {
|
|
|
17798
18441
|
overmindEntry: THIS_FILE,
|
|
17799
18442
|
dbPath: DB_PATH,
|
|
17800
18443
|
runLogsDir: RUN_LOGS_DIR,
|
|
18444
|
+
// TASWM-419: чем именуется каталог лога прогона — стабильные ключи из стора, не номер.
|
|
18445
|
+
runLogKeys: (run) => db.runLogKeys(run.id),
|
|
17801
18446
|
// Кем исполнять (TASWM-349) — настройка SWARM_EXECUTOR, один резолв на весь демон.
|
|
17802
18447
|
executor: EXECUTOR,
|
|
17803
18448
|
droneIsolation: DRONE_ISOLATION,
|
|
@@ -17852,6 +18497,8 @@ async function runDaemon(opts) {
|
|
|
17852
18497
|
}
|
|
17853
18498
|
let onHostCommands = () => {
|
|
17854
18499
|
};
|
|
18500
|
+
const tickIntervalMs = opts.intervalMs ?? 4e3;
|
|
18501
|
+
const tickHealth = initialTickHealth(Date.now());
|
|
17855
18502
|
let heartbeat;
|
|
17856
18503
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient && SWARM_PROD_HEARTBEAT_MS > 0) {
|
|
17857
18504
|
const client = db;
|
|
@@ -17865,6 +18512,7 @@ async function runDaemon(opts) {
|
|
|
17865
18512
|
lastOwnCapacity,
|
|
17866
18513
|
hostResourcesForProd(),
|
|
17867
18514
|
{ active: stormActive, reason: stormStartedAt ? `\u043D\u043E\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0441 ${stormStartedAt} UTC` : null },
|
|
18515
|
+
worktreesDir,
|
|
17868
18516
|
// TASWM-393: ВХОД в Taskless — та же природа, что пауза и шторм: состояние
|
|
17869
18517
|
// машины, о котором знает только она. Пульс идёт и без входа, поэтому «на
|
|
17870
18518
|
// связи» без этого поля читалось как «возьмёт работу» (12.08 — шесть прогонов
|
|
@@ -17874,7 +18522,12 @@ async function runDaemon(opts) {
|
|
|
17874
18522
|
// отвергают», а 12.08 входа не делали вовсе, и по отказу машина числилась бы
|
|
17875
18523
|
// вошедшей. Адрес — WEB_PORT (он же SWARM_WEB_PORT из окружения, на нём и
|
|
17876
18524
|
// поднимается морда): зашитый 7420 отправил бы человека туда, где ничего нет.
|
|
17877
|
-
{ signedIn: signedInToTaskless(), webUrl: `http://127.0.0.1:${WEB_PORT}` }
|
|
18525
|
+
{ signedIn: signedInToTaskless(), webUrl: `http://127.0.0.1:${WEB_PORT}` },
|
|
18526
|
+
// TASWM-452: и ПРИЗНАК ЖИВОГО ТИКА — номер последнего завершённого прохода
|
|
18527
|
+
// реконсайла и его возраст. Возраст, а не момент: часы машины и прода
|
|
18528
|
+
// расходятся (ноутбук после сна — на минуты), а сверяет признак прод со своим
|
|
18529
|
+
// настенным временем. Не «демон запущен», а «цикл прокрутился».
|
|
18530
|
+
tickPulse(tickHealth, Date.now(), tickIntervalMs)
|
|
17878
18531
|
);
|
|
17879
18532
|
if (reply.maxDrones !== void 0) operatorMaxDrones = reply.maxDrones;
|
|
17880
18533
|
if (reply.commands.length) onHostCommands(reply.commands);
|
|
@@ -17883,7 +18536,13 @@ async function runDaemon(opts) {
|
|
|
17883
18536
|
{
|
|
17884
18537
|
runLogsDir: RUN_LOGS_DIR,
|
|
17885
18538
|
daemonLogPath: DAEMON_LOG_PATH,
|
|
17886
|
-
|
|
18539
|
+
// TASWM-419: каталог лога адресуется стабильными ключами прогона, а не его
|
|
18540
|
+
// локальным номером — номера переиспользуются между жизнями зеркала, и
|
|
18541
|
+
// оператору приезжал хвост чужого прогона.
|
|
18542
|
+
runLogKeys: (prodRunId) => {
|
|
18543
|
+
const local = client.localRunIdForProd(prodRunId);
|
|
18544
|
+
return local == null ? [] : db.runLogKeys(local);
|
|
18545
|
+
},
|
|
17887
18546
|
deliver: (requestId, payload) => client.deliverLog(requestId, payload)
|
|
17888
18547
|
},
|
|
17889
18548
|
reply.logRequests
|
|
@@ -17941,6 +18600,7 @@ async function runDaemon(opts) {
|
|
|
17941
18600
|
questionUnansweredMs: SWARM_ANOMALY_QUESTION_UNANSWERED_MS,
|
|
17942
18601
|
outboxStuckMs: SWARM_ANOMALY_OUTBOX_STUCK_MS,
|
|
17943
18602
|
resourceGateStallMs: SWARM_ANOMALY_RESOURCE_GATE_STALL_MS,
|
|
18603
|
+
depMissingMs: SWARM_ANOMALY_DEP_MISSING_MS,
|
|
17944
18604
|
runDownWindowMs: SWARM_ANOMALY_RUN_DOWN_WINDOW_MS
|
|
17945
18605
|
},
|
|
17946
18606
|
autoReviewEnabled: SWARM_AUTO_REVIEW,
|
|
@@ -17992,8 +18652,13 @@ async function runDaemon(opts) {
|
|
|
17992
18652
|
let exclusiveProject;
|
|
17993
18653
|
let projectDroneCap;
|
|
17994
18654
|
let autoReviewProject;
|
|
18655
|
+
let pendingRepoRules;
|
|
17995
18656
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
|
|
17996
18657
|
const client = db;
|
|
18658
|
+
pendingRepoRules = async (repo, taskSlug) => {
|
|
18659
|
+
const repoFullName = await remoteRepoFullName(repo).catch(() => null);
|
|
18660
|
+
return repoFullName ? client.repoPendingRules(repoFullName, taskSlug) : [];
|
|
18661
|
+
};
|
|
17997
18662
|
verifyModeReader = makeVerifyModeReader({
|
|
17998
18663
|
fetch: async (repo) => {
|
|
17999
18664
|
const repoFullName = await remoteRepoFullName(repo).catch(() => null);
|
|
@@ -18065,6 +18730,9 @@ async function runDaemon(opts) {
|
|
|
18065
18730
|
// иначе ревьюер судит проект с verifyMode=local по правилам ci и блокирует PR
|
|
18066
18731
|
// за отсутствие CI, которого у проекта нет и не предполагается.
|
|
18067
18732
|
...verifyModeReader ? { verifyMode: verifyModeReader } : {},
|
|
18733
|
+
// TASWM-431: правила о репозитории, принесённые дроном этого прогона, — ревьюер
|
|
18734
|
+
// решает их тем же вердиктом (см. buildReviewerPrompt).
|
|
18735
|
+
...pendingRepoRules ? { pendingRepoRules } : {},
|
|
18068
18736
|
// TASWM-283: те же вложения задачи, что у исполнителя — ревьюер без макета
|
|
18069
18737
|
// не может проверить «сделано ли как просили».
|
|
18070
18738
|
...tl?.fetchFile ? {
|
|
@@ -18200,6 +18868,30 @@ async function runDaemon(opts) {
|
|
|
18200
18868
|
taskClosed: (slug, ws) => tl.forWorkspace(ws).getTask(slug).then((t) => t ? isClosingTaskCategory(t.statusCategory) : null)
|
|
18201
18869
|
} : {},
|
|
18202
18870
|
finishTask: (slug, ws) => tlFor(ws)?.finishTask(slug) ?? Promise.resolve(),
|
|
18871
|
+
...reposServed ? { reposServed } : {},
|
|
18872
|
+
// TASWM-422: ОТЛОЖИТЬ задачу, по которой рой трижды не сумел поднять дрона. Механика —
|
|
18873
|
+
// ровно та же, что у return_task дрона (TASWM-412): `claimableTaskWhere` (api) требует
|
|
18874
|
+
// категорию unstarted, и start_task её ломает — задача перестаёт быть claimable, то есть
|
|
18875
|
+
// авто-интейк больше не берёт её раз в минуту. Успех проверяем ФАКТОМ (перечитываем
|
|
18876
|
+
// задачу), а не кодом возврата: клиент глушит отказ сервера внутрь (safeCall), и по
|
|
18877
|
+
// возврату «отложил» было бы неотличимо от «сказал, но не доехало» — то самое молчание,
|
|
18878
|
+
// из-за которого 17.08 круг и крутился.
|
|
18879
|
+
parkTask: async (run, reason) => {
|
|
18880
|
+
const client = tlFor(run.workspace_id);
|
|
18881
|
+
if (!client) return false;
|
|
18882
|
+
await client.addComment(
|
|
18883
|
+
run.task_slug,
|
|
18884
|
+
`${reason}
|
|
18885
|
+
|
|
18886
|
+
\u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u043D\u044F\u0442\u0430 \u0441 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0440\u043E\u044F \u2014 \u043F\u043E\u043A\u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u043D\u0435\u043D\u0430, \u0431\u0440\u0430\u0442\u044C \u0435\u0451 \u0441\u043D\u043E\u0432\u0430 \u0441\u043C\u044B\u0441\u043B\u0430 \u043D\u0435\u0442. \u0420\u0430\u0437\u0431\u0435\u0440\u0438\u0441\u044C \u0438 \u0432\u0435\u0440\u043D\u0438 \u0432 \u0431\u044D\u043A\u043B\u043E\u0433.`,
|
|
18887
|
+
{ swarmKind: "report", swarmDroneName: run.name }
|
|
18888
|
+
).catch(() => {
|
|
18889
|
+
});
|
|
18890
|
+
await client.startTask(run.task_slug).catch(() => {
|
|
18891
|
+
});
|
|
18892
|
+
const after = await client.getTask(run.task_slug).catch(() => null);
|
|
18893
|
+
return after != null && after.statusCategory !== "unstarted";
|
|
18894
|
+
},
|
|
18203
18895
|
// TASWM-272: отчёт/вердикт-компенсация при усыновлении мёртвого дрона — реальный
|
|
18204
18896
|
// TasklessClient.addComment + gh/git хелперы. Без tl (нет токена) ключи не задаём —
|
|
18205
18897
|
// postAdoptionReport/postScrapedVerdictReport становятся no-op, как раньше.
|
|
@@ -18221,9 +18913,12 @@ async function runDaemon(opts) {
|
|
|
18221
18913
|
// TASWM-364: swarmModels — ТОТ ЖЕ снимок, что уехал в launcher: цена токенов считается
|
|
18222
18914
|
// по модели, на которой прогон реально работал.
|
|
18223
18915
|
accountCost: makeCostAccountant({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels }),
|
|
18916
|
+
// TASWM-413: тот же аккаунтант, но поводом служит завершённый ЗАПУСК, а не смерть
|
|
18917
|
+
// прогона в running — иначе сданный через done() прогон не получает цену никогда.
|
|
18918
|
+
sweepCost: makeCostSweeper({ runLogsDir: RUN_LOGS_DIR, db, executor: EXECUTOR, swarmModels: () => lastSwarmModels, isAlive: pidAlive }),
|
|
18224
18919
|
// Классификация выхода прогона (TASWM-229): result-файл того же RUN_LOGS_DIR — «вышел сам»
|
|
18225
18920
|
// (graceful) vs «убит на полпути» (crash). Тот же каталог, что читает accountCost.
|
|
18226
|
-
exitOutcome: makeExitClassifier(RUN_LOGS_DIR, EXECUTOR),
|
|
18921
|
+
exitOutcome: makeExitClassifier(RUN_LOGS_DIR, (runId) => db.runLogKeys(runId), EXECUTOR),
|
|
18227
18922
|
// Живой child-процесс (TASWM-89): долгая Bash-команда (тесты/сборка) — не «завис».
|
|
18228
18923
|
livePpids,
|
|
18229
18924
|
// Погасить дрона (SIGTERM) — reap внешне-остановленных прогонов (kill из дашборда:
|
|
@@ -18370,7 +19065,7 @@ async function runDaemon(opts) {
|
|
|
18370
19065
|
mode,
|
|
18371
19066
|
nudge
|
|
18372
19067
|
),
|
|
18373
|
-
worktreeExists: (p) =>
|
|
19068
|
+
worktreeExists: (p) => existsSync17(p),
|
|
18374
19069
|
hasTranscript: executorHasSession,
|
|
18375
19070
|
// CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
|
|
18376
19071
|
// (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
|
|
@@ -18394,8 +19089,6 @@ async function runDaemon(opts) {
|
|
|
18394
19089
|
// Периодический re-hydrate зеркала (client-режим): ключ есть только когда сконфигурен.
|
|
18395
19090
|
...rehydrate ? { rehydrate } : {},
|
|
18396
19091
|
...droneBridge ? { droneBridge } : {},
|
|
18397
|
-
// Пульс overmind в прод (client-режим, TASWM-129): ключ есть только когда сконфигурен.
|
|
18398
|
-
...heartbeat ? { heartbeat } : {},
|
|
18399
19092
|
// Сверка кода демона с main (TASWM-251): ключ есть только когда entryFile в git-репо.
|
|
18400
19093
|
...daemonVersion ? { daemonVersion } : {},
|
|
18401
19094
|
// Карта репозиториев машины (TASWM-336): ключ есть только в client-режиме с непустым TTL.
|
|
@@ -18735,8 +19428,30 @@ async function runDaemon(opts) {
|
|
|
18735
19428
|
console.error("[overmind] \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u041D\u0415 \u0432\u044B\u0431\u0440\u0430\u043D \u2014 \u043E\u043D \u043D\u0430\u0432\u044F\u0437\u0430\u043D \u043E\u0442\u043A\u0430\u0437\u043E\u043C: \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443, \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox.");
|
|
18736
19429
|
}
|
|
18737
19430
|
console.log(`\u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430: http://127.0.0.1:${web.port} \u2190 \u043E\u0442\u043A\u0440\u043E\u0439 \u0438 \u0432\u043E\u0439\u0434\u0438 \u0447\u0435\u0440\u0435\u0437 Taskless`);
|
|
18738
|
-
const interval =
|
|
19431
|
+
const interval = tickIntervalMs;
|
|
18739
19432
|
writeHeartbeat(HEARTBEAT_PATH, Date.now());
|
|
19433
|
+
if (heartbeat) {
|
|
19434
|
+
const hb = heartbeat;
|
|
19435
|
+
let inFlight = false;
|
|
19436
|
+
setInterval(() => {
|
|
19437
|
+
if (inFlight) return;
|
|
19438
|
+
inFlight = true;
|
|
19439
|
+
void hb.push().catch((e) => console.error("[overmind] \u043F\u0443\u043B\u044C\u0441 \u0432 \u043F\u0440\u043E\u0434 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e)).finally(() => {
|
|
19440
|
+
inFlight = false;
|
|
19441
|
+
});
|
|
19442
|
+
}, hb.intervalMs);
|
|
19443
|
+
}
|
|
19444
|
+
setInterval(() => {
|
|
19445
|
+
if (restart.phase === "restarting") return;
|
|
19446
|
+
const now = Date.now();
|
|
19447
|
+
if (!tickSelfHealDue(tickHealth, now, interval)) return;
|
|
19448
|
+
const age = Math.round(tickAgeMs(tickHealth, now) / 1e3);
|
|
19449
|
+
console.error(
|
|
19450
|
+
`[overmind] \u0442\u0438\u043A \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B: \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0439 \u043F\u0440\u043E\u0445\u043E\u0434 #${tickHealth.seq} \u0431\u044B\u043B ${age}\u0441 \u043D\u0430\u0437\u0430\u0434${tickHealth.lastError ? ` (\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043E\u0448\u0438\u0431\u043A\u0430: ${tickHealth.lastError})` : ""} \u2014 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u044E \u0441\u0435\u0431\u044F`
|
|
19451
|
+
);
|
|
19452
|
+
narrator.narrate(`\u{1FA7A} \u0426\u0438\u043A\u043B \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B\u0430 \u0432\u0441\u0442\u0430\u043B (\u043F\u0440\u043E\u0445\u043E\u0434 #${tickHealth.seq}, ${age}\u0441 \u0431\u0435\u0437 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F) \u2014 \u0434\u0435\u043C\u043E\u043D \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0441\u0435\u0431\u044F \u0441\u0430\u043C.`);
|
|
19453
|
+
performRestart(liveSessionIds(db, pidAlive));
|
|
19454
|
+
}, Math.max(1e3, interval));
|
|
18740
19455
|
if (deps.sweep) {
|
|
18741
19456
|
deps.sweep.lastAt = Date.now();
|
|
18742
19457
|
await deps.sweep.run().catch((e) => console.error("[overmind] \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u044B\u0439 sweep \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e));
|
|
@@ -18745,7 +19460,9 @@ async function runDaemon(opts) {
|
|
|
18745
19460
|
deps.daemonVersion.lastAt = Date.now();
|
|
18746
19461
|
await deps.daemonVersion.run().catch((e) => console.error("[overmind] \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u0430\u044F \u0441\u0432\u0435\u0440\u043A\u0430 \u0432\u0435\u0440\u0441\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C:", e));
|
|
18747
19462
|
}
|
|
19463
|
+
tickHealth.startedAt = Date.now();
|
|
18748
19464
|
for (; ; ) {
|
|
19465
|
+
let tickError;
|
|
18749
19466
|
try {
|
|
18750
19467
|
writeHeartbeat(HEARTBEAT_PATH, Date.now());
|
|
18751
19468
|
await tickOnce(deps);
|
|
@@ -18755,8 +19472,10 @@ async function runDaemon(opts) {
|
|
|
18755
19472
|
else onRestartStillWaiting?.(live.length);
|
|
18756
19473
|
}
|
|
18757
19474
|
} catch (e) {
|
|
18758
|
-
|
|
19475
|
+
tickError = e;
|
|
19476
|
+
console.error(`[overmind] \u0442\u0438\u043A \u0443\u043F\u0430\u043B: ${describeTickError(e)} \u2014 \u0446\u0438\u043A\u043B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0443`, e);
|
|
18759
19477
|
}
|
|
19478
|
+
markTickDone(tickHealth, Date.now(), tickError);
|
|
18760
19479
|
await new Promise((r) => setTimeout(r, interval));
|
|
18761
19480
|
}
|
|
18762
19481
|
}
|