@taskless-app/swarm 0.6.13 → 0.6.15
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 +496 -200
- package/dist/overmind.js +955 -361
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -332,9 +332,22 @@ var init_timeline = __esm({
|
|
|
332
332
|
});
|
|
333
333
|
|
|
334
334
|
// ../swarm-core/src/reviewPhase.ts
|
|
335
|
+
var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
|
|
335
336
|
var init_reviewPhase = __esm({
|
|
336
337
|
"../swarm-core/src/reviewPhase.ts"() {
|
|
337
338
|
"use strict";
|
|
339
|
+
REVIEW_PHASE_TEXT = {
|
|
340
|
+
checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
|
|
341
|
+
awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
|
|
342
|
+
ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
|
|
343
|
+
in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
|
|
344
|
+
changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
|
|
345
|
+
ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
|
|
346
|
+
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"
|
|
347
|
+
};
|
|
348
|
+
PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
|
|
349
|
+
(a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
|
|
350
|
+
);
|
|
338
351
|
}
|
|
339
352
|
});
|
|
340
353
|
|
|
@@ -452,6 +465,40 @@ var init_mergeGate = __esm({
|
|
|
452
465
|
}
|
|
453
466
|
});
|
|
454
467
|
|
|
468
|
+
// ../swarm-core/src/modelPolicy.ts
|
|
469
|
+
function findProvider(id) {
|
|
470
|
+
const key = typeof id === "string" ? id.trim().toLowerCase() : "";
|
|
471
|
+
return MODEL_PROVIDERS.find((p) => p.id === key) ?? null;
|
|
472
|
+
}
|
|
473
|
+
function parseModelRef(ref) {
|
|
474
|
+
const s = typeof ref === "string" ? ref.trim() : "";
|
|
475
|
+
if (!s) return null;
|
|
476
|
+
const slash = s.indexOf("/");
|
|
477
|
+
if (slash < 0) return { providerId: DEFAULT_PROVIDER_ID, model: s };
|
|
478
|
+
const providerId = s.slice(0, slash).trim().toLowerCase();
|
|
479
|
+
const model = s.slice(slash + 1).trim();
|
|
480
|
+
if (!model) return null;
|
|
481
|
+
return { providerId: providerId || DEFAULT_PROVIDER_ID, model };
|
|
482
|
+
}
|
|
483
|
+
function defaultModelFor(role, providerId = null) {
|
|
484
|
+
const provider = findProvider(providerId) ?? findProvider(DEFAULT_PROVIDER_ID);
|
|
485
|
+
return provider.roles[role];
|
|
486
|
+
}
|
|
487
|
+
var MODEL_PROVIDERS, DEFAULT_PROVIDER_ID;
|
|
488
|
+
var init_modelPolicy = __esm({
|
|
489
|
+
"../swarm-core/src/modelPolicy.ts"() {
|
|
490
|
+
"use strict";
|
|
491
|
+
MODEL_PROVIDERS = [
|
|
492
|
+
{
|
|
493
|
+
id: "anthropic",
|
|
494
|
+
label: "Anthropic",
|
|
495
|
+
roles: { executor: "claude-opus-5", review: "claude-sonnet-5" }
|
|
496
|
+
}
|
|
497
|
+
];
|
|
498
|
+
DEFAULT_PROVIDER_ID = MODEL_PROVIDERS[0].id;
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
455
502
|
// ../swarm-core/src/status.ts
|
|
456
503
|
function statusMeta(status) {
|
|
457
504
|
return STATUS_META[status] ?? FALLBACK;
|
|
@@ -500,7 +547,7 @@ var init_status = __esm({
|
|
|
500
547
|
// ../swarm-core/src/deps.ts
|
|
501
548
|
function judgeDep(runs) {
|
|
502
549
|
const real = runs.filter((r) => !r.service);
|
|
503
|
-
if (real.length === 0) return { ...
|
|
550
|
+
if (real.length === 0) return { ...UNKNOWN };
|
|
504
551
|
if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
|
|
505
552
|
if (real.some((r) => r.status === "done")) {
|
|
506
553
|
return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
@@ -514,12 +561,13 @@ function judgeDep(runs) {
|
|
|
514
561
|
stopReason: last.stopReason ?? null
|
|
515
562
|
};
|
|
516
563
|
}
|
|
517
|
-
var PENDING;
|
|
564
|
+
var PENDING, UNKNOWN;
|
|
518
565
|
var init_deps = __esm({
|
|
519
566
|
"../swarm-core/src/deps.ts"() {
|
|
520
567
|
"use strict";
|
|
521
568
|
init_status();
|
|
522
569
|
PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
570
|
+
UNKNOWN = { outcome: "unknown", met: false, status: null, name: null, stopReason: null };
|
|
523
571
|
}
|
|
524
572
|
});
|
|
525
573
|
|
|
@@ -687,6 +735,13 @@ var init_baseBranch = __esm({
|
|
|
687
735
|
});
|
|
688
736
|
|
|
689
737
|
// ../swarm-core/src/hosts.ts
|
|
738
|
+
function hostSignInHint(webUrl) {
|
|
739
|
+
const url = (webUrl ?? "").trim();
|
|
740
|
+
return url ? `\u0420\u043E\u0439 \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D. \u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 ${url} \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0440\u043E\u044F \u0438 \u0432\u043E\u0439\u0434\u0438\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 Taskless` : "\u0420\u043E\u0439 \u043D\u0435 \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u043E\u0432\u0430\u043D. \u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0443 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u0440\u043E\u044F \u0438 \u0432\u043E\u0439\u0434\u0438\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 Taskless";
|
|
741
|
+
}
|
|
742
|
+
function hostNotSignedIn(host) {
|
|
743
|
+
return host.signedIn === false;
|
|
744
|
+
}
|
|
690
745
|
function normalizeHostRef(value) {
|
|
691
746
|
if (value == null) return null;
|
|
692
747
|
const trimmed = value.trim();
|
|
@@ -720,6 +775,10 @@ function groupByHost(items, ref, opts = {}) {
|
|
|
720
775
|
offline: false,
|
|
721
776
|
paused: unknown ? false : info?.paused === true,
|
|
722
777
|
pauseReason: unknown ? null : info?.pauseReason ?? null,
|
|
778
|
+
// Поле ставим, ТОЛЬКО если машина о входе сказала: undefined здесь — «не знаем»,
|
|
779
|
+
// и превращать молчание в отказ значило бы объявить невойденным весь парк машин
|
|
780
|
+
// со старым демоном.
|
|
781
|
+
...unknown || info?.signedIn == null ? {} : { signedIn: info.signedIn },
|
|
723
782
|
runs: []
|
|
724
783
|
};
|
|
725
784
|
byKey.set(key, g);
|
|
@@ -752,6 +811,7 @@ function hostStateText(host, nowMs = Date.now(), staleMs2 = HOST_HEARTBEAT_STALE
|
|
|
752
811
|
const at = Date.parse(host.lastSeenAt);
|
|
753
812
|
if (Number.isNaN(at)) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
|
|
754
813
|
if (!isHostOffline(host.lastSeenAt, nowMs, staleMs2)) {
|
|
814
|
+
if (hostNotSignedIn(host)) return hostSignInHint(host.webUrl);
|
|
755
815
|
if (host.paused) return host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435";
|
|
756
816
|
if (host.storm) return host.stormReason ? `\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 ${host.stormReason}` : "\u0448\u0442\u043E\u0440\u043C\u0438\u0442 \u2014 \u043D\u043E\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442";
|
|
757
817
|
return "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
|
|
@@ -873,6 +933,42 @@ var init_cost = __esm({
|
|
|
873
933
|
}
|
|
874
934
|
});
|
|
875
935
|
|
|
936
|
+
// ../swarm-core/src/modelPricing.ts
|
|
937
|
+
function findModelPricing(model, table = MODEL_PRICING) {
|
|
938
|
+
if (!model) return void 0;
|
|
939
|
+
const exact = table[model];
|
|
940
|
+
if (exact) return exact;
|
|
941
|
+
let bestKey = "";
|
|
942
|
+
for (const key of Object.keys(table)) {
|
|
943
|
+
if (model.startsWith(key) && key.length > bestKey.length) bestKey = key;
|
|
944
|
+
}
|
|
945
|
+
return bestKey ? table[bestKey] : void 0;
|
|
946
|
+
}
|
|
947
|
+
var MODEL_PRICING, CACHE_READ_FACTOR, CACHE_WRITE_5M_FACTOR;
|
|
948
|
+
var init_modelPricing = __esm({
|
|
949
|
+
"../swarm-core/src/modelPricing.ts"() {
|
|
950
|
+
"use strict";
|
|
951
|
+
MODEL_PRICING = {
|
|
952
|
+
// Sonnet 5 — 2/10. Цена ПОСТОЯННАЯ: 18.08.2026 владелец подтвердил, что вводной
|
|
953
|
+
// она быть перестала. Даты годности у тарифа тут больше нет и заводить её заново не
|
|
954
|
+
// нужно: тариф со сроком означает, что в календарный день расчёт молча меняет цифру,
|
|
955
|
+
// и «почему прогон вчера стоил иначе» приходится выяснять по коду, а не по таблице.
|
|
956
|
+
// Меняется цена — меняется строка, одним видимым коммитом.
|
|
957
|
+
"claude-sonnet-5": { inputPerMTok: 2, outputPerMTok: 10 },
|
|
958
|
+
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
959
|
+
"claude-fable-5": { inputPerMTok: 10, outputPerMTok: 50 },
|
|
960
|
+
// Прогоны и вызовы на 4.x остаются в истории — строки держим ради их стоимости.
|
|
961
|
+
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
962
|
+
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
963
|
+
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
964
|
+
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15 },
|
|
965
|
+
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
966
|
+
};
|
|
967
|
+
CACHE_READ_FACTOR = 0.1;
|
|
968
|
+
CACHE_WRITE_5M_FACTOR = 1.25;
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
|
|
876
972
|
// ../swarm-core/src/runGuards.ts
|
|
877
973
|
function remainingRunBudgetUsd(maxCostUsd, spentUsd) {
|
|
878
974
|
if (!(maxCostUsd > 0)) return null;
|
|
@@ -1316,6 +1412,7 @@ var init_src = __esm({
|
|
|
1316
1412
|
init_reviewPhase();
|
|
1317
1413
|
init_mergeGate();
|
|
1318
1414
|
init_verifyVerdict();
|
|
1415
|
+
init_modelPolicy();
|
|
1319
1416
|
init_status();
|
|
1320
1417
|
init_deps();
|
|
1321
1418
|
init_time();
|
|
@@ -1331,6 +1428,7 @@ var init_src = __esm({
|
|
|
1331
1428
|
init_hostCommands();
|
|
1332
1429
|
init_relief();
|
|
1333
1430
|
init_cost();
|
|
1431
|
+
init_modelPricing();
|
|
1334
1432
|
init_runGuards();
|
|
1335
1433
|
init_codeZones();
|
|
1336
1434
|
init_effort();
|
|
@@ -1428,18 +1526,12 @@ var init_swarmFiles = __esm({
|
|
|
1428
1526
|
});
|
|
1429
1527
|
|
|
1430
1528
|
// src/pricing.ts
|
|
1431
|
-
function
|
|
1432
|
-
const
|
|
1433
|
-
if (!intro) return pricing;
|
|
1434
|
-
const day = at.toISOString().slice(0, 10);
|
|
1435
|
-
return day <= intro.until ? { inputPerMTok: intro.inputPerMTok, outputPerMTok: intro.outputPerMTok } : pricing;
|
|
1436
|
-
}
|
|
1437
|
-
function costFromUsage(usage2, model, table = MODEL_PRICING, at = /* @__PURE__ */ new Date()) {
|
|
1438
|
-
const entry = model ? table[model] : void 0;
|
|
1529
|
+
function costFromUsage(usage2, model, table = MODEL_PRICING) {
|
|
1530
|
+
const entry = findModelPricing(model, table);
|
|
1439
1531
|
if (!entry) {
|
|
1440
1532
|
return { costUsd: 0, warning: `\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C \u0434\u043B\u044F \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0430: "${model ?? ""}" \u2014 cost \u043D\u0435 \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u043D` };
|
|
1441
1533
|
}
|
|
1442
|
-
const pricing =
|
|
1534
|
+
const pricing = entry;
|
|
1443
1535
|
const input = usage2.input_tokens ?? 0;
|
|
1444
1536
|
const output = usage2.output_tokens ?? 0;
|
|
1445
1537
|
const cacheWrite = usage2.cache_creation_input_tokens ?? 0;
|
|
@@ -1447,24 +1539,10 @@ function costFromUsage(usage2, model, table = MODEL_PRICING, at = /* @__PURE__ *
|
|
|
1447
1539
|
const costUsd = (input * pricing.inputPerMTok + output * pricing.outputPerMTok + cacheWrite * pricing.inputPerMTok * CACHE_WRITE_5M_FACTOR + cacheRead * pricing.inputPerMTok * CACHE_READ_FACTOR) / 1e6;
|
|
1448
1540
|
return { costUsd };
|
|
1449
1541
|
}
|
|
1450
|
-
var MODEL_PRICING, CACHE_READ_FACTOR, CACHE_WRITE_5M_FACTOR;
|
|
1451
1542
|
var init_pricing = __esm({
|
|
1452
1543
|
"src/pricing.ts"() {
|
|
1453
1544
|
"use strict";
|
|
1454
|
-
|
|
1455
|
-
// Sonnet 5: вводные 2/10 по 31.08.2026 включительно, дальше базовые 3/15.
|
|
1456
|
-
"claude-sonnet-5": {
|
|
1457
|
-
inputPerMTok: 3,
|
|
1458
|
-
outputPerMTok: 15,
|
|
1459
|
-
intro: { inputPerMTok: 2, outputPerMTok: 10, until: "2026-08-31" }
|
|
1460
|
-
},
|
|
1461
|
-
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1462
|
-
// Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
|
|
1463
|
-
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1464
|
-
"claude-haiku-4-5": { inputPerMTok: 1, outputPerMTok: 5 }
|
|
1465
|
-
};
|
|
1466
|
-
CACHE_READ_FACTOR = 0.1;
|
|
1467
|
-
CACHE_WRITE_5M_FACTOR = 1.25;
|
|
1545
|
+
init_src();
|
|
1468
1546
|
}
|
|
1469
1547
|
});
|
|
1470
1548
|
|
|
@@ -1875,7 +1953,10 @@ function normalizeRepo(repo) {
|
|
|
1875
1953
|
}
|
|
1876
1954
|
}
|
|
1877
1955
|
function droneWorktreePath(worktreesDir, repo, slug) {
|
|
1878
|
-
return join4(worktreesDir,
|
|
1956
|
+
return join4(droneWorktreeRoot(worktreesDir, repo), slug);
|
|
1957
|
+
}
|
|
1958
|
+
function droneWorktreeRoot(worktreesDir, repo) {
|
|
1959
|
+
return join4(worktreesDir, basename(normalizeRepo(repo)));
|
|
1879
1960
|
}
|
|
1880
1961
|
function resolveEffectiveWorktreesDir(deps) {
|
|
1881
1962
|
if (deps.envOverride) return deps.envOverride;
|
|
@@ -1901,7 +1982,7 @@ function tokenFromFile() {
|
|
|
1901
1982
|
return "";
|
|
1902
1983
|
}
|
|
1903
1984
|
}
|
|
1904
|
-
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;
|
|
1985
|
+
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;
|
|
1905
1986
|
var init_config = __esm({
|
|
1906
1987
|
"src/config.ts"() {
|
|
1907
1988
|
"use strict";
|
|
@@ -1963,6 +2044,7 @@ var init_config = __esm({
|
|
|
1963
2044
|
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
1964
2045
|
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join4(SWARM_HOME, "repo-map.json");
|
|
1965
2046
|
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
2047
|
+
SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
|
|
1966
2048
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
1967
2049
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
1968
2050
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
@@ -1989,6 +2071,7 @@ var init_config = __esm({
|
|
|
1989
2071
|
SWARM_ANOMALY_QUESTION_UNANSWERED_MS = envNum("SWARM_ANOMALY_QUESTION_UNANSWERED_MS", 20 * 6e4);
|
|
1990
2072
|
SWARM_ANOMALY_OUTBOX_STUCK_MS = envNum("SWARM_ANOMALY_OUTBOX_STUCK_MS", 10 * 6e4);
|
|
1991
2073
|
SWARM_ANOMALY_RESOURCE_GATE_STALL_MS = envNum("SWARM_ANOMALY_RESOURCE_GATE_STALL_MS", 5 * 6e4);
|
|
2074
|
+
SWARM_ANOMALY_DEP_MISSING_MS = envNum("SWARM_ANOMALY_DEP_MISSING_MS", 2 * 6e4);
|
|
1992
2075
|
SWARM_WATCHDOG_STALL_MS = envNum("SWARM_WATCHDOG_STALL_MS", 15 * 6e4);
|
|
1993
2076
|
}
|
|
1994
2077
|
});
|
|
@@ -2103,7 +2186,7 @@ var init_package = __esm({
|
|
|
2103
2186
|
"package.json"() {
|
|
2104
2187
|
package_default = {
|
|
2105
2188
|
name: "@taskless-app/swarm",
|
|
2106
|
-
version: "0.6.
|
|
2189
|
+
version: "0.6.15",
|
|
2107
2190
|
type: "module",
|
|
2108
2191
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2109
2192
|
keywords: [
|
|
@@ -2153,7 +2236,7 @@ var init_package = __esm({
|
|
|
2153
2236
|
execa: "^10.0.0",
|
|
2154
2237
|
zod: "3.25.76"
|
|
2155
2238
|
},
|
|
2156
|
-
gitHead: "
|
|
2239
|
+
gitHead: "5bc8e0c067de58fd72b0eb434e80ad29c7a00223"
|
|
2157
2240
|
};
|
|
2158
2241
|
}
|
|
2159
2242
|
});
|
|
@@ -2171,7 +2254,7 @@ var init_version = __esm({
|
|
|
2171
2254
|
|
|
2172
2255
|
// src/droneChannel.ts
|
|
2173
2256
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
2174
|
-
import { chmodSync, mkdirSync as
|
|
2257
|
+
import { chmodSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2175
2258
|
import { dirname as dirname2 } from "path";
|
|
2176
2259
|
import { serve } from "@hono/node-server";
|
|
2177
2260
|
function loadChannelSecret(path) {
|
|
@@ -2181,7 +2264,7 @@ function loadChannelSecret(path) {
|
|
|
2181
2264
|
} catch {
|
|
2182
2265
|
}
|
|
2183
2266
|
const secret = randomBytes(32).toString("hex");
|
|
2184
|
-
|
|
2267
|
+
mkdirSync6(dirname2(path), { recursive: true });
|
|
2185
2268
|
writeFileSync5(path, secret, { mode: 384 });
|
|
2186
2269
|
try {
|
|
2187
2270
|
chmodSync(path, 384);
|
|
@@ -2223,7 +2306,7 @@ var init_droneChannel = __esm({
|
|
|
2223
2306
|
|
|
2224
2307
|
// src/agentToken.ts
|
|
2225
2308
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
2226
|
-
import { join as
|
|
2309
|
+
import { join as join9 } from "path";
|
|
2227
2310
|
function loadAgentToken() {
|
|
2228
2311
|
try {
|
|
2229
2312
|
const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
|
|
@@ -2304,7 +2387,7 @@ var init_agentToken = __esm({
|
|
|
2304
2387
|
"src/agentToken.ts"() {
|
|
2305
2388
|
"use strict";
|
|
2306
2389
|
init_config();
|
|
2307
|
-
STORE_PATH =
|
|
2390
|
+
STORE_PATH = join9(SWARM_HOME, "agent.json");
|
|
2308
2391
|
base = (api) => api.replace(/\/$/, "");
|
|
2309
2392
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2310
2393
|
}
|
|
@@ -2555,13 +2638,13 @@ var init_daemonLink = __esm({
|
|
|
2555
2638
|
});
|
|
2556
2639
|
|
|
2557
2640
|
// src/pauseState.ts
|
|
2558
|
-
import { existsSync as
|
|
2559
|
-
import { join as
|
|
2641
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
2642
|
+
import { join as join17 } from "path";
|
|
2560
2643
|
function pauseStatePath(home) {
|
|
2561
|
-
return
|
|
2644
|
+
return join17(home, "pause.json");
|
|
2562
2645
|
}
|
|
2563
2646
|
function loadPauseState(home, io = {}) {
|
|
2564
|
-
const exists = io.exists ??
|
|
2647
|
+
const exists = io.exists ?? existsSync10;
|
|
2565
2648
|
const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
|
|
2566
2649
|
const path = pauseStatePath(home);
|
|
2567
2650
|
if (!exists(path)) return ACTIVE;
|
|
@@ -2581,13 +2664,13 @@ function loadPauseState(home, io = {}) {
|
|
|
2581
2664
|
function savePauseState(home, state, io = {}) {
|
|
2582
2665
|
const path = pauseStatePath(home);
|
|
2583
2666
|
if (!state.paused) {
|
|
2584
|
-
const remove = io.remove ?? ((p) =>
|
|
2667
|
+
const remove = io.remove ?? ((p) => existsSync10(p) && unlinkSync5(p));
|
|
2585
2668
|
remove(path);
|
|
2586
2669
|
return;
|
|
2587
2670
|
}
|
|
2588
2671
|
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
2589
2672
|
writeFileSync9(`${p}.tmp`, data);
|
|
2590
|
-
|
|
2673
|
+
renameSync5(`${p}.tmp`, p);
|
|
2591
2674
|
});
|
|
2592
2675
|
writeFile3(path, JSON.stringify(state, null, 2));
|
|
2593
2676
|
}
|
|
@@ -2623,7 +2706,7 @@ var init_pauseState = __esm({
|
|
|
2623
2706
|
});
|
|
2624
2707
|
|
|
2625
2708
|
// src/heartbeat.ts
|
|
2626
|
-
import { readFileSync as readFileSync11, renameSync as
|
|
2709
|
+
import { readFileSync as readFileSync11, renameSync as renameSync6, writeFileSync as writeFileSync10 } from "fs";
|
|
2627
2710
|
function readHeartbeat(path) {
|
|
2628
2711
|
try {
|
|
2629
2712
|
const ms = Number(readFileSync11(path, "utf8").trim());
|
|
@@ -2644,7 +2727,8 @@ __export(db_exports, {
|
|
|
2644
2727
|
SwarmDb: () => SwarmDb
|
|
2645
2728
|
});
|
|
2646
2729
|
import { createRequire as createRequire2 } from "module";
|
|
2647
|
-
import {
|
|
2730
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2731
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2648
2732
|
import { resolve as resolve2 } from "path";
|
|
2649
2733
|
function rowToQuestion(row) {
|
|
2650
2734
|
return {
|
|
@@ -2677,6 +2761,7 @@ function rowToRun(row) {
|
|
|
2677
2761
|
area: row.area ?? null,
|
|
2678
2762
|
depends_on: row.depends_on ? JSON.parse(row.depends_on) : [],
|
|
2679
2763
|
model: row.model ?? null,
|
|
2764
|
+
review_model: row.review_model ?? null,
|
|
2680
2765
|
// TASWM-395: уровень усилия — через toEffortLevel, а не сырой строкой: в зеркале может
|
|
2681
2766
|
// лежать значение, записанное демоном другой версии (словарь уровней задаёт CLI), и
|
|
2682
2767
|
// непонятное здесь означает «уровень не задан», а не сломанный тип.
|
|
@@ -2754,6 +2839,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
2754
2839
|
area TEXT,
|
|
2755
2840
|
depends_on TEXT,
|
|
2756
2841
|
model TEXT,
|
|
2842
|
+
review_model TEXT,
|
|
2757
2843
|
effort TEXT,
|
|
2758
2844
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
2759
2845
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
@@ -2804,6 +2890,12 @@ CREATE INDEX IF NOT EXISTS idx_runs_area ON runs(area);
|
|
|
2804
2890
|
CREATE INDEX IF NOT EXISTS idx_questions_run ON questions(run_id);
|
|
2805
2891
|
CREATE INDEX IF NOT EXISTS idx_questions_status ON questions(status);
|
|
2806
2892
|
CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id);
|
|
2893
|
+
-- \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
|
|
2894
|
+
-- (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.
|
|
2895
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
2896
|
+
key TEXT PRIMARY KEY,
|
|
2897
|
+
value TEXT NOT NULL
|
|
2898
|
+
);
|
|
2807
2899
|
-- \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)
|
|
2808
2900
|
CREATE TABLE IF NOT EXISTS imports (
|
|
2809
2901
|
source TEXT PRIMARY KEY,
|
|
@@ -2816,6 +2908,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2816
2908
|
defaultRepo;
|
|
2817
2909
|
databaseCtor;
|
|
2818
2910
|
// для importDb (второй Database, source)
|
|
2911
|
+
/** Идентификатор этой жизни файла базы (TASWM-419) — им неймспейсятся каталоги run-log'ов. */
|
|
2912
|
+
instanceId;
|
|
2819
2913
|
constructor(path = "swarm.db", defaultRepo = "") {
|
|
2820
2914
|
this.defaultRepo = normalizeRepo(defaultRepo);
|
|
2821
2915
|
this.databaseCtor = loadBetterSqlite3();
|
|
@@ -2867,7 +2961,11 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2867
2961
|
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
2868
2962
|
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
2869
2963
|
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
2870
|
-
"ALTER TABLE runs ADD COLUMN effort TEXT"
|
|
2964
|
+
"ALTER TABLE runs ADD COLUMN effort TEXT",
|
|
2965
|
+
// TASWM-451: чем проверять работу этого прогона — модель ревью, приехавшая с задачи.
|
|
2966
|
+
// Живёт на прогоне ИСПОЛНИТЕЛЯ: ревьюер и верификатор заводятся локально и читают её
|
|
2967
|
+
// оттуда, а не ходят за задачей в прод в момент спавна.
|
|
2968
|
+
"ALTER TABLE runs ADD COLUMN review_model TEXT"
|
|
2871
2969
|
]) {
|
|
2872
2970
|
try {
|
|
2873
2971
|
this.db.exec(alter);
|
|
@@ -2879,6 +2977,22 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2879
2977
|
if (this.defaultRepo) {
|
|
2880
2978
|
this.db.prepare("UPDATE runs SET repo = ? WHERE repo IS NULL").run(this.defaultRepo);
|
|
2881
2979
|
}
|
|
2980
|
+
this.instanceId = this.ensureInstanceId();
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Идентификатор ЭТОЙ жизни файла базы (TASWM-419). Заводится при первом открытии и живёт
|
|
2984
|
+
* в самой базе, поэтому переживает рестарт демона, но НЕ переживает пересоздание файла:
|
|
2985
|
+
* снесли базу — новый id, и каталоги run-log'ов нового прогона #1 не подселяются к файлам
|
|
2986
|
+
* прогона #1 из прошлой жизни. Именно это и было корнем «лог не того прогона»: номер там
|
|
2987
|
+
* был вся информация об адресе.
|
|
2988
|
+
*/
|
|
2989
|
+
ensureInstanceId() {
|
|
2990
|
+
const read = () => this.db.prepare("SELECT value FROM meta WHERE key = 'instance_id'").get()?.value;
|
|
2991
|
+
const existing = read();
|
|
2992
|
+
if (existing) return existing;
|
|
2993
|
+
const fresh = randomUUID3().replace(/-/g, "").slice(0, 12);
|
|
2994
|
+
this.db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('instance_id', ?)").run(fresh);
|
|
2995
|
+
return read() ?? fresh;
|
|
2882
2996
|
}
|
|
2883
2997
|
close() {
|
|
2884
2998
|
this.db.close();
|
|
@@ -2887,7 +3001,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2887
3001
|
createRun(input) {
|
|
2888
3002
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
2889
3003
|
const res = this.db.prepare(
|
|
2890
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
3004
|
+
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, review_model, effort, decision, resolves_run_id, reviews_run_id, verifies_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2891
3005
|
).run(
|
|
2892
3006
|
input.task_slug,
|
|
2893
3007
|
name,
|
|
@@ -2900,6 +3014,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2900
3014
|
input.area ?? null,
|
|
2901
3015
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
2902
3016
|
input.model ?? null,
|
|
3017
|
+
input.review_model ?? null,
|
|
2903
3018
|
input.effort ?? null,
|
|
2904
3019
|
// усилие рассуждения (TASWM-395); null → разрешит спавн
|
|
2905
3020
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
@@ -2972,6 +3087,15 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2972
3087
|
const m = /^local:(\d+)$/.exec(key);
|
|
2973
3088
|
return m ? this.getRun(Number(m[1])) : null;
|
|
2974
3089
|
}
|
|
3090
|
+
/**
|
|
3091
|
+
* TASWM-419: адрес каталога run-log'ов — номер, НЕЙМСПЕЙСНУТЫЙ идентификатором файла базы
|
|
3092
|
+
* (`local:<instance>:<id>`). Тот же номер в пересозданной базе даёт другой instance, то
|
|
3093
|
+
* есть другой каталог: подселиться к файлам чужого прогона с тем же номером больше нечем.
|
|
3094
|
+
* Псевдонимов у sqlite-прогона нет — ключ не меняется, пока жив файл базы.
|
|
3095
|
+
*/
|
|
3096
|
+
runLogKeys(id) {
|
|
3097
|
+
return this.getRun(id) ? [`local:${this.instanceId}:${id}`] : [];
|
|
3098
|
+
}
|
|
2975
3099
|
listRuns(status) {
|
|
2976
3100
|
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();
|
|
2977
3101
|
return rows.map(rowToRun);
|
|
@@ -3212,7 +3336,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3212
3336
|
const repoN = normalizeRepo(repo);
|
|
3213
3337
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
3214
3338
|
if (already) return { imported: false, runs: already.run_count };
|
|
3215
|
-
if (!
|
|
3339
|
+
if (!existsSync11(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
3216
3340
|
const src = new this.databaseCtor(abs);
|
|
3217
3341
|
try {
|
|
3218
3342
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -3539,8 +3663,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3539
3663
|
});
|
|
3540
3664
|
|
|
3541
3665
|
// src/repoScan.ts
|
|
3542
|
-
import { readdirSync as
|
|
3543
|
-
import { join as
|
|
3666
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
3667
|
+
import { join as join18 } from "path";
|
|
3544
3668
|
var init_repoScan = __esm({
|
|
3545
3669
|
"src/repoScan.ts"() {
|
|
3546
3670
|
"use strict";
|
|
@@ -3550,8 +3674,8 @@ var init_repoScan = __esm({
|
|
|
3550
3674
|
|
|
3551
3675
|
// src/repoDiscovery.ts
|
|
3552
3676
|
import { homedir as homedir4 } from "os";
|
|
3553
|
-
import { readFileSync as readFileSync13, renameSync as
|
|
3554
|
-
import { isAbsolute as isAbsolute3, join as
|
|
3677
|
+
import { readFileSync as readFileSync13, renameSync as renameSync7, writeFileSync as writeFileSync11 } from "fs";
|
|
3678
|
+
import { isAbsolute as isAbsolute3, join as join19, resolve as resolve3 } from "path";
|
|
3555
3679
|
function repoMapLines(rows) {
|
|
3556
3680
|
if (!rows?.length) return [];
|
|
3557
3681
|
const missing = rows.filter((r) => !r.path).length;
|
|
@@ -3564,18 +3688,24 @@ function repoMapLines(rows) {
|
|
|
3564
3688
|
});
|
|
3565
3689
|
return [head, ...body];
|
|
3566
3690
|
}
|
|
3567
|
-
function
|
|
3691
|
+
function loadRepoMapCache(path) {
|
|
3568
3692
|
try {
|
|
3569
3693
|
const raw = JSON.parse(readFileSync13(path, "utf8"));
|
|
3570
|
-
if (!Array.isArray(raw?.entries)) return [];
|
|
3571
|
-
return
|
|
3572
|
-
|
|
3573
|
-
|
|
3694
|
+
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
3695
|
+
return {
|
|
3696
|
+
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
3697
|
+
entries: raw.entries.filter(
|
|
3698
|
+
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
3699
|
+
)
|
|
3700
|
+
};
|
|
3574
3701
|
} catch {
|
|
3575
|
-
return [];
|
|
3702
|
+
return { scannedAt: null, entries: [] };
|
|
3576
3703
|
}
|
|
3577
3704
|
}
|
|
3578
|
-
|
|
3705
|
+
function readRepoMapCache(path) {
|
|
3706
|
+
return loadRepoMapCache(path).entries;
|
|
3707
|
+
}
|
|
3708
|
+
var REPO_SCAN_TTL_MS, UNKNOWN_LIST_SCAN_TTL_MS, SOURCE_TEXT;
|
|
3579
3709
|
var init_repoDiscovery = __esm({
|
|
3580
3710
|
"src/repoDiscovery.ts"() {
|
|
3581
3711
|
"use strict";
|
|
@@ -3583,6 +3713,8 @@ var init_repoDiscovery = __esm({
|
|
|
3583
3713
|
init_config();
|
|
3584
3714
|
init_linkFailure();
|
|
3585
3715
|
init_repoScan();
|
|
3716
|
+
REPO_SCAN_TTL_MS = 24 * 60 * 6e4;
|
|
3717
|
+
UNKNOWN_LIST_SCAN_TTL_MS = 60 * 6e4;
|
|
3586
3718
|
SOURCE_TEXT = {
|
|
3587
3719
|
configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
3588
3720
|
cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
|
|
@@ -3927,10 +4059,10 @@ __export(statusCli_exports, {
|
|
|
3927
4059
|
probeLink: () => probeLink,
|
|
3928
4060
|
runStatusCli: () => runStatusCli
|
|
3929
4061
|
});
|
|
3930
|
-
import { existsSync as
|
|
4062
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
3931
4063
|
function outboxPending(path) {
|
|
3932
4064
|
try {
|
|
3933
|
-
if (!
|
|
4065
|
+
if (!existsSync12(path)) return 0;
|
|
3934
4066
|
return readFileSync14(path, "utf8").split("\n").filter((l) => l.trim()).length;
|
|
3935
4067
|
} catch {
|
|
3936
4068
|
return null;
|
|
@@ -4069,8 +4201,8 @@ var init_statusCli = __esm({
|
|
|
4069
4201
|
});
|
|
4070
4202
|
|
|
4071
4203
|
// src/platform.ts
|
|
4072
|
-
import { existsSync as
|
|
4073
|
-
import { delimiter, join as
|
|
4204
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
4205
|
+
import { delimiter, join as join20 } from "path";
|
|
4074
4206
|
function safeReadText(path) {
|
|
4075
4207
|
try {
|
|
4076
4208
|
return readFileSync15(path, "utf8");
|
|
@@ -4079,13 +4211,13 @@ function safeReadText(path) {
|
|
|
4079
4211
|
}
|
|
4080
4212
|
}
|
|
4081
4213
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
4082
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
4214
|
+
if (name.includes("/") || name.includes("\\")) return existsSync13(name) ? name : null;
|
|
4083
4215
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
4084
4216
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
4085
4217
|
for (const dir of dirs) {
|
|
4086
4218
|
for (const ext of exts) {
|
|
4087
|
-
const candidate =
|
|
4088
|
-
if (
|
|
4219
|
+
const candidate = join20(dir, name + ext);
|
|
4220
|
+
if (existsSync13(candidate)) return candidate;
|
|
4089
4221
|
}
|
|
4090
4222
|
}
|
|
4091
4223
|
return null;
|
|
@@ -4188,13 +4320,13 @@ var init_platform = __esm({
|
|
|
4188
4320
|
});
|
|
4189
4321
|
|
|
4190
4322
|
// src/envGate.ts
|
|
4191
|
-
import { existsSync as
|
|
4192
|
-
import { join as
|
|
4323
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4324
|
+
import { join as join21 } from "path";
|
|
4193
4325
|
import { createConnection } from "net";
|
|
4194
4326
|
import { execa as execa3 } from "execa";
|
|
4195
4327
|
function detectRequirements(repo, io = {}) {
|
|
4196
|
-
const exists = io.exists ??
|
|
4197
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
4328
|
+
const exists = io.exists ?? existsSync14;
|
|
4329
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join21(repo, f)));
|
|
4198
4330
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
4199
4331
|
}
|
|
4200
4332
|
async function dockerAlive() {
|
|
@@ -4226,7 +4358,7 @@ var init_envGate = __esm({
|
|
|
4226
4358
|
});
|
|
4227
4359
|
|
|
4228
4360
|
// src/doctor.ts
|
|
4229
|
-
import { isAbsolute as isAbsolute4, join as
|
|
4361
|
+
import { isAbsolute as isAbsolute4, join as join22 } from "path";
|
|
4230
4362
|
function ok(id, title, detail) {
|
|
4231
4363
|
return { id, title, status: "ok", detail, remedies: [], critical: false };
|
|
4232
4364
|
}
|
|
@@ -4433,7 +4565,7 @@ function checkRepos(io, snap) {
|
|
|
4433
4565
|
critical: true
|
|
4434
4566
|
};
|
|
4435
4567
|
}
|
|
4436
|
-
if (env && !io.exists(
|
|
4568
|
+
if (env && !io.exists(join22(env, ".git"))) {
|
|
4437
4569
|
return {
|
|
4438
4570
|
id: "repos",
|
|
4439
4571
|
title,
|
|
@@ -4443,7 +4575,7 @@ function checkRepos(io, snap) {
|
|
|
4443
4575
|
critical: true
|
|
4444
4576
|
};
|
|
4445
4577
|
}
|
|
4446
|
-
const broken = snap.paths.filter((p) => !io.exists(
|
|
4578
|
+
const broken = snap.paths.filter((p) => !io.exists(join22(p, ".git")));
|
|
4447
4579
|
if (broken.length) {
|
|
4448
4580
|
return {
|
|
4449
4581
|
id: "repos",
|
|
@@ -4608,11 +4740,11 @@ var init_doctor = __esm({
|
|
|
4608
4740
|
|
|
4609
4741
|
// src/repoMemory.ts
|
|
4610
4742
|
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
|
|
4611
|
-
import { join as
|
|
4743
|
+
import { join as join23 } from "path";
|
|
4612
4744
|
function loadRememberedRepos(home) {
|
|
4613
4745
|
let raw;
|
|
4614
4746
|
try {
|
|
4615
|
-
raw = readFileSync16(
|
|
4747
|
+
raw = readFileSync16(join23(home, FILE), "utf8");
|
|
4616
4748
|
} catch {
|
|
4617
4749
|
return [];
|
|
4618
4750
|
}
|
|
@@ -4647,7 +4779,7 @@ __export(installCli_exports, {
|
|
|
4647
4779
|
runInstallCli: () => runInstallCli,
|
|
4648
4780
|
shortenError: () => shortenError
|
|
4649
4781
|
});
|
|
4650
|
-
import { existsSync as
|
|
4782
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4651
4783
|
function classifyChromiumError(message) {
|
|
4652
4784
|
if (/playwright-core не установлен|Cannot find (module|package)/i.test(message)) return "no-module";
|
|
4653
4785
|
if (/error while loading shared libraries|cannot open shared object file|missing dependencies|\blib[\w.+-]*\.so[.\d]*\b/i.test(message)) return "no-libs";
|
|
@@ -4702,7 +4834,7 @@ function defaultDoctorIO(packageVersion) {
|
|
|
4702
4834
|
packageVersion,
|
|
4703
4835
|
entry,
|
|
4704
4836
|
hasCommand: (name) => hasCommandInPath(name),
|
|
4705
|
-
exists:
|
|
4837
|
+
exists: existsSync15,
|
|
4706
4838
|
run: (command) => runQuiet(command),
|
|
4707
4839
|
latestVersion: () => latestMemo ??= fetchLatestVersion(),
|
|
4708
4840
|
probeChromium: () => probeChromium(),
|
|
@@ -4911,13 +5043,13 @@ var init_updateCli = __esm({
|
|
|
4911
5043
|
|
|
4912
5044
|
// src/cli.ts
|
|
4913
5045
|
init_cliIo();
|
|
4914
|
-
import { basename as basename4, join as
|
|
4915
|
-
import { existsSync as
|
|
5046
|
+
import { basename as basename4, join as join24 } from "path";
|
|
5047
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17, readdirSync as readdirSync10, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4, rmSync as rmSync2 } from "fs";
|
|
4916
5048
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
4917
5049
|
|
|
4918
5050
|
// src/swarmClient.ts
|
|
4919
5051
|
init_config();
|
|
4920
|
-
import { appendFileSync, existsSync as
|
|
5052
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4921
5053
|
import { dirname } from "path";
|
|
4922
5054
|
import { randomUUID } from "crypto";
|
|
4923
5055
|
|
|
@@ -5124,8 +5256,34 @@ init_names2();
|
|
|
5124
5256
|
init_storm();
|
|
5125
5257
|
|
|
5126
5258
|
// src/logTail.ts
|
|
5127
|
-
import { closeSync, existsSync as
|
|
5259
|
+
import { closeSync, existsSync as existsSync4, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
5260
|
+
import { join as join6 } from "path";
|
|
5261
|
+
|
|
5262
|
+
// src/runLogDir.ts
|
|
5263
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync as renameSync2, statSync as statSync3 } from "fs";
|
|
5128
5264
|
import { join as join5 } from "path";
|
|
5265
|
+
function runLogDirName(runLogKey) {
|
|
5266
|
+
const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
5267
|
+
if (!safe) throw new Error(`\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435 \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0432 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 run-log'\u043E\u0432: ${JSON.stringify(runLogKey)}`);
|
|
5268
|
+
return /^\d+$/.test(safe) ? `k-${safe}` : safe;
|
|
5269
|
+
}
|
|
5270
|
+
function runLogDirPath(runLogsDir, runLogKey) {
|
|
5271
|
+
return join5(runLogsDir, runLogDirName(runLogKey));
|
|
5272
|
+
}
|
|
5273
|
+
function findRunLogDir(runLogsDir, keys) {
|
|
5274
|
+
for (const key of keys) {
|
|
5275
|
+
const dir = runLogDirPath(runLogsDir, key);
|
|
5276
|
+
if (existsSync3(dir)) return dir;
|
|
5277
|
+
}
|
|
5278
|
+
return null;
|
|
5279
|
+
}
|
|
5280
|
+
function runLogDirForWrite(runLogsDir, keys) {
|
|
5281
|
+
const canonical = keys[0];
|
|
5282
|
+
if (!canonical) return null;
|
|
5283
|
+
return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
|
|
5284
|
+
}
|
|
5285
|
+
|
|
5286
|
+
// src/logTail.ts
|
|
5129
5287
|
var TAIL_LINES_FALLBACK = 200;
|
|
5130
5288
|
var TAIL_MAX_BYTES_FALLBACK = 64 * 1024;
|
|
5131
5289
|
|
|
@@ -5212,6 +5370,11 @@ var SwarmClient = class _SwarmClient {
|
|
|
5212
5370
|
// Живёт в памяти: рестарт демона просто переспросит прод.
|
|
5213
5371
|
depsProd = /* @__PURE__ */ new Map();
|
|
5214
5372
|
depsAskedAt = /* @__PURE__ */ new Map();
|
|
5373
|
+
// TASWM-399: ссылка dependsOn → человеко-слаг блокера, как его назвал прод. В колонке
|
|
5374
|
+
// лежит cuid прогона или задачи, а в лог демона, в событие прогона и в аномалию должен
|
|
5375
|
+
// уходить «TASWM-392», а не «cmsqqpphd011y2xtbckkut6bj» — иначе оператору всё равно
|
|
5376
|
+
// нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
|
|
5377
|
+
depSlug = /* @__PURE__ */ new Map();
|
|
5215
5378
|
static DEPS_TTL_MS = 6e4;
|
|
5216
5379
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
5217
5380
|
// ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
|
|
@@ -5271,25 +5434,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
5271
5434
|
this.repoFullNames = registry.map((e) => e.repoFullName);
|
|
5272
5435
|
this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
|
|
5273
5436
|
this.host = opts.host ?? null;
|
|
5274
|
-
this.repoExists = opts.repoExists ??
|
|
5437
|
+
this.repoExists = opts.repoExists ?? existsSync5;
|
|
5275
5438
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
5276
5439
|
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
5277
5440
|
this.outboxPath = opts.outboxPath;
|
|
5278
5441
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
5279
5442
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
5280
|
-
|
|
5443
|
+
mkdirSync5(dirname(this.outboxPath), { recursive: true });
|
|
5281
5444
|
this.recover();
|
|
5282
5445
|
}
|
|
5283
5446
|
// ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
|
|
5284
5447
|
recover() {
|
|
5285
|
-
if (
|
|
5448
|
+
if (existsSync5(this.mapPath)) {
|
|
5286
5449
|
try {
|
|
5287
5450
|
const obj = JSON.parse(readFileSync3(this.mapPath, "utf8"));
|
|
5288
5451
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
5289
5452
|
} catch {
|
|
5290
5453
|
}
|
|
5291
5454
|
}
|
|
5292
|
-
if (
|
|
5455
|
+
if (existsSync5(this.outboxPath)) {
|
|
5293
5456
|
const txt = readFileSync3(this.outboxPath, "utf8");
|
|
5294
5457
|
for (const line of txt.split("\n")) {
|
|
5295
5458
|
const s = line.trim();
|
|
@@ -5310,7 +5473,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5310
5473
|
// eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
|
|
5311
5474
|
// переприсваиваем локально (id событий — только порядок, не корреляция с продом).
|
|
5312
5475
|
replayGlobalEvents() {
|
|
5313
|
-
if (!
|
|
5476
|
+
if (!existsSync5(this.stormPath)) return;
|
|
5314
5477
|
let txt;
|
|
5315
5478
|
try {
|
|
5316
5479
|
txt = readFileSync3(this.stormPath, "utf8");
|
|
@@ -5390,6 +5553,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5390
5553
|
if (patch.status === "running") out.executor = this.executorId;
|
|
5391
5554
|
if ("area" in patch) out.area = patch.area ?? null;
|
|
5392
5555
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
5556
|
+
if ("review_model" in patch) out.reviewModel = patch.review_model ?? null;
|
|
5393
5557
|
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
5394
5558
|
if ("name" in patch) out.droneName = patch.name ?? null;
|
|
5395
5559
|
if ("cost_usd" in patch && patch.cost_usd !== void 0) out.costUsd = patch.cost_usd;
|
|
@@ -5463,6 +5627,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
5463
5627
|
area: input.area ?? null,
|
|
5464
5628
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
5465
5629
|
model: input.model ?? null,
|
|
5630
|
+
// TASWM-451: чем проверять эту работу. У прогона исполнителя приезжает из задачи
|
|
5631
|
+
// (claim в проде), у локально заведённого ревьюера/верификатора — наследством от
|
|
5632
|
+
// проверяемого прогона, которое ставит spawnReviewer/spawnVerifier.
|
|
5633
|
+
review_model: input.review_model ?? null,
|
|
5466
5634
|
// Усилие рассуждения (TASWM-395): пин, если его поставили на спавне; иначе null —
|
|
5467
5635
|
// разрешённый уровень впечатает launcher, когда действительно поднимет дрона.
|
|
5468
5636
|
effort: input.effort ?? null,
|
|
@@ -5521,6 +5689,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5521
5689
|
status: "queued",
|
|
5522
5690
|
area: d.area,
|
|
5523
5691
|
model: d.model,
|
|
5692
|
+
review_model: d.review_model,
|
|
5693
|
+
// TASWM-451: чем проверять — едет рядом с моделью
|
|
5524
5694
|
effort: d.effort,
|
|
5525
5695
|
// усилие рассуждения (TASWM-395), если пинили на спавне
|
|
5526
5696
|
name: d.name,
|
|
@@ -5568,6 +5738,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
5568
5738
|
const viaProd = [...this.runs.values()].find((r) => r.runKey === `p:${prodId}` || this.prodIdByKey.get(r.runKey) === prodId);
|
|
5569
5739
|
return viaProd ? this.mat(viaProd) : null;
|
|
5570
5740
|
}
|
|
5741
|
+
/**
|
|
5742
|
+
* TASWM-419: адреса каталога run-log'ов прогона. КАНОНИЧЕСКИЙ — прод-вида `p:<cuid>`, как
|
|
5743
|
+
* только cuid известен: он один и тот же во всех жизнях демона, а локальный uuid прогона
|
|
5744
|
+
* после рестарта уже не воскресает (гидрированный прогон приезжает под прод-ключом).
|
|
5745
|
+
* Дальше — псевдонимы: uuid, под которым каталог мог завестись в окно между спавном и
|
|
5746
|
+
* первым флашем в прод. По ним каталог ищется, чтобы запуски одного прогона не расползлись
|
|
5747
|
+
* по двум каталогам (иначе cost недосчитал бы часть, а хвост показал бы не тот запуск).
|
|
5748
|
+
* Номер прогона в адресе не участвует вовсе — он и есть источник «лога чужого прогона».
|
|
5749
|
+
*/
|
|
5750
|
+
runLogKeys(id) {
|
|
5751
|
+
const key = this.runs.get(id)?.runKey;
|
|
5752
|
+
if (!key) return [];
|
|
5753
|
+
const prodId = key.startsWith("p:") ? key.slice(2) : this.prodIdByKey.get(key);
|
|
5754
|
+
if (!prodId) return [key];
|
|
5755
|
+
const canonical = `p:${prodId}`;
|
|
5756
|
+
const aliases = [key, ...[...this.prodIdByKey.entries()].filter(([, v]) => v === prodId).map(([k]) => k)];
|
|
5757
|
+
return [canonical, ...new Set(aliases.filter((k) => k !== canonical))];
|
|
5758
|
+
}
|
|
5571
5759
|
listRuns(status) {
|
|
5572
5760
|
const all = [...this.runs.values()].filter((r) => status ? r.status === status : true);
|
|
5573
5761
|
all.sort((a, b) => b.id - a.id);
|
|
@@ -5684,16 +5872,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
5684
5872
|
/**
|
|
5685
5873
|
* TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
|
|
5686
5874
|
*
|
|
5687
|
-
*
|
|
5688
|
-
* (
|
|
5689
|
-
* стоят разного:
|
|
5690
|
-
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
|
|
5691
|
-
* портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
5692
|
-
* •
|
|
5693
|
-
* на соседней машине как раз и может быть
|
|
5694
|
-
*
|
|
5695
|
-
*
|
|
5696
|
-
*
|
|
5875
|
+
* ИСТОЧНИК ПРАВДЫ — ПРОД (TASWM-399), зеркало здесь только быстрый путь. Зеркало —
|
|
5876
|
+
* ЧАСТИЧНАЯ выборка (прогоны этой машины, окно гидрации), и по частичной выборке два
|
|
5877
|
+
* вывода стоят разного:
|
|
5878
|
+
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
|
|
5879
|
+
* не портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
5880
|
+
* • всё остальное — вывод от ОТСУТСТВИЯ, и его принимаем ТОЛЬКО из ответа прода
|
|
5881
|
+
* (refreshDeps): невидимый зеркалу прогон на соседней машине как раз и может быть
|
|
5882
|
+
* живым, а закрытый с прода — давно смерженным.
|
|
5883
|
+
*
|
|
5884
|
+
* ССЫЛКА — НЕ ОБЯЗАТЕЛЬНО СЛАГ. В `dependsOn` лежит чаще всего cuid ПРОГОНА (так пишет
|
|
5885
|
+
* overmind), реже cuid задачи, и только дев-спавн `--after` пишет человеко-слаг. Быстрый
|
|
5886
|
+
* путь по зеркалу сравнивает `task_slug`, то есть срабатывает лишь на третьей форме —
|
|
5887
|
+
* это нормально: остальные две резолвит прод. А вот считать «раз в зеркале не нашёл —
|
|
5888
|
+
* значит не готово» было корнем TASWM-399.
|
|
5889
|
+
*
|
|
5890
|
+
* Прода ещё не спрашивали (первый тик) или он моргнул → `pending`: прогон честно ждёт
|
|
5891
|
+
* следующего окна. Лишняя минута очереди дешевле дрона, поднятого поверх работающего
|
|
5892
|
+
* блокера. `unknown` (блокера НЕ ЗНАЕТ И ПРОД) ставит только ответ прода — местным
|
|
5893
|
+
* незнанием такой вывод не делается.
|
|
5697
5894
|
*/
|
|
5698
5895
|
depVerdicts(depends_on) {
|
|
5699
5896
|
if (!depends_on || depends_on.length === 0) return [];
|
|
@@ -5703,57 +5900,89 @@ var SwarmClient = class _SwarmClient {
|
|
|
5703
5900
|
doneInMirror.add(r.task_slug);
|
|
5704
5901
|
}
|
|
5705
5902
|
}
|
|
5706
|
-
return depends_on.map((
|
|
5707
|
-
if (doneInMirror.has(
|
|
5708
|
-
return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
5903
|
+
return depends_on.map((ref) => {
|
|
5904
|
+
if (doneInMirror.has(ref)) {
|
|
5905
|
+
return { slug: ref, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
5709
5906
|
}
|
|
5710
|
-
const fromProd = this.depsProd.get(
|
|
5711
|
-
if (fromProd)
|
|
5712
|
-
|
|
5907
|
+
const fromProd = this.depsProd.get(ref);
|
|
5908
|
+
if (fromProd) {
|
|
5909
|
+
if (fromProd.outcome === "unknown" && this.knowsDepRef(ref)) {
|
|
5910
|
+
return { slug: this.depSlug.get(ref) ?? ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
5911
|
+
}
|
|
5912
|
+
return { slug: this.depSlug.get(ref) ?? ref, ...fromProd };
|
|
5913
|
+
}
|
|
5914
|
+
return { slug: ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
5713
5915
|
});
|
|
5714
5916
|
}
|
|
5715
5917
|
/**
|
|
5716
|
-
*
|
|
5918
|
+
* Знает ли ЗЕРКАЛО хоть один прогон по ссылке `dependsOn` (TASWM-399). Ссылка бывает
|
|
5919
|
+
* слагом, cuid'ом задачи и cuid'ом прогона — последний сверяем с прод-id, как это
|
|
5920
|
+
* делают runByProdId/refreshRun. Служебные сабраны (ревьюер/резолвер/верификатор) не
|
|
5921
|
+
* в счёт: их существование не означает, что жив сам блокер.
|
|
5922
|
+
*/
|
|
5923
|
+
knowsDepRef(ref) {
|
|
5924
|
+
for (const r of this.runs.values()) {
|
|
5925
|
+
if (r.resolves_run_id != null || r.reviews_run_id != null || r.verifies_run_id != null) continue;
|
|
5926
|
+
if (r.task_slug === ref) return true;
|
|
5927
|
+
if (r.runKey === `p:${ref}` || this.prodIdByKey.get(r.runKey) === ref) return true;
|
|
5928
|
+
}
|
|
5929
|
+
return false;
|
|
5930
|
+
}
|
|
5931
|
+
/**
|
|
5932
|
+
* TASWM-300 → TASWM-399: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
|
|
5717
5933
|
*
|
|
5718
|
-
* Почему зеркала
|
|
5719
|
-
*
|
|
5720
|
-
*
|
|
5721
|
-
* просто
|
|
5722
|
-
*
|
|
5934
|
+
* Почему зеркала мало. В нём лежат только прогоны ЭТОЙ машины (гейт isMine, hydrateRun) и
|
|
5935
|
+
* только те, что попали в окно гидрации (`swarm_list_runs limit`, сортировка по updatedAt
|
|
5936
|
+
* desc — сутки работы роя). Зависимость, закрытая на соседней машине, обнулившаяся при
|
|
5937
|
+
* рестарте демона или просто позавчерашняя, в зеркало не попадала — и `dependsOn` держал
|
|
5938
|
+
* прогон в очереди вечно.
|
|
5939
|
+
*
|
|
5940
|
+
* ЧЕМ СПРАШИВАЕМ И ПОЧЕМУ НЕ `swarm_list_runs` (корень TASWM-399). До этой задачи здесь
|
|
5941
|
+
* шёл `swarm_list_runs { taskId: <ref> }` — то есть ссылка трактовалась как ЗАДАЧА. Но в
|
|
5942
|
+
* `dependsOn` чаще всего лежит cuid ПРОГОНА: `resolveTaskRef` его задачей не признаёт,
|
|
5943
|
+
* ответ приходит `isError`, вердикт не появляется НИКОГДА, и прогон стоит молча при
|
|
5944
|
+
* свободной машине (12–13.08 — пять остановок роя за сутки). `swarm_deps_status` понимает
|
|
5945
|
+
* все три формы ссылки и отвечает по единому правилу роя (`judgeDep` на прод-стороне).
|
|
5723
5946
|
*
|
|
5724
5947
|
* Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
|
|
5725
5948
|
* положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
|
|
5726
|
-
* «исчерпана
|
|
5727
|
-
*
|
|
5728
|
-
*
|
|
5949
|
+
* «исчерпана неуспешно» и «блокера не знает никто») переспрашиваем не чаще DEPS_TTL_MS:
|
|
5950
|
+
* оператор вправе переиграть блокер новым прогоном, а тик идёт раз в ~4 с — без TTL это
|
|
5951
|
+
* был бы поход в прод на каждую ссылку каждый тик.
|
|
5952
|
+
*
|
|
5953
|
+
* Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
|
|
5954
|
+
* бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
|
|
5955
|
+
* дороже.
|
|
5729
5956
|
*/
|
|
5730
|
-
async refreshDeps(
|
|
5731
|
-
|
|
5732
|
-
if (!
|
|
5733
|
-
const asked = this.depsAskedAt.get(
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5957
|
+
async refreshDeps(refs, nowMs = Date.now()) {
|
|
5958
|
+
const ask = [...new Set(refs)].filter((ref) => {
|
|
5959
|
+
if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
|
|
5960
|
+
const asked = this.depsAskedAt.get(ref);
|
|
5961
|
+
return asked === void 0 || nowMs - asked >= _SwarmClient.DEPS_TTL_MS;
|
|
5962
|
+
});
|
|
5963
|
+
if (!ask.length) return;
|
|
5964
|
+
for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
|
|
5965
|
+
let res;
|
|
5966
|
+
try {
|
|
5967
|
+
res = await this.call("swarm_deps_status", { refs: ask });
|
|
5968
|
+
} catch {
|
|
5969
|
+
for (const ref of ask) this.depsAskedAt.delete(ref);
|
|
5970
|
+
return;
|
|
5971
|
+
}
|
|
5972
|
+
if (res?.isError) return;
|
|
5973
|
+
const deps = res.structuredContent?.deps ?? [];
|
|
5974
|
+
for (const d of deps) {
|
|
5975
|
+
const ref = typeof d.ref === "string" ? d.ref : null;
|
|
5976
|
+
if (!ref) continue;
|
|
5977
|
+
const outcome = String(d.outcome ?? "pending");
|
|
5978
|
+
this.depsProd.set(ref, {
|
|
5979
|
+
outcome,
|
|
5980
|
+
met: Boolean(d.met),
|
|
5981
|
+
status: d.status ?? null,
|
|
5982
|
+
name: d.name ?? null,
|
|
5983
|
+
stopReason: d.stopReason ?? null
|
|
5984
|
+
});
|
|
5985
|
+
if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
|
|
5757
5986
|
}
|
|
5758
5987
|
}
|
|
5759
5988
|
/**
|
|
@@ -6072,7 +6301,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6072
6301
|
* транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
|
|
6073
6302
|
*/
|
|
6074
6303
|
reconcileRuntime(opts) {
|
|
6075
|
-
const wtExists = opts.worktreeExists ??
|
|
6304
|
+
const wtExists = opts.worktreeExists ?? existsSync5;
|
|
6076
6305
|
const pidByWorktree = /* @__PURE__ */ new Map();
|
|
6077
6306
|
try {
|
|
6078
6307
|
for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
|
|
@@ -6561,6 +6790,12 @@ var SwarmClient = class _SwarmClient {
|
|
|
6561
6790
|
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
6562
6791
|
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
6563
6792
|
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
6793
|
+
// TASWM-424: «доступ отозван» — состояние ровно того же рода, что пауза, и
|
|
6794
|
+
// формулировка у него ОДНА на все поверхности (HOST_ACCESS_REVOKED_TEXT):
|
|
6795
|
+
// `hostLines` зовёт тот же `hostStateText`, что и парк машин в /swarm. Без
|
|
6796
|
+
// этого поля `swarm hosts` показывал отвергнутую машину просто молчащей —
|
|
6797
|
+
// то самое различие, ради которого флаг и заводили (TASWM-324).
|
|
6798
|
+
...typeof h.accessRevoked === "boolean" ? { accessRevoked: h.accessRevoked } : {},
|
|
6564
6799
|
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
6565
6800
|
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
6566
6801
|
...resources ? { resources } : {}
|
|
@@ -6646,9 +6881,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
6646
6881
|
* ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
|
|
6647
6882
|
* означало бы «прод помнит вчерашний шторм», когда рой давно работает.
|
|
6648
6883
|
*
|
|
6884
|
+
* TASWM-402: тем же пульсом уезжают КОРНИ WORKTREE'ЕВ этой машины (`worktreeRoots` —
|
|
6885
|
+
* `owner/repo` → `<worktreesDir>/<repo-basename>`). Это НЕ рантайм и правило «pid и путь
|
|
6886
|
+
* дрона в прод не уходят» не трогает: корень — статичная конфигурация машины, того же
|
|
6887
|
+
* рода, что реестр репо и ёмкость, он не зависит ни от прогона, ни от живости процесса и
|
|
6888
|
+
* не протухает между пульсами. Прод склеивает `<корень>/<слаг>` сам — и отвечает человеку
|
|
6889
|
+
* на вопрос «куда идти руками», когда команда машине не доиграла (а в этом случае, по
|
|
6890
|
+
* определению, спросить рантайм у молчащей машины уже нельзя). Не передали корень (не
|
|
6891
|
+
* знаем своего каталога) → ключа нет, прод прошлое не трогает — как с capacity/resources.
|
|
6892
|
+
*
|
|
6649
6893
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
6650
6894
|
*/
|
|
6651
|
-
async heartbeat(daemonVersion, pause, capacity, resources, storm) {
|
|
6895
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn) {
|
|
6652
6896
|
try {
|
|
6653
6897
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
6654
6898
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -6658,10 +6902,19 @@ var SwarmClient = class _SwarmClient {
|
|
|
6658
6902
|
args.paused = !!pause?.paused;
|
|
6659
6903
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
6660
6904
|
}
|
|
6905
|
+
if (signIn) {
|
|
6906
|
+
args.signedIn = signIn.signedIn;
|
|
6907
|
+
if (signIn.webUrl) args.webUrl = signIn.webUrl;
|
|
6908
|
+
}
|
|
6661
6909
|
if (storm !== void 0 && storm !== null) {
|
|
6662
6910
|
args.storm = storm.active;
|
|
6663
6911
|
if (storm.reason) args.stormReason = storm.reason;
|
|
6664
6912
|
}
|
|
6913
|
+
if (worktreesDir) {
|
|
6914
|
+
const roots = {};
|
|
6915
|
+
for (const [full, path] of this.repoPathByFullName) roots[full] = droneWorktreeRoot(worktreesDir, path);
|
|
6916
|
+
if (Object.keys(roots).length) args.worktreeRoots = roots;
|
|
6917
|
+
}
|
|
6665
6918
|
if (this.host) {
|
|
6666
6919
|
args.hostId = this.host.hostId;
|
|
6667
6920
|
args.hostName = this.host.hostName;
|
|
@@ -6810,10 +7063,20 @@ var SwarmClient = class _SwarmClient {
|
|
|
6810
7063
|
* именно путь. Не нашли в реестре (репо не наш / ручной `--repo <path>` спавн, где
|
|
6811
7064
|
* repo — ВСЕГДА уже путь, старый формат) → возвращаем как есть без изменений
|
|
6812
7065
|
* (обратная совместимость: раньше pr.repo ВСЕГДА был локальным путём).
|
|
7066
|
+
*
|
|
7067
|
+
* РЕГИСТР НЕ РАЗЛИЧАЕМ (TASWM-422). Ключи карты приведены к нижнему регистру
|
|
7068
|
+
* (parseRepoFullName — «GitHub сам не различает case в owner/repo»), а прод присылает
|
|
7069
|
+
* `run.repo` тем регистром, каким репозиторий записан в проекте. 17.08 это разошлось:
|
|
7070
|
+
* машина объявляла `ilyasmurov/ansamblist`, задача пришла с `ilyasmurov/Ansamblist`,
|
|
7071
|
+
* `Map.get` промахнулся — и вместо пути в прогон уехало полное имя репозитория. Дальше
|
|
7072
|
+
* git-операции шли в несуществующий каталог, прогон умирал за 5 секунд, а задача
|
|
7073
|
+
* возвращалась в очередь и бралась снова: пять павших прогонов за пять минут.
|
|
7074
|
+
* Возвращаем ИСХОДНУЮ строку, а не приведённую: промах бывает и на локальном пути
|
|
7075
|
+
* (ручной спавн), а путь регистр различает — вернув lowercase, мы сломали бы его.
|
|
6813
7076
|
*/
|
|
6814
7077
|
resolveRepo(raw) {
|
|
6815
7078
|
if (!raw) return "";
|
|
6816
|
-
return this.repoPathByFullName.get(raw) ?? raw;
|
|
7079
|
+
return this.repoPathByFullName.get(raw) ?? this.repoPathByFullName.get(raw.toLowerCase()) ?? raw;
|
|
6817
7080
|
}
|
|
6818
7081
|
/**
|
|
6819
7082
|
* TASWM-281: МОЯ ли это работа. Три исхода:
|
|
@@ -6889,6 +7152,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
6889
7152
|
// существующая локальная запись НЕ перетирается (поле не в field-copy ниже),
|
|
6890
7153
|
// в отличие от respawns, — авто-ревью v2, TASWM-162.
|
|
6891
7154
|
review_cycle: 0,
|
|
7155
|
+
// TASWM-451: модель ПРОВЕРКИ — прод-поле, заполняется ниже из ответа (как base_branch).
|
|
7156
|
+
review_model: null,
|
|
6892
7157
|
task_title: null,
|
|
6893
7158
|
task_desc: null,
|
|
6894
7159
|
task_url: null,
|
|
@@ -6941,6 +7206,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6941
7206
|
local.area = pr.area ?? null;
|
|
6942
7207
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
6943
7208
|
local.model = pr.model ?? null;
|
|
7209
|
+
local.review_model = pr.reviewModel ?? null;
|
|
6944
7210
|
local.effort = toEffortLevel(pr.effort);
|
|
6945
7211
|
local.cost_usd = typeof pr.costUsd === "number" ? pr.costUsd : 0;
|
|
6946
7212
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
@@ -7408,20 +7674,20 @@ function tailFile(path, lines) {
|
|
|
7408
7674
|
}
|
|
7409
7675
|
|
|
7410
7676
|
// src/logRotate.ts
|
|
7411
|
-
import { copyFileSync, renameSync as
|
|
7677
|
+
import { copyFileSync, renameSync as renameSync3, statSync as statSync5, fstatSync, truncateSync, unlinkSync, existsSync as existsSync6 } from "fs";
|
|
7412
7678
|
var realIo = {
|
|
7413
7679
|
size: (path) => {
|
|
7414
7680
|
try {
|
|
7415
|
-
return
|
|
7681
|
+
return statSync5(path).size;
|
|
7416
7682
|
} catch {
|
|
7417
7683
|
return null;
|
|
7418
7684
|
}
|
|
7419
7685
|
},
|
|
7420
7686
|
copy: copyFileSync,
|
|
7421
|
-
rename:
|
|
7687
|
+
rename: renameSync3,
|
|
7422
7688
|
truncate: (path) => truncateSync(path, 0),
|
|
7423
7689
|
unlink: unlinkSync,
|
|
7424
|
-
exists:
|
|
7690
|
+
exists: existsSync6
|
|
7425
7691
|
};
|
|
7426
7692
|
var STRAY_GENERATIONS = 8;
|
|
7427
7693
|
function rotateDaemonLog(path, opts, io = realIo) {
|
|
@@ -7679,18 +7945,18 @@ init_repoRegistry();
|
|
|
7679
7945
|
|
|
7680
7946
|
// src/hostIdentity.ts
|
|
7681
7947
|
import { hostname } from "os";
|
|
7682
|
-
import { existsSync as
|
|
7683
|
-
import { join as
|
|
7948
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
7949
|
+
import { join as join7 } from "path";
|
|
7684
7950
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
7685
7951
|
function hostIdentityPath(home) {
|
|
7686
|
-
return
|
|
7952
|
+
return join7(home, "host.json");
|
|
7687
7953
|
}
|
|
7688
7954
|
function loadHostIdentity(deps) {
|
|
7689
|
-
const exists = deps.exists ??
|
|
7955
|
+
const exists = deps.exists ?? existsSync7;
|
|
7690
7956
|
const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
|
|
7691
7957
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
7692
7958
|
writeFileSync4(`${p}.tmp`, data);
|
|
7693
|
-
|
|
7959
|
+
renameSync4(`${p}.tmp`, p);
|
|
7694
7960
|
});
|
|
7695
7961
|
const host = deps.hostname ?? hostname;
|
|
7696
7962
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -7804,8 +8070,8 @@ import { basename as basename2 } from "path";
|
|
|
7804
8070
|
import { serve as serve2 } from "@hono/node-server";
|
|
7805
8071
|
|
|
7806
8072
|
// src/diff.ts
|
|
7807
|
-
import { statSync as
|
|
7808
|
-
import { join as
|
|
8073
|
+
import { statSync as statSync6 } from "fs";
|
|
8074
|
+
import { join as join8 } from "path";
|
|
7809
8075
|
import { $ as $3 } from "execa";
|
|
7810
8076
|
var MAX_FILES = 200;
|
|
7811
8077
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -7948,7 +8214,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
7948
8214
|
}
|
|
7949
8215
|
function mtime(path) {
|
|
7950
8216
|
try {
|
|
7951
|
-
return
|
|
8217
|
+
return statSync6(path).mtimeMs;
|
|
7952
8218
|
} catch {
|
|
7953
8219
|
return 0;
|
|
7954
8220
|
}
|
|
@@ -7973,7 +8239,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
7973
8239
|
files.push(pf);
|
|
7974
8240
|
}
|
|
7975
8241
|
}
|
|
7976
|
-
files.sort((a, b) => mtime(
|
|
8242
|
+
files.sort((a, b) => mtime(join8(worktree, b.path)) - mtime(join8(worktree, a.path)));
|
|
7977
8243
|
const truncatedFiles = files.length > MAX_FILES;
|
|
7978
8244
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
7979
8245
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -8126,13 +8392,13 @@ function deniedSinceLine(nowMs) {
|
|
|
8126
8392
|
init_config();
|
|
8127
8393
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
8128
8394
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
8129
|
-
import { join as
|
|
8395
|
+
import { join as join10 } from "path";
|
|
8130
8396
|
var REGISTER = "/api/oauth/register";
|
|
8131
8397
|
var AUTHORIZE = "/api/oauth/authorize";
|
|
8132
8398
|
var TOKEN = "/api/oauth/token";
|
|
8133
8399
|
var SCOPE = "openid profile email taskless:full";
|
|
8134
|
-
var STORE_PATH2 =
|
|
8135
|
-
var CLIENT_PATH =
|
|
8400
|
+
var STORE_PATH2 = join10(SWARM_HOME, "oauth.json");
|
|
8401
|
+
var CLIENT_PATH = join10(SWARM_HOME, "oauth-client.json");
|
|
8136
8402
|
function makePkce() {
|
|
8137
8403
|
const verifier = randomBytes2(48).toString("base64url");
|
|
8138
8404
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -9859,15 +10125,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
9859
10125
|
|
|
9860
10126
|
// src/launcher.ts
|
|
9861
10127
|
import { spawn as spawn2 } from "child_process";
|
|
9862
|
-
import { closeSync as closeSync3, existsSync as
|
|
10128
|
+
import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync7 } from "fs";
|
|
9863
10129
|
import { createRequire } from "module";
|
|
9864
10130
|
import { homedir as homedir3 } from "os";
|
|
9865
|
-
import { dirname as dirname3, join as
|
|
10131
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
9866
10132
|
import { fileURLToPath } from "url";
|
|
9867
10133
|
|
|
9868
10134
|
// src/droneHome.ts
|
|
9869
|
-
import { lstatSync, mkdirSync as
|
|
9870
|
-
import { join as
|
|
10135
|
+
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync6, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
10136
|
+
import { join as join11, relative, sep } from "path";
|
|
9871
10137
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
9872
10138
|
function droneHomeDeny(env = process.env) {
|
|
9873
10139
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -9885,29 +10151,29 @@ function buildDroneHome(opts) {
|
|
|
9885
10151
|
const { realHome, droneHome } = opts;
|
|
9886
10152
|
const deny = opts.deny ?? droneHomeDeny();
|
|
9887
10153
|
const platform = opts.platform ?? process.platform;
|
|
9888
|
-
|
|
9889
|
-
const wanted = new Set(mirroredEntries(
|
|
10154
|
+
mkdirSync7(droneHome, { recursive: true, mode: 448 });
|
|
10155
|
+
const wanted = new Set(mirroredEntries(readdirSync6(realHome), deny, platform));
|
|
9890
10156
|
const rel = relative(realHome, droneHome);
|
|
9891
10157
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
9892
|
-
for (const existing of
|
|
10158
|
+
for (const existing of readdirSync6(droneHome)) {
|
|
9893
10159
|
if (wanted.has(existing)) continue;
|
|
9894
|
-
const p =
|
|
10160
|
+
const p = join11(droneHome, existing);
|
|
9895
10161
|
try {
|
|
9896
10162
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
9897
10163
|
} catch {
|
|
9898
10164
|
}
|
|
9899
10165
|
}
|
|
9900
10166
|
for (const name of wanted) {
|
|
9901
|
-
const link =
|
|
10167
|
+
const link = join11(droneHome, name);
|
|
9902
10168
|
try {
|
|
9903
10169
|
lstatSync(link);
|
|
9904
10170
|
continue;
|
|
9905
10171
|
} catch {
|
|
9906
10172
|
}
|
|
9907
|
-
const target =
|
|
10173
|
+
const target = join11(realHome, name);
|
|
9908
10174
|
let isDir;
|
|
9909
10175
|
try {
|
|
9910
|
-
isDir =
|
|
10176
|
+
isDir = statSync7(target).isDirectory();
|
|
9911
10177
|
} catch {
|
|
9912
10178
|
continue;
|
|
9913
10179
|
}
|
|
@@ -9925,7 +10191,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
9925
10191
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
9926
10192
|
];
|
|
9927
10193
|
function realPathEnvPins(realHome) {
|
|
9928
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
10194
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join11(realHome, ...segments)]));
|
|
9929
10195
|
}
|
|
9930
10196
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
9931
10197
|
return [
|
|
@@ -9985,12 +10251,26 @@ function pick(v) {
|
|
|
9985
10251
|
const s = typeof v === "string" ? v.trim() : "";
|
|
9986
10252
|
return s.length > 0 ? s : null;
|
|
9987
10253
|
}
|
|
10254
|
+
function isReviewKind(kind) {
|
|
10255
|
+
return kind === "reviewer" || kind === "verifier";
|
|
10256
|
+
}
|
|
10257
|
+
function modelRoleOf(kind) {
|
|
10258
|
+
return isReviewKind(kind) ? "review" : "executor";
|
|
10259
|
+
}
|
|
9988
10260
|
function forKind(kind, swarm, norm) {
|
|
9989
10261
|
if (!swarm) return null;
|
|
9990
|
-
return kind
|
|
10262
|
+
return isReviewKind(kind) ? norm(swarm.reviewer) ?? norm(swarm.drone) : norm(swarm.drone);
|
|
10263
|
+
}
|
|
10264
|
+
function resolveRunModelRef(run, swarm, env = process.env) {
|
|
10265
|
+
const configured = parseModelRef(
|
|
10266
|
+
pick(run.model) ?? forKind(run.kind, swarm, pick) ?? pick(env.SWARM_DRONE_MODEL)
|
|
10267
|
+
);
|
|
10268
|
+
if (configured) return configured;
|
|
10269
|
+
const providerId = pick(env.SWARM_MODEL_PROVIDER) ?? DEFAULT_PROVIDER_ID;
|
|
10270
|
+
return { providerId, model: defaultModelFor(modelRoleOf(run.kind), providerId) };
|
|
9991
10271
|
}
|
|
9992
10272
|
function resolveRunModel(run, swarm, env = process.env) {
|
|
9993
|
-
return
|
|
10273
|
+
return resolveRunModelRef(run, swarm, env).model;
|
|
9994
10274
|
}
|
|
9995
10275
|
function resolveRunEffort(run, swarm, env = process.env) {
|
|
9996
10276
|
return toEffortLevel(run.effort) ?? forKind(run.kind, swarm, toEffortLevel) ?? toEffortLevel(env.SWARM_DRONE_EFFORT) ?? null;
|
|
@@ -10023,7 +10303,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
10023
10303
|
const env = { ...base3 };
|
|
10024
10304
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
10025
10305
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
10026
|
-
env.TURBO_CACHE_DIR =
|
|
10306
|
+
env.TURBO_CACHE_DIR = join12(worktree, ".turbo", "cache");
|
|
10027
10307
|
}
|
|
10028
10308
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
10029
10309
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -10051,20 +10331,21 @@ var DAEMON_STATE_ENV = [
|
|
|
10051
10331
|
"TASKLESS_TOKEN"
|
|
10052
10332
|
];
|
|
10053
10333
|
function bundledRoleDir(kind) {
|
|
10054
|
-
return
|
|
10334
|
+
return join12(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
10055
10335
|
}
|
|
10056
10336
|
function roleDir(worktree, kind) {
|
|
10057
|
-
return
|
|
10337
|
+
return join12(worktree, ".claude", "roles", kind);
|
|
10058
10338
|
}
|
|
10059
10339
|
function roleDirs(worktree, kind) {
|
|
10060
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
10340
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync8(dir));
|
|
10061
10341
|
}
|
|
10062
|
-
function nextRunLogPath(runLogsDir,
|
|
10063
|
-
const dir =
|
|
10064
|
-
|
|
10342
|
+
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
10343
|
+
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
10344
|
+
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)");
|
|
10345
|
+
mkdirSync8(dir, { recursive: true });
|
|
10065
10346
|
let files = [];
|
|
10066
10347
|
try {
|
|
10067
|
-
files =
|
|
10348
|
+
files = readdirSync7(dir);
|
|
10068
10349
|
} catch {
|
|
10069
10350
|
}
|
|
10070
10351
|
let next = 0;
|
|
@@ -10072,11 +10353,11 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
10072
10353
|
const m = /^(\d+)\.json/.exec(f);
|
|
10073
10354
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
10074
10355
|
}
|
|
10075
|
-
return
|
|
10356
|
+
return join12(dir, `${next}.json`);
|
|
10076
10357
|
}
|
|
10077
|
-
function openNextRunLog(runLogsDir,
|
|
10358
|
+
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
10078
10359
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
10079
|
-
const path = nextRunLogPath(runLogsDir,
|
|
10360
|
+
const path = nextRunLogPath(runLogsDir, runLogKeys);
|
|
10080
10361
|
let out;
|
|
10081
10362
|
try {
|
|
10082
10363
|
out = openSync3(path, "wx");
|
|
@@ -10092,7 +10373,7 @@ function openNextRunLog(runLogsDir, runId) {
|
|
|
10092
10373
|
}
|
|
10093
10374
|
return { out, err };
|
|
10094
10375
|
}
|
|
10095
|
-
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 \
|
|
10376
|
+
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?`);
|
|
10096
10377
|
}
|
|
10097
10378
|
function makeLauncher(cfg) {
|
|
10098
10379
|
return ({ worktree, run, prompt, continueSession }) => {
|
|
@@ -10155,9 +10436,9 @@ function makeLauncher(cfg) {
|
|
|
10155
10436
|
}
|
|
10156
10437
|
const realHome = cfg.droneIsolation?.realHome ?? homedir3();
|
|
10157
10438
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
10158
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
10439
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
10159
10440
|
console.error(
|
|
10160
|
-
`[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 ${
|
|
10441
|
+
`[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 ${join12(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.`
|
|
10161
10442
|
);
|
|
10162
10443
|
}
|
|
10163
10444
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10184,7 +10465,13 @@ function makeLauncher(cfg) {
|
|
|
10184
10465
|
let outFd;
|
|
10185
10466
|
let errFd;
|
|
10186
10467
|
if (cfg.runLogsDir) {
|
|
10187
|
-
const
|
|
10468
|
+
const logKeys = cfg.runLogKeys?.(run) ?? [];
|
|
10469
|
+
if (logKeys.length === 0) {
|
|
10470
|
+
throw new Error(
|
|
10471
|
+
`\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).`
|
|
10472
|
+
);
|
|
10473
|
+
}
|
|
10474
|
+
const fds = openNextRunLog(cfg.runLogsDir, logKeys);
|
|
10188
10475
|
outFd = fds.out;
|
|
10189
10476
|
errFd = fds.err ?? void 0;
|
|
10190
10477
|
stdio = ["ignore", outFd, errFd ?? "ignore"];
|
|
@@ -10212,14 +10499,14 @@ function makeLauncher(cfg) {
|
|
|
10212
10499
|
init_droneChannel();
|
|
10213
10500
|
|
|
10214
10501
|
// src/git.ts
|
|
10215
|
-
import { existsSync as
|
|
10502
|
+
import { existsSync as existsSync9, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
|
|
10216
10503
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
10217
|
-
import { isAbsolute, join as
|
|
10504
|
+
import { isAbsolute, join as join13, sep as sep2 } from "path";
|
|
10218
10505
|
import { $ as $5 } from "execa";
|
|
10219
10506
|
init_swarmFiles();
|
|
10220
10507
|
function isLinkedWorktree(dir) {
|
|
10221
10508
|
try {
|
|
10222
|
-
return
|
|
10509
|
+
return statSync8(join13(dir, ".git")).isFile();
|
|
10223
10510
|
} catch {
|
|
10224
10511
|
return false;
|
|
10225
10512
|
}
|
|
@@ -10231,7 +10518,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
10231
10518
|
}
|
|
10232
10519
|
}
|
|
10233
10520
|
async function ensureWorktreeDirGone(worktree) {
|
|
10234
|
-
if (!
|
|
10521
|
+
if (!existsSync9(worktree)) return false;
|
|
10235
10522
|
if (!isLinkedWorktree(worktree)) {
|
|
10236
10523
|
console.error(
|
|
10237
10524
|
`[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.`
|
|
@@ -10246,7 +10533,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
10246
10533
|
);
|
|
10247
10534
|
return true;
|
|
10248
10535
|
}
|
|
10249
|
-
if (
|
|
10536
|
+
if (existsSync9(worktree)) {
|
|
10250
10537
|
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.`);
|
|
10251
10538
|
}
|
|
10252
10539
|
return true;
|
|
@@ -10274,7 +10561,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
10274
10561
|
|
|
10275
10562
|
// src/attachments.ts
|
|
10276
10563
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
10277
|
-
import { join as
|
|
10564
|
+
import { join as join14 } from "path";
|
|
10278
10565
|
init_swarmFiles();
|
|
10279
10566
|
var ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
|
|
10280
10567
|
var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
|
|
@@ -10286,7 +10573,7 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
|
|
|
10286
10573
|
|
|
10287
10574
|
// src/shotUpload.ts
|
|
10288
10575
|
import { readFile as fsReadFile } from "fs/promises";
|
|
10289
|
-
import { basename as basename3, isAbsolute as isAbsolute2, join as
|
|
10576
|
+
import { basename as basename3, isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
10290
10577
|
var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
10291
10578
|
|
|
10292
10579
|
// src/taskless.ts
|
|
@@ -10297,8 +10584,8 @@ init_linkFailure();
|
|
|
10297
10584
|
// src/workspace.ts
|
|
10298
10585
|
init_config();
|
|
10299
10586
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
|
|
10300
|
-
import { join as
|
|
10301
|
-
var WORKSPACE_PATH =
|
|
10587
|
+
import { join as join16 } from "path";
|
|
10588
|
+
var WORKSPACE_PATH = join16(SWARM_HOME, "workspace.json");
|
|
10302
10589
|
function loadWorkspace() {
|
|
10303
10590
|
try {
|
|
10304
10591
|
const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
|
|
@@ -11189,7 +11476,7 @@ async function runDaemonLogs(argv) {
|
|
|
11189
11476
|
console.error(`\u043B\u043E\u0433 \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u044D\u0442\u043E \xABswarm log ${stray}\xBB (\u0431\u0435\u0437 s). \xABswarm logs\xBB \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430; \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A \u2014 \xABswarm logs -n 200\xBB.`);
|
|
11190
11477
|
return 1;
|
|
11191
11478
|
}
|
|
11192
|
-
if (!
|
|
11479
|
+
if (!existsSync16(DAEMON_LOG_PATH)) {
|
|
11193
11480
|
console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
|
|
11194
11481
|
console.error("\u043E\u043D \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0444\u043E\u043D\u043E\u0432\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 (swarm start). \u0414\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 (--foreground) \u2014 \u0432\u044B\u0432\u043E\u0434 \u0438\u0434\u0451\u0442 \u0442\u0443\u0434\u0430.");
|
|
11195
11482
|
return 1;
|
|
@@ -11197,12 +11484,12 @@ async function runDaemonLogs(argv) {
|
|
|
11197
11484
|
const head = tailFile(DAEMON_LOG_PATH, lines);
|
|
11198
11485
|
if (head) console.log(head);
|
|
11199
11486
|
if (!follow) return 0;
|
|
11200
|
-
let pos =
|
|
11487
|
+
let pos = statSync10(DAEMON_LOG_PATH).size;
|
|
11201
11488
|
for (; ; ) {
|
|
11202
11489
|
await sleep2(500);
|
|
11203
11490
|
let size;
|
|
11204
11491
|
try {
|
|
11205
|
-
size =
|
|
11492
|
+
size = statSync10(DAEMON_LOG_PATH).size;
|
|
11206
11493
|
} catch {
|
|
11207
11494
|
continue;
|
|
11208
11495
|
}
|
|
@@ -11461,10 +11748,10 @@ function effectiveWorktreesDir() {
|
|
|
11461
11748
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
11462
11749
|
targetDir: WORKTREES_DIR,
|
|
11463
11750
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
11464
|
-
exists: (d) =>
|
|
11751
|
+
exists: (d) => existsSync16(d),
|
|
11465
11752
|
isNonEmptyDir: (d) => {
|
|
11466
11753
|
try {
|
|
11467
|
-
return
|
|
11754
|
+
return readdirSync10(d).length > 0;
|
|
11468
11755
|
} catch {
|
|
11469
11756
|
return false;
|
|
11470
11757
|
}
|
|
@@ -11501,7 +11788,7 @@ async function runScoutCommand(repo) {
|
|
|
11501
11788
|
});
|
|
11502
11789
|
const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, autoFlush: false });
|
|
11503
11790
|
const name = pickName([]);
|
|
11504
|
-
const outFile =
|
|
11791
|
+
const outFile = join24(SWARM_HOME, `scout-${repoFullName.replace(/[^\w.-]+/g, "-")}.json`);
|
|
11505
11792
|
const bin = resolveExecutor().resolveBin();
|
|
11506
11793
|
const code = await runRepoScout({
|
|
11507
11794
|
repoFullName,
|
|
@@ -11521,7 +11808,7 @@ async function runScoutCommand(repo) {
|
|
|
11521
11808
|
});
|
|
11522
11809
|
child.on("exit", (c) => resolve4(c ?? 1));
|
|
11523
11810
|
}),
|
|
11524
|
-
readOut: () =>
|
|
11811
|
+
readOut: () => existsSync16(outFile) ? readFileSync17(outFile, "utf8") : null,
|
|
11525
11812
|
clearOut: () => rmSync2(outFile, { force: true }),
|
|
11526
11813
|
report: (report) => client.reportRepoScout(repoFullName, report, name),
|
|
11527
11814
|
log: (line) => console.log(line)
|
|
@@ -11597,7 +11884,7 @@ async function runZones(db2, repo, args) {
|
|
|
11597
11884
|
}
|
|
11598
11885
|
if (model.spine.length) {
|
|
11599
11886
|
console.log(`
|
|
11600
|
-
\u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${model.spine.length} \u0444\u0430\u0439\u043B\u043E\u0432) \u2014 \u0437\u0430\u0434\u0430\u0447\u0438 \
|
|
11887
|
+
\u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${model.spine.length} \u0444\u0430\u0439\u043B\u043E\u0432) \u2014 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u041E\u0414\u041D\u041E\u041C \u0442\u0430\u043A\u043E\u043C \u0444\u0430\u0439\u043B\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:`);
|
|
11601
11888
|
for (const f of model.spine.slice(0, 15)) console.log(` ${f}`);
|
|
11602
11889
|
}
|
|
11603
11890
|
console.log("\n\u0441\u0432\u0435\u0440\u0438\u0442\u044C \u0441 \u0440\u0443\u0447\u043D\u043E\u0439 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u043E\u0439: swarm zones check");
|
|
@@ -11918,10 +12205,19 @@ switch (cmd) {
|
|
|
11918
12205
|
overmindEntry: OVERMIND_ENTRY,
|
|
11919
12206
|
dbPath: DB_PATH,
|
|
11920
12207
|
runLogsDir: RUN_LOGS_DIR,
|
|
12208
|
+
// TASWM-419: каталог лога — по стабильному ключу прогона (тот же адрес, что у демона).
|
|
12209
|
+
runLogKeys: (r) => db.runLogKeys(r.id),
|
|
11921
12210
|
// Тот же исполнитель, что у демона (TASWM-349): `swarm wake` при мёртвом демоне не
|
|
11922
12211
|
// должен внезапно поднимать дрона другим харнессом.
|
|
11923
12212
|
executor: EXECUTOR,
|
|
11924
12213
|
droneIsolation: DRONE_ISOLATION,
|
|
12214
|
+
// TASWM-450: ПОТОЛОК ТРАТ обязателен и здесь. Демон собирает лаунчер с
|
|
12215
|
+
// `maxCostUsd: () => lastMaxCostUsd`, а этот путь его не передавал вовсе — значит
|
|
12216
|
+
// разбуженный руками прогон уходил БЕЗ `--max-budget-usd` и мог жечь сколько угодно.
|
|
12217
|
+
// Живой настройки роя у CLI нет (демон не отвечает — иначе мы сюда не попали бы),
|
|
12218
|
+
// поэтому источник тот же, что и у остальных ~40 ручек: env машины, а за ним дефолт
|
|
12219
|
+
// ядра. Это ровно то, что делает `launchBudgetUsd(null, …)`.
|
|
12220
|
+
maxCostUsd: () => null,
|
|
11925
12221
|
...DRONE_CHANNEL_ENABLED ? {
|
|
11926
12222
|
channel: {
|
|
11927
12223
|
url: DRONE_CHANNEL_URL,
|