@taskless-app/swarm 0.6.10 → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1220 -356
- package/dist/overmind.js +1128 -301
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -49,7 +49,7 @@ function isMilestoneKind(kind) {
|
|
|
49
49
|
return MILESTONE_KIND_SET.has(kind);
|
|
50
50
|
}
|
|
51
51
|
function toRunKind(role) {
|
|
52
|
-
return role === "hero" || role === "reviewer" ? role : "drone";
|
|
52
|
+
return role === "hero" || role === "reviewer" || role === "verifier" ? role : "drone";
|
|
53
53
|
}
|
|
54
54
|
var EVENT_KINDS, MILESTONE_KINDS, MILESTONE_KIND_SET;
|
|
55
55
|
var init_types = __esm({
|
|
@@ -338,6 +338,74 @@ var init_reviewPhase = __esm({
|
|
|
338
338
|
}
|
|
339
339
|
});
|
|
340
340
|
|
|
341
|
+
// ../swarm-core/src/verifyVerdict.ts
|
|
342
|
+
function isVerifyVerdict(v) {
|
|
343
|
+
return typeof v === "string" && VERIFY_VERDICT_SET.has(v);
|
|
344
|
+
}
|
|
345
|
+
function toVerifyVerdict(v) {
|
|
346
|
+
return isVerifyVerdict(v) ? v : null;
|
|
347
|
+
}
|
|
348
|
+
function isNegativeVerifyVerdict(v) {
|
|
349
|
+
return v === "not_reproduced" || v === "found_issues";
|
|
350
|
+
}
|
|
351
|
+
function verifyVerdictLabel(v) {
|
|
352
|
+
switch (v) {
|
|
353
|
+
case "confirmed":
|
|
354
|
+
return "\u041F\u041E\u0414\u0422\u0412\u0415\u0420\u0416\u0414\u0410\u042E";
|
|
355
|
+
case "not_reproduced":
|
|
356
|
+
return "\u041D\u0415 \u0412\u041E\u0421\u041F\u0420\u041E\u0418\u0417\u0412\u041E\u0414\u0418\u0422\u0421\u042F";
|
|
357
|
+
case "found_issues":
|
|
358
|
+
return "\u041D\u0410\u0428\u0401\u041B";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function verifyStats(runs) {
|
|
362
|
+
let confirmed = 0;
|
|
363
|
+
let notReproduced = 0;
|
|
364
|
+
let foundIssues = 0;
|
|
365
|
+
for (const r of runs) {
|
|
366
|
+
switch (toVerifyVerdict(r.verify_verdict)) {
|
|
367
|
+
case "confirmed":
|
|
368
|
+
confirmed++;
|
|
369
|
+
break;
|
|
370
|
+
case "not_reproduced":
|
|
371
|
+
notReproduced++;
|
|
372
|
+
break;
|
|
373
|
+
case "found_issues":
|
|
374
|
+
foundIssues++;
|
|
375
|
+
break;
|
|
376
|
+
default:
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const negative = notReproduced + foundIssues;
|
|
381
|
+
const total = confirmed + negative;
|
|
382
|
+
return { total, confirmed, notReproduced, foundIssues, negative, negativeShare: total ? negative / total : 0 };
|
|
383
|
+
}
|
|
384
|
+
function verifyStatsByDrone(runs) {
|
|
385
|
+
const byName = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const r of runs) {
|
|
387
|
+
if (!toVerifyVerdict(r.verify_verdict)) continue;
|
|
388
|
+
const name = (r.name ?? "").trim() || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)";
|
|
389
|
+
const bucket = byName.get(name);
|
|
390
|
+
if (bucket) bucket.push(r);
|
|
391
|
+
else byName.set(name, [r]);
|
|
392
|
+
}
|
|
393
|
+
return [...byName.entries()].map(([name, rs]) => ({ name, ...verifyStats(rs) })).sort((a, b) => b.negative - a.negative || b.total - a.total || a.name.localeCompare(b.name));
|
|
394
|
+
}
|
|
395
|
+
function verifyStatsLine(s) {
|
|
396
|
+
if (!s.total) return "\u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u0432 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442";
|
|
397
|
+
const pct = Math.round(s.negativeShare * 100);
|
|
398
|
+
return `\u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u0432 ${s.total} \xB7 \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0445 ${s.negative} (${pct}%): \u043D\u0435 \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u043B\u043E\u0441\u044C ${s.notReproduced}, \u043D\u0430\u0448\u043B\u0438 \u0434\u0435\u0444\u0435\u043A\u0442 ${s.foundIssues}`;
|
|
399
|
+
}
|
|
400
|
+
var VERIFY_VERDICTS, VERIFY_VERDICT_SET;
|
|
401
|
+
var init_verifyVerdict = __esm({
|
|
402
|
+
"../swarm-core/src/verifyVerdict.ts"() {
|
|
403
|
+
"use strict";
|
|
404
|
+
VERIFY_VERDICTS = ["confirmed", "not_reproduced", "found_issues"];
|
|
405
|
+
VERIFY_VERDICT_SET = new Set(VERIFY_VERDICTS);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
341
409
|
// ../swarm-core/src/mergeGate.ts
|
|
342
410
|
function computeMergeGate(input) {
|
|
343
411
|
if (!input.hasRun) {
|
|
@@ -346,17 +414,33 @@ function computeMergeGate(input) {
|
|
|
346
414
|
if (!input.autoReview) {
|
|
347
415
|
return { allowed: true, code: "auto_review_off", reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F" };
|
|
348
416
|
}
|
|
349
|
-
if (input.reviewVerdict === "approve") {
|
|
350
|
-
return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
|
|
351
|
-
}
|
|
352
417
|
if (input.reviewVerdict === "changes") {
|
|
353
418
|
return { allowed: false, code: "changes_requested", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043F\u0440\u0430\u0432\u043A\u0438 (REQUEST CHANGES) \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F, \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0444\u0438\u043A\u0441-\u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043D\u043E\u0432\u043E\u0433\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430" };
|
|
354
419
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
420
|
+
if (input.reviewVerdict !== "approve") {
|
|
421
|
+
return {
|
|
422
|
+
allowed: false,
|
|
423
|
+
code: "no_verdict",
|
|
424
|
+
reason: "\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E, \u0430 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-355)"
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (input.autoVerify) {
|
|
428
|
+
if (input.verifyVerdict == null) {
|
|
429
|
+
return {
|
|
430
|
+
allowed: false,
|
|
431
|
+
code: "no_verify_verdict",
|
|
432
|
+
reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve, \u043D\u043E \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0433\u043E \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 PR \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0440\u0430\u0431\u043E\u0442\u0430 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0435\u0439 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 (TASWM-344)"
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
if (isNegativeVerifyVerdict(input.verifyVerdict)) {
|
|
436
|
+
return {
|
|
437
|
+
allowed: false,
|
|
438
|
+
code: "verify_negative",
|
|
439
|
+
reason: `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u044B\u043D\u0435\u0441 \xAB${verifyVerdictLabel(input.verifyVerdict)}\xBB \u2014 \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u043E\u0441\u044C, \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F (TASWM-344)`
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return { allowed: true, code: "approved", reason: "\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u0430\u043B approve \u2014 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u043C\u043E\u0436\u043D\u043E" };
|
|
360
444
|
}
|
|
361
445
|
function mergeGateLine(d) {
|
|
362
446
|
return `${d.allowed ? "\u2705 \u043C\u0435\u0440\u0436 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D" : "\u26D4 \u043C\u0435\u0440\u0436 \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D"}: ${d.reason}`;
|
|
@@ -364,6 +448,7 @@ function mergeGateLine(d) {
|
|
|
364
448
|
var init_mergeGate = __esm({
|
|
365
449
|
"../swarm-core/src/mergeGate.ts"() {
|
|
366
450
|
"use strict";
|
|
451
|
+
init_verifyVerdict();
|
|
367
452
|
}
|
|
368
453
|
});
|
|
369
454
|
|
|
@@ -619,7 +704,7 @@ function splitQueue(items, ref) {
|
|
|
619
704
|
return { queue, hosted };
|
|
620
705
|
}
|
|
621
706
|
function groupByHost(items, ref, opts = {}) {
|
|
622
|
-
const { hosts = [], nowMs = Date.now(), staleMs = HOST_HEARTBEAT_STALE_MS } = opts;
|
|
707
|
+
const { hosts = [], nowMs = Date.now(), staleMs: staleMs2 = HOST_HEARTBEAT_STALE_MS } = opts;
|
|
623
708
|
const infoById = new Map(hosts.map((h) => [h.hostId, h]));
|
|
624
709
|
const byKey = /* @__PURE__ */ new Map();
|
|
625
710
|
const ensure = (key, fallbackName) => {
|
|
@@ -648,25 +733,28 @@ function groupByHost(items, ref, opts = {}) {
|
|
|
648
733
|
ensure(key, hostName ?? null).runs.push(item);
|
|
649
734
|
}
|
|
650
735
|
const groups = [...byKey.values()];
|
|
651
|
-
for (const g of groups) g.offline = isHostOffline(g.lastSeenAt, nowMs,
|
|
736
|
+
for (const g of groups) g.offline = isHostOffline(g.lastSeenAt, nowMs, staleMs2);
|
|
652
737
|
return groups.sort((a, b) => {
|
|
653
738
|
if (a.unknown !== b.unknown) return a.unknown ? 1 : -1;
|
|
654
739
|
return a.name.localeCompare(b.name, "ru");
|
|
655
740
|
});
|
|
656
741
|
}
|
|
657
|
-
function isHostOffline(lastSeenAt, nowMs,
|
|
742
|
+
function isHostOffline(lastSeenAt, nowMs, staleMs2 = HOST_HEARTBEAT_STALE_MS) {
|
|
658
743
|
if (!lastSeenAt) return false;
|
|
659
744
|
const at = Date.parse(lastSeenAt);
|
|
660
745
|
if (Number.isNaN(at)) return false;
|
|
661
|
-
return nowMs - at >
|
|
746
|
+
return nowMs - at > staleMs2;
|
|
662
747
|
}
|
|
663
|
-
function hostStateText(host, nowMs = Date.now(),
|
|
748
|
+
function hostStateText(host, nowMs = Date.now(), staleMs2 = HOST_HEARTBEAT_STALE_MS) {
|
|
664
749
|
if (host.draining) return host.drainReason ? `\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435 \u2014 ${host.drainReason}` : "\u0432\u044B\u0432\u043E\u0434\u0438\u0442\u0441\u044F \u043D\u0430 \u043E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435";
|
|
750
|
+
if (host.accessRevoked) return HOST_ACCESS_REVOKED_TEXT;
|
|
665
751
|
if (!host.lastSeenAt) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
|
|
666
752
|
const at = Date.parse(host.lastSeenAt);
|
|
667
753
|
if (Number.isNaN(at)) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
|
|
668
|
-
if (!isHostOffline(host.lastSeenAt, nowMs,
|
|
669
|
-
|
|
754
|
+
if (!isHostOffline(host.lastSeenAt, nowMs, staleMs2)) {
|
|
755
|
+
if (host.paused) return host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435";
|
|
756
|
+
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
|
+
return "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
|
|
670
758
|
}
|
|
671
759
|
const min = Math.max(1, Math.round((nowMs - at) / 6e4));
|
|
672
760
|
return `\u043C\u043E\u043B\u0447\u0438\u0442 ${min} \u043C\u0438\u043D`;
|
|
@@ -674,7 +762,7 @@ function hostStateText(host, nowMs = Date.now(), staleMs = HOST_HEARTBEAT_STALE_
|
|
|
674
762
|
function shouldGroupByHost(groups) {
|
|
675
763
|
return groups.length > 1;
|
|
676
764
|
}
|
|
677
|
-
var ANY_HOST_TOKENS, UNKNOWN_HOST_KEY, UNKNOWN_HOST_LABEL, QUEUE_LABEL, NOT_STARTED_STATUSES, HOST_HEARTBEAT_STALE_MS;
|
|
765
|
+
var ANY_HOST_TOKENS, UNKNOWN_HOST_KEY, UNKNOWN_HOST_LABEL, QUEUE_LABEL, NOT_STARTED_STATUSES, HOST_HEARTBEAT_STALE_MS, HOST_ACCESS_REVOKED_TEXT;
|
|
678
766
|
var init_hosts = __esm({
|
|
679
767
|
"../swarm-core/src/hosts.ts"() {
|
|
680
768
|
"use strict";
|
|
@@ -684,6 +772,7 @@ var init_hosts = __esm({
|
|
|
684
772
|
QUEUE_LABEL = "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438";
|
|
685
773
|
NOT_STARTED_STATUSES = /* @__PURE__ */ new Set(["queued"]);
|
|
686
774
|
HOST_HEARTBEAT_STALE_MS = 45e3;
|
|
775
|
+
HOST_ACCESS_REVOKED_TEXT = "\u0434\u043E\u0441\u0442\u0443\u043F \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u2014 \u0432\u043E\u0439\u0434\u0438 \u0437\u0430\u043D\u043E\u0432\u043E \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 (swarm login)";
|
|
687
776
|
}
|
|
688
777
|
});
|
|
689
778
|
|
|
@@ -706,17 +795,17 @@ function parseHostResources(raw) {
|
|
|
706
795
|
measuredAt: v.measuredAt
|
|
707
796
|
};
|
|
708
797
|
}
|
|
709
|
-
function hostResourcesFresh(resources, nowMs,
|
|
798
|
+
function hostResourcesFresh(resources, nowMs, staleMs2 = HOST_RESOURCES_STALE_MS) {
|
|
710
799
|
if (!resources) return false;
|
|
711
800
|
const at = Date.parse(resources.measuredAt);
|
|
712
801
|
if (Number.isNaN(at)) return false;
|
|
713
|
-
return nowMs - at <=
|
|
802
|
+
return nowMs - at <= staleMs2;
|
|
714
803
|
}
|
|
715
804
|
function gb(value) {
|
|
716
805
|
return value < 10 ? value.toFixed(1) : String(Math.round(value));
|
|
717
806
|
}
|
|
718
|
-
function hostResourcesText(resources, nowMs = Date.now(),
|
|
719
|
-
if (!hostResourcesFresh(resources, nowMs,
|
|
807
|
+
function hostResourcesText(resources, nowMs = Date.now(), staleMs2 = HOST_RESOURCES_STALE_MS) {
|
|
808
|
+
if (!hostResourcesFresh(resources, nowMs, staleMs2)) return null;
|
|
720
809
|
const r = resources;
|
|
721
810
|
const parts = [];
|
|
722
811
|
if (r.cpuBusyPct != null) parts.push(`CPU ${Math.round(r.cpuBusyPct)}%`);
|
|
@@ -1226,6 +1315,7 @@ var init_src = __esm({
|
|
|
1226
1315
|
init_timeline();
|
|
1227
1316
|
init_reviewPhase();
|
|
1228
1317
|
init_mergeGate();
|
|
1318
|
+
init_verifyVerdict();
|
|
1229
1319
|
init_status();
|
|
1230
1320
|
init_deps();
|
|
1231
1321
|
init_time();
|
|
@@ -1338,11 +1428,18 @@ var init_swarmFiles = __esm({
|
|
|
1338
1428
|
});
|
|
1339
1429
|
|
|
1340
1430
|
// src/pricing.ts
|
|
1341
|
-
function
|
|
1342
|
-
const
|
|
1343
|
-
if (!
|
|
1431
|
+
function pricingAt(pricing, at) {
|
|
1432
|
+
const intro = pricing.intro;
|
|
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;
|
|
1439
|
+
if (!entry) {
|
|
1344
1440
|
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` };
|
|
1345
1441
|
}
|
|
1442
|
+
const pricing = pricingAt(entry, at);
|
|
1346
1443
|
const input = usage2.input_tokens ?? 0;
|
|
1347
1444
|
const output = usage2.output_tokens ?? 0;
|
|
1348
1445
|
const cacheWrite = usage2.cache_creation_input_tokens ?? 0;
|
|
@@ -1355,10 +1452,12 @@ var init_pricing = __esm({
|
|
|
1355
1452
|
"src/pricing.ts"() {
|
|
1356
1453
|
"use strict";
|
|
1357
1454
|
MODEL_PRICING = {
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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
|
+
},
|
|
1362
1461
|
"claude-opus-5": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
1363
1462
|
// Прогоны на 4.8 остаются в истории — строку держим ради их стоимости.
|
|
1364
1463
|
"claude-opus-4-8": { inputPerMTok: 5, outputPerMTok: 25 },
|
|
@@ -1802,7 +1901,7 @@ function tokenFromFile() {
|
|
|
1802
1901
|
return "";
|
|
1803
1902
|
}
|
|
1804
1903
|
}
|
|
1805
|
-
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, 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;
|
|
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;
|
|
1806
1905
|
var init_config = __esm({
|
|
1807
1906
|
"src/config.ts"() {
|
|
1808
1907
|
"use strict";
|
|
@@ -1873,6 +1972,7 @@ var init_config = __esm({
|
|
|
1873
1972
|
TASKLESS_TASK_URL_BASE = process.env.TASKLESS_TASK_URL_BASE || (TASKLESS_WORKSPACE_SLUG ? `${TASKLESS_API.replace(/\/$/, "")}/app/${TASKLESS_WORKSPACE_SLUG}` : "");
|
|
1874
1973
|
SWARM_VERIFY = process.env.SWARM_VERIFY === "ci" ? "ci" : "local";
|
|
1875
1974
|
SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
|
|
1975
|
+
SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
|
|
1876
1976
|
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join4(SWARM_HOME, "review-cutoff.json");
|
|
1877
1977
|
SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
|
|
1878
1978
|
SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
|
|
@@ -1997,65 +2097,13 @@ var init_repoRegistry = __esm({
|
|
|
1997
2097
|
}
|
|
1998
2098
|
});
|
|
1999
2099
|
|
|
2000
|
-
// src/repoScan.ts
|
|
2001
|
-
import { readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
|
|
2002
|
-
import { join as join6 } from "path";
|
|
2003
|
-
var init_repoScan = __esm({
|
|
2004
|
-
"src/repoScan.ts"() {
|
|
2005
|
-
"use strict";
|
|
2006
|
-
init_repoRegistry();
|
|
2007
|
-
}
|
|
2008
|
-
});
|
|
2009
|
-
|
|
2010
|
-
// src/repoDiscovery.ts
|
|
2011
|
-
import { homedir as homedir3 } from "os";
|
|
2012
|
-
import { readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2013
|
-
import { isAbsolute, join as join7, resolve as resolve2 } from "path";
|
|
2014
|
-
function repoMapLines(rows) {
|
|
2015
|
-
if (!rows?.length) return [];
|
|
2016
|
-
const missing = rows.filter((r) => !r.path).length;
|
|
2017
|
-
const head = `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F: ${rows.length}` + (missing ? `, \u0431\u0435\u0437 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${missing}` : "");
|
|
2018
|
-
const body = rows.map((r) => {
|
|
2019
|
-
if (!r.path) return ` \u2717 ${r.repoFullName} \u2014 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u041D\u0415\u0422 (\u0441\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u044E)`;
|
|
2020
|
-
const how = r.source ? ` (${SOURCE_TEXT[r.source]})` : "";
|
|
2021
|
-
const alt = r.alternatives.length ? ` \u26A0 \u0435\u0441\u0442\u044C \u0435\u0449\u0451 ${r.alternatives.length}: ${r.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C` : "";
|
|
2022
|
-
return ` ${r.alternatives.length ? "\u26A0" : "\u2713"} ${r.repoFullName} \u2192 ${r.path}${how}${alt}`;
|
|
2023
|
-
});
|
|
2024
|
-
return [head, ...body];
|
|
2025
|
-
}
|
|
2026
|
-
function readRepoMapCache(path) {
|
|
2027
|
-
try {
|
|
2028
|
-
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
2029
|
-
if (!Array.isArray(raw?.entries)) return [];
|
|
2030
|
-
return raw.entries.filter(
|
|
2031
|
-
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
2032
|
-
);
|
|
2033
|
-
} catch {
|
|
2034
|
-
return [];
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
var SOURCE_TEXT;
|
|
2038
|
-
var init_repoDiscovery = __esm({
|
|
2039
|
-
"src/repoDiscovery.ts"() {
|
|
2040
|
-
"use strict";
|
|
2041
|
-
init_src();
|
|
2042
|
-
init_config();
|
|
2043
|
-
init_repoScan();
|
|
2044
|
-
SOURCE_TEXT = {
|
|
2045
|
-
configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
2046
|
-
cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
|
|
2047
|
-
scan: "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C"
|
|
2048
|
-
};
|
|
2049
|
-
}
|
|
2050
|
-
});
|
|
2051
|
-
|
|
2052
2100
|
// package.json
|
|
2053
2101
|
var package_default;
|
|
2054
2102
|
var init_package = __esm({
|
|
2055
2103
|
"package.json"() {
|
|
2056
2104
|
package_default = {
|
|
2057
2105
|
name: "@taskless-app/swarm",
|
|
2058
|
-
version: "0.6.
|
|
2106
|
+
version: "0.6.12",
|
|
2059
2107
|
type: "module",
|
|
2060
2108
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2061
2109
|
keywords: [
|
|
@@ -2105,7 +2153,7 @@ var init_package = __esm({
|
|
|
2105
2153
|
execa: "^10.0.0",
|
|
2106
2154
|
zod: "3.25.76"
|
|
2107
2155
|
},
|
|
2108
|
-
gitHead: "
|
|
2156
|
+
gitHead: "43c844efdbea61a27c54edc17a111d658ce6e2b4"
|
|
2109
2157
|
};
|
|
2110
2158
|
}
|
|
2111
2159
|
});
|
|
@@ -2123,18 +2171,18 @@ var init_version = __esm({
|
|
|
2123
2171
|
|
|
2124
2172
|
// src/droneChannel.ts
|
|
2125
2173
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
2126
|
-
import { chmodSync, mkdirSync as mkdirSync5, readFileSync as
|
|
2174
|
+
import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2127
2175
|
import { dirname as dirname2 } from "path";
|
|
2128
2176
|
import { serve } from "@hono/node-server";
|
|
2129
2177
|
function loadChannelSecret(path) {
|
|
2130
2178
|
try {
|
|
2131
|
-
const s =
|
|
2179
|
+
const s = readFileSync6(path, "utf8").trim();
|
|
2132
2180
|
if (s.length >= 32) return s;
|
|
2133
2181
|
} catch {
|
|
2134
2182
|
}
|
|
2135
2183
|
const secret = randomBytes(32).toString("hex");
|
|
2136
2184
|
mkdirSync5(dirname2(path), { recursive: true });
|
|
2137
|
-
|
|
2185
|
+
writeFileSync5(path, secret, { mode: 384 });
|
|
2138
2186
|
try {
|
|
2139
2187
|
chmodSync(path, 384);
|
|
2140
2188
|
} catch {
|
|
@@ -2174,30 +2222,34 @@ var init_droneChannel = __esm({
|
|
|
2174
2222
|
});
|
|
2175
2223
|
|
|
2176
2224
|
// src/agentToken.ts
|
|
2177
|
-
import { readFileSync as
|
|
2178
|
-
import { join as
|
|
2225
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
2226
|
+
import { join as join8 } from "path";
|
|
2179
2227
|
function loadAgentToken() {
|
|
2180
2228
|
try {
|
|
2181
|
-
const s = JSON.parse(
|
|
2229
|
+
const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
|
|
2182
2230
|
return s.token ? s : null;
|
|
2183
2231
|
} catch {
|
|
2184
2232
|
return null;
|
|
2185
2233
|
}
|
|
2186
2234
|
}
|
|
2187
2235
|
function saveAgentToken(s) {
|
|
2188
|
-
|
|
2236
|
+
writeFileSync6(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
|
|
2189
2237
|
}
|
|
2190
2238
|
function clearAgentToken() {
|
|
2191
2239
|
try {
|
|
2192
|
-
|
|
2240
|
+
unlinkSync2(STORE_PATH);
|
|
2193
2241
|
} catch {
|
|
2194
2242
|
}
|
|
2195
2243
|
}
|
|
2196
2244
|
async function mintAgentToken(args, fetchFn = fetch) {
|
|
2197
|
-
const res = await fetchFn(`${
|
|
2245
|
+
const res = await fetchFn(`${base(args.api)}/api/swarm/agent/token`, {
|
|
2198
2246
|
method: "POST",
|
|
2199
2247
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${args.bearer}` },
|
|
2200
|
-
body: JSON.stringify(
|
|
2248
|
+
body: JSON.stringify({
|
|
2249
|
+
...args.label ? { label: args.label } : {},
|
|
2250
|
+
...args.hostId ? { hostId: args.hostId } : {},
|
|
2251
|
+
...args.hostName ? { hostName: args.hostName } : {}
|
|
2252
|
+
})
|
|
2201
2253
|
});
|
|
2202
2254
|
const text = await res.text();
|
|
2203
2255
|
if (!res.ok) throw new Error(`agent token: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
@@ -2207,8 +2259,11 @@ async function mintAgentToken(args, fetchFn = fetch) {
|
|
|
2207
2259
|
}
|
|
2208
2260
|
async function probeAgentToken(args, fetchFn = fetch) {
|
|
2209
2261
|
try {
|
|
2210
|
-
const res = await fetchFn(`${
|
|
2211
|
-
headers: { Authorization: `Bearer ${args.token}` }
|
|
2262
|
+
const res = await fetchFn(`${base(args.api)}/api/auth/me`, {
|
|
2263
|
+
headers: { Authorization: `Bearer ${args.token}` },
|
|
2264
|
+
// Оборвётся по таймауту — попадём в catch ниже, то есть в "unreachable": это ровно
|
|
2265
|
+
// то, чем висящая сеть и является. Соседи по CLI (daemonLink) живут так же.
|
|
2266
|
+
signal: AbortSignal.timeout(args.timeoutMs ?? TOKEN_PROBE_TIMEOUT_MS)
|
|
2212
2267
|
});
|
|
2213
2268
|
if (res.status === 401 || res.status === 403) return "revoked";
|
|
2214
2269
|
return res.ok ? "ok" : "unreachable";
|
|
@@ -2224,13 +2279,69 @@ function isRevokedTokenError(e) {
|
|
|
2224
2279
|
function revokedTokenLine() {
|
|
2225
2280
|
return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
|
|
2226
2281
|
}
|
|
2227
|
-
|
|
2282
|
+
function revokedTokenComplaint() {
|
|
2283
|
+
return [
|
|
2284
|
+
"",
|
|
2285
|
+
"\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
2286
|
+
"\u2502 \u0422\u041E\u041A\u0415\u041D \u0420\u041E\u042F \u041D\u0415 \u041F\u0420\u0418\u041D\u042F\u0422 (401). \u0414\u043E\u0441\u0442\u0443\u043F \u043A Taskless \u0440\u0430\u0437\u043E\u0440\u0432\u0430\u043D. \u2502",
|
|
2287
|
+
"\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
|
|
2288
|
+
"\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u2014 \u043E\u0434\u043D\u0430 \u0438\u0437 \u0442\u0440\u0451\u0445, \u0432\u0441\u0435 \u0442\u0440\u0438 \u0440\u0435\u0448\u0430\u044E\u0442\u0441\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C:",
|
|
2289
|
+
" \u2022 \xAB\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\xBB \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0442\u043E\u043A\u0435\u043D \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u044B;",
|
|
2290
|
+
" \u2022 \xAB\u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u0440\u043E\u044F\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439) \u2014 \u043E\u0442\u043E\u0437\u0432\u0430\u043D\u044B \u0432\u0441\u0435;",
|
|
2291
|
+
" \u2022 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C \u2014 \u0442\u043E\u043A\u0435\u043D \u0430\u0433\u0435\u043D\u0442\u0430 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0451\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u043E\u0432\u0430\u0442\u044C.",
|
|
2292
|
+
"",
|
|
2293
|
+
"\u0412\u0445\u043E\u0434 \u043D\u0430 \u0414\u0420\u0423\u0413\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435 \u044D\u0442\u043E\u0442 \u0442\u043E\u043A\u0435\u043D \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0433\u0430\u0441\u0438\u0442 (TASWM-324): \u0443 \u043A\u0430\u0436\u0434\u043E\u0439",
|
|
2294
|
+
"\u043C\u0430\u0448\u0438\u043D\u044B \u0441\u0432\u043E\u0439 \u0442\u043E\u043A\u0435\u043D, \u043F\u0440\u0438\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 \u043A \u0435\u0451 hostId.",
|
|
2295
|
+
"",
|
|
2296
|
+
"\u0414\u0435\u043C\u043E\u043D \u041D\u0415 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E: \u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442, \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0438 \u043E\u0442\u0447\u0451\u0442\u044B",
|
|
2297
|
+
"\u043D\u0435 \u043F\u0438\u0448\u0443\u0442\u0441\u044F. \u041F\u043E\u0447\u0438\u043D\u0438 \u0432\u0445\u043E\u0434\u043E\u043C \u0437\u0430\u043D\u043E\u0432\u043E: swarm login",
|
|
2298
|
+
"\u041F\u0440\u043E\u0431\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442, \u0430 \u0432\u043E\u0439\u0442\u0438 \u043C\u0435\u0448\u0430\u0435\u0442 \u0441\u0442\u0430\u0440\u044B\u0439 \u0444\u0430\u0439\u043B: swarm login --force",
|
|
2299
|
+
""
|
|
2300
|
+
].join("\n");
|
|
2301
|
+
}
|
|
2302
|
+
var STORE_PATH, base, TOKEN_PROBE_TIMEOUT_MS;
|
|
2228
2303
|
var init_agentToken = __esm({
|
|
2229
2304
|
"src/agentToken.ts"() {
|
|
2230
2305
|
"use strict";
|
|
2231
2306
|
init_config();
|
|
2232
|
-
|
|
2233
|
-
|
|
2307
|
+
STORE_PATH = join8(SWARM_HOME, "agent.json");
|
|
2308
|
+
base = (api) => api.replace(/\/$/, "");
|
|
2309
|
+
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
// src/linkFailure.ts
|
|
2314
|
+
function describeError(e, maxDepth = 4) {
|
|
2315
|
+
const parts = [];
|
|
2316
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2317
|
+
let cur = e;
|
|
2318
|
+
for (let depth = 0; depth < maxDepth && cur != null && !seen.has(cur); depth++) {
|
|
2319
|
+
seen.add(cur);
|
|
2320
|
+
const msg = (cur instanceof Error ? cur.message : typeof cur === "string" ? cur : String(cur)).trim();
|
|
2321
|
+
const code = cur.code;
|
|
2322
|
+
const codeText = code === void 0 || code === null ? "" : String(code);
|
|
2323
|
+
const label = codeText && !msg.includes(codeText) ? `${msg} [code=${codeText}]` : msg;
|
|
2324
|
+
if (label && !parts.includes(label)) parts.push(label);
|
|
2325
|
+
cur = cur.cause;
|
|
2326
|
+
}
|
|
2327
|
+
return parts.join(" \u2190 ") || String(e);
|
|
2328
|
+
}
|
|
2329
|
+
function explainLinkFailure(e) {
|
|
2330
|
+
const detail = describeError(e);
|
|
2331
|
+
if (detail.includes(UNKNOWN_LINK_REASON)) return { kind: "unknown", detail };
|
|
2332
|
+
if (isRevokedTokenError(e) || isRevokedTokenError(detail)) return { kind: "unauthorized", detail };
|
|
2333
|
+
if (MISSING_TOOL_RE.test(detail)) return { kind: "missing_tool", detail };
|
|
2334
|
+
if (OFFLINE_RE.test(detail)) return { kind: "offline", detail };
|
|
2335
|
+
return { kind: "rejected", detail };
|
|
2336
|
+
}
|
|
2337
|
+
var UNKNOWN_LINK_REASON, OFFLINE_RE, MISSING_TOOL_RE;
|
|
2338
|
+
var init_linkFailure = __esm({
|
|
2339
|
+
"src/linkFailure.ts"() {
|
|
2340
|
+
"use strict";
|
|
2341
|
+
init_agentToken();
|
|
2342
|
+
UNKNOWN_LINK_REASON = "\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B";
|
|
2343
|
+
OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout/i;
|
|
2344
|
+
MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
|
|
2234
2345
|
}
|
|
2235
2346
|
});
|
|
2236
2347
|
|
|
@@ -2443,6 +2554,90 @@ var init_daemonLink = __esm({
|
|
|
2443
2554
|
}
|
|
2444
2555
|
});
|
|
2445
2556
|
|
|
2557
|
+
// src/pauseState.ts
|
|
2558
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
2559
|
+
import { join as join16 } from "path";
|
|
2560
|
+
function pauseStatePath(home) {
|
|
2561
|
+
return join16(home, "pause.json");
|
|
2562
|
+
}
|
|
2563
|
+
function loadPauseState(home, io = {}) {
|
|
2564
|
+
const exists = io.exists ?? existsSync9;
|
|
2565
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
|
|
2566
|
+
const path = pauseStatePath(home);
|
|
2567
|
+
if (!exists(path)) return ACTIVE;
|
|
2568
|
+
try {
|
|
2569
|
+
const parsed = JSON.parse(readFile2(path));
|
|
2570
|
+
if (parsed.paused !== true) return ACTIVE;
|
|
2571
|
+
return {
|
|
2572
|
+
paused: true,
|
|
2573
|
+
at: typeof parsed.at === "string" ? parsed.at : "",
|
|
2574
|
+
source: parsed.source === "web" || parsed.source === "remote" ? parsed.source : "cli",
|
|
2575
|
+
...typeof parsed.reason === "string" && parsed.reason.trim() ? { reason: parsed.reason.trim() } : {}
|
|
2576
|
+
};
|
|
2577
|
+
} catch {
|
|
2578
|
+
return ACTIVE;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
function savePauseState(home, state, io = {}) {
|
|
2582
|
+
const path = pauseStatePath(home);
|
|
2583
|
+
if (!state.paused) {
|
|
2584
|
+
const remove = io.remove ?? ((p) => existsSync9(p) && unlinkSync5(p));
|
|
2585
|
+
remove(path);
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
2589
|
+
writeFileSync9(`${p}.tmp`, data);
|
|
2590
|
+
renameSync4(`${p}.tmp`, p);
|
|
2591
|
+
});
|
|
2592
|
+
writeFile3(path, JSON.stringify(state, null, 2));
|
|
2593
|
+
}
|
|
2594
|
+
function makePauseState(source, reason, now = Date.now()) {
|
|
2595
|
+
return {
|
|
2596
|
+
paused: true,
|
|
2597
|
+
at: new Date(now).toISOString(),
|
|
2598
|
+
source,
|
|
2599
|
+
...reason && reason.trim() ? { reason: reason.trim() } : {}
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
function pauseText(state, now = Date.now()) {
|
|
2603
|
+
if (!state?.paused) return "\u0440\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u0435\u043D \u2014 \u0431\u0435\u0440\u0451\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443";
|
|
2604
|
+
const since = state.at ? sinceText(state.at, now) : "";
|
|
2605
|
+
const who = { cli: "\u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435", web: "\u0441 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0439 \u043C\u043E\u0440\u0434\u044B", remote: "\u0438\u0437 Taskless" }[state.source];
|
|
2606
|
+
const why = state.reason ? ` \u2014 ${state.reason}` : "";
|
|
2607
|
+
return `\u0440\u043E\u0439 \u041D\u0410 \u041F\u0410\u0423\u0417\u0415${since} (${who})${why}`;
|
|
2608
|
+
}
|
|
2609
|
+
function sinceText(at, now) {
|
|
2610
|
+
const ms = Date.parse(at);
|
|
2611
|
+
if (Number.isNaN(ms)) return "";
|
|
2612
|
+
const min = Math.max(0, Math.round((now - ms) / 6e4));
|
|
2613
|
+
if (min < 1) return " (\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E)";
|
|
2614
|
+
if (min < 60) return ` (${min} \u043C\u0438\u043D)`;
|
|
2615
|
+
return ` (${Math.round(min / 60)} \u0447)`;
|
|
2616
|
+
}
|
|
2617
|
+
var ACTIVE;
|
|
2618
|
+
var init_pauseState = __esm({
|
|
2619
|
+
"src/pauseState.ts"() {
|
|
2620
|
+
"use strict";
|
|
2621
|
+
ACTIVE = { paused: false, at: "", source: "cli" };
|
|
2622
|
+
}
|
|
2623
|
+
});
|
|
2624
|
+
|
|
2625
|
+
// src/heartbeat.ts
|
|
2626
|
+
import { readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
2627
|
+
function readHeartbeat(path) {
|
|
2628
|
+
try {
|
|
2629
|
+
const ms = Number(readFileSync11(path, "utf8").trim());
|
|
2630
|
+
return Number.isFinite(ms) ? ms : null;
|
|
2631
|
+
} catch {
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
var init_heartbeat = __esm({
|
|
2636
|
+
"src/heartbeat.ts"() {
|
|
2637
|
+
"use strict";
|
|
2638
|
+
}
|
|
2639
|
+
});
|
|
2640
|
+
|
|
2446
2641
|
// src/db.ts
|
|
2447
2642
|
var db_exports = {};
|
|
2448
2643
|
__export(db_exports, {
|
|
@@ -2450,7 +2645,7 @@ __export(db_exports, {
|
|
|
2450
2645
|
});
|
|
2451
2646
|
import { createRequire as createRequire2 } from "module";
|
|
2452
2647
|
import { existsSync as existsSync10 } from "fs";
|
|
2453
|
-
import { resolve as
|
|
2648
|
+
import { resolve as resolve2 } from "path";
|
|
2454
2649
|
function rowToQuestion(row) {
|
|
2455
2650
|
return {
|
|
2456
2651
|
id: row.id,
|
|
@@ -2500,6 +2695,10 @@ function rowToRun(row) {
|
|
|
2500
2695
|
resolves_run_id: row.resolves_run_id ?? null,
|
|
2501
2696
|
reviews_run_id: row.reviews_run_id ?? null,
|
|
2502
2697
|
review_verdict: row.review_verdict ?? null,
|
|
2698
|
+
verifies_run_id: row.verifies_run_id ?? null,
|
|
2699
|
+
// TASWM-344: через узкий парсер, а не сырой строкой — в зеркале может лежать значение,
|
|
2700
|
+
// записанное демоном другой версии; непонятное здесь значит «вердикта нет».
|
|
2701
|
+
verify_verdict: toVerifyVerdict(row.verify_verdict),
|
|
2503
2702
|
review_phase: row.review_phase ?? null,
|
|
2504
2703
|
// вычисляемая фаза ревью (TASWM-230)
|
|
2505
2704
|
review_cycle: row.review_cycle ?? 0,
|
|
@@ -2568,6 +2767,8 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
2568
2767
|
resolves_run_id INTEGER,
|
|
2569
2768
|
reviews_run_id INTEGER,
|
|
2570
2769
|
review_verdict TEXT,
|
|
2770
|
+
verifies_run_id INTEGER,
|
|
2771
|
+
verify_verdict TEXT,
|
|
2571
2772
|
review_cycle INTEGER NOT NULL DEFAULT 0,
|
|
2572
2773
|
task_title TEXT,
|
|
2573
2774
|
task_desc TEXT,
|
|
@@ -2644,6 +2845,12 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2644
2845
|
// вердикт (approve|changes), который ревьюер пишет на проверяемый прогон.
|
|
2645
2846
|
"ALTER TABLE runs ADD COLUMN reviews_run_id INTEGER",
|
|
2646
2847
|
"ALTER TABLE runs ADD COLUMN review_verdict TEXT",
|
|
2848
|
+
// Верификатор (TASWM-344): прогон, который пытается ОПРОВЕРГНУТЬ работу
|
|
2849
|
+
// #verifies_run_id, и его вердикт (confirmed|not_reproduced|found_issues) на
|
|
2850
|
+
// проверяемом прогоне. Своя пара колонок, а не переиспользование reviews_*:
|
|
2851
|
+
// ревью и верификация идут на одном PR одновременно и считаются раздельно.
|
|
2852
|
+
"ALTER TABLE runs ADD COLUMN verifies_run_id INTEGER",
|
|
2853
|
+
"ALTER TABLE runs ADD COLUMN verify_verdict TEXT",
|
|
2647
2854
|
// Авто-ревью v2 (TASWM-162): номер цикла ревью — пере-ревью после фикс-итерации.
|
|
2648
2855
|
"ALTER TABLE runs ADD COLUMN review_cycle INTEGER NOT NULL DEFAULT 0",
|
|
2649
2856
|
// Вычисляемая фаза ревью (TASWM-230): человеко-фраза «чего ждёт PR», её считает демон.
|
|
@@ -2680,7 +2887,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2680
2887
|
createRun(input) {
|
|
2681
2888
|
const name = input.name ?? pickName(this.db.prepare("SELECT id, name, status FROM runs").all());
|
|
2682
2889
|
const res = this.db.prepare(
|
|
2683
|
-
"INSERT INTO runs (task_slug, name, kind, hero_run_id, repo, area, depends_on, model, effort, decision, resolves_run_id, reviews_run_id, review_cycle, priority, base_branch, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2684
2891
|
).run(
|
|
2685
2892
|
input.task_slug,
|
|
2686
2893
|
name,
|
|
@@ -2698,6 +2905,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2698
2905
|
input.decision ? JSON.stringify(input.decision) : null,
|
|
2699
2906
|
input.resolves_run_id ?? null,
|
|
2700
2907
|
input.reviews_run_id ?? null,
|
|
2908
|
+
input.verifies_run_id ?? null,
|
|
2909
|
+
// прогон-верификатор (TASWM-344); обычный прогон — null
|
|
2701
2910
|
input.review_cycle ?? 0,
|
|
2702
2911
|
// цикл ревью для ревьюера (авто-ревью v2, TASWM-162); обычный прогон — 0
|
|
2703
2912
|
input.priority ?? 0,
|
|
@@ -2708,7 +2917,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2708
2917
|
// воркспейс задачи прогона (TASWM-288); null → глобальный пин
|
|
2709
2918
|
);
|
|
2710
2919
|
const id = Number(res.lastInsertRowid);
|
|
2711
|
-
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
2920
|
+
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
2712
2921
|
this.addEvent(id, "spawned", spawnNote);
|
|
2713
2922
|
return this.getRun(id);
|
|
2714
2923
|
}
|
|
@@ -2734,13 +2943,14 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2734
2943
|
*/
|
|
2735
2944
|
ensureRun(input) {
|
|
2736
2945
|
this.db.prepare(
|
|
2737
|
-
"INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?)"
|
|
2946
|
+
"INSERT OR IGNORE INTO runs (id, task_slug, kind, status, name, reviews_run_id, verifies_run_id, hero_run_id) VALUES (?, ?, ?, 'running', ?, ?, ?, ?)"
|
|
2738
2947
|
).run(
|
|
2739
2948
|
input.id,
|
|
2740
2949
|
input.task_slug,
|
|
2741
2950
|
input.kind ?? "drone",
|
|
2742
2951
|
input.name || "(\u0438\u043C\u044F \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)",
|
|
2743
2952
|
input.reviews_run_id ?? null,
|
|
2953
|
+
input.verifies_run_id ?? null,
|
|
2744
2954
|
input.hero_run_id ?? null
|
|
2745
2955
|
);
|
|
2746
2956
|
}
|
|
@@ -2885,6 +3095,31 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2885
3095
|
const rows = this.db.prepare(`SELECT * FROM runs WHERE reviews_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(reviewRunId);
|
|
2886
3096
|
return rows.map(rowToRun);
|
|
2887
3097
|
}
|
|
3098
|
+
/**
|
|
3099
|
+
* ВЕРИФИКАЦИЯ (TASWM-344) — те же три счётчика, что у ревью выше, но по своей связи
|
|
3100
|
+
* `verifies_run_id` и со своим капом. Скоуп «текущий цикл ревью» тот же и по той же
|
|
3101
|
+
* причине: фикс-итерация бампит review_cycle и обнуляет вердикты (reviewTransitionPatch),
|
|
3102
|
+
* значит исправленную работу обязан проверить СВЕЖИЙ верификатор, а не считаться уже
|
|
3103
|
+
* полученным подтверждением по коду, которого больше нет.
|
|
3104
|
+
*/
|
|
3105
|
+
verifierCountFor(verifyRunId) {
|
|
3106
|
+
const row = this.db.prepare(
|
|
3107
|
+
"SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status NOT IN ('failed','stalled','stopped','superseded') AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
|
|
3108
|
+
).get(verifyRunId, verifyRunId);
|
|
3109
|
+
return row.n;
|
|
3110
|
+
}
|
|
3111
|
+
/** Плоский кап попыток верификации на цикл — любой статус, кроме сброшенных оператором. */
|
|
3112
|
+
verifierAttemptCountFor(verifyRunId) {
|
|
3113
|
+
const row = this.db.prepare(
|
|
3114
|
+
"SELECT COUNT(*) n FROM runs WHERE verifies_run_id = ? AND status != 'superseded' AND review_cycle = COALESCE((SELECT review_cycle FROM runs WHERE id = ?), 0)"
|
|
3115
|
+
).get(verifyRunId, verifyRunId);
|
|
3116
|
+
return row.n;
|
|
3117
|
+
}
|
|
3118
|
+
/** Нетерминальные верификаторы прогона — закрыть зависших, когда PR уже смержен/закрыт. */
|
|
3119
|
+
activeVerifiersFor(verifyRunId) {
|
|
3120
|
+
const rows = this.db.prepare(`SELECT * FROM runs WHERE verifies_run_id = ? AND status NOT IN (${TERMINAL_SQL}) ORDER BY id DESC`).all(verifyRunId);
|
|
3121
|
+
return rows.map(rowToRun);
|
|
3122
|
+
}
|
|
2888
3123
|
/**
|
|
2889
3124
|
* Последний живой (не терминальный) прогон по slug — для swarm cancel. Служебные
|
|
2890
3125
|
* сабраны (reviews_run_id/resolves_run_id — ревьюер/резолвер, TASWM-273) исключены:
|
|
@@ -2899,7 +3134,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2899
3134
|
*/
|
|
2900
3135
|
activeRunForSlug(slug) {
|
|
2901
3136
|
const row = this.db.prepare(
|
|
2902
|
-
`SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL ORDER BY id DESC LIMIT 1`
|
|
3137
|
+
`SELECT * FROM runs WHERE task_slug = ? AND status NOT IN (${TERMINAL_SQL}) AND reviews_run_id IS NULL AND resolves_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id DESC LIMIT 1`
|
|
2903
3138
|
).get(slug);
|
|
2904
3139
|
return row ? rowToRun(row) : null;
|
|
2905
3140
|
}
|
|
@@ -2939,7 +3174,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2939
3174
|
if (!depends_on || depends_on.length === 0) return [];
|
|
2940
3175
|
const slugs = [...new Set(depends_on)];
|
|
2941
3176
|
const rows = this.db.prepare(
|
|
2942
|
-
`SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL ORDER BY id`
|
|
3177
|
+
`SELECT task_slug, status, name, stop_reason FROM runs WHERE task_slug IN (${slugs.map(() => "?").join(",")}) AND resolves_run_id IS NULL AND reviews_run_id IS NULL AND verifies_run_id IS NULL ORDER BY id`
|
|
2943
3178
|
).all(...slugs);
|
|
2944
3179
|
const bySlug = /* @__PURE__ */ new Map();
|
|
2945
3180
|
for (const r of rows) {
|
|
@@ -2973,7 +3208,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2973
3208
|
* транзакции: прервётся — маркер не встанет, повтор безопасен.
|
|
2974
3209
|
*/
|
|
2975
3210
|
importDb(sourcePath, repo) {
|
|
2976
|
-
const abs =
|
|
3211
|
+
const abs = resolve2(sourcePath);
|
|
2977
3212
|
const repoN = normalizeRepo(repo);
|
|
2978
3213
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
2979
3214
|
if (already) return { imported: false, runs: already.run_count };
|
|
@@ -3303,24 +3538,554 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3303
3538
|
}
|
|
3304
3539
|
});
|
|
3305
3540
|
|
|
3306
|
-
// src/
|
|
3541
|
+
// src/repoScan.ts
|
|
3542
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
3543
|
+
import { join as join17 } from "path";
|
|
3544
|
+
var init_repoScan = __esm({
|
|
3545
|
+
"src/repoScan.ts"() {
|
|
3546
|
+
"use strict";
|
|
3547
|
+
init_repoRegistry();
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
|
|
3551
|
+
// src/repoDiscovery.ts
|
|
3552
|
+
import { homedir as homedir4 } from "os";
|
|
3553
|
+
import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
3554
|
+
import { isAbsolute as isAbsolute3, join as join18, resolve as resolve3 } from "path";
|
|
3555
|
+
function repoMapLines(rows) {
|
|
3556
|
+
if (!rows?.length) return [];
|
|
3557
|
+
const missing = rows.filter((r) => !r.path).length;
|
|
3558
|
+
const head = `\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F: ${rows.length}` + (missing ? `, \u0431\u0435\u0437 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${missing}` : "");
|
|
3559
|
+
const body = rows.map((r) => {
|
|
3560
|
+
if (!r.path) return ` \u2717 ${r.repoFullName} \u2014 \u043A\u043B\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u041D\u0415\u0422 (\u0441\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u044E)`;
|
|
3561
|
+
const how = r.source ? ` (${SOURCE_TEXT[r.source]})` : "";
|
|
3562
|
+
const alt = r.alternatives.length ? ` \u26A0 \u0435\u0441\u0442\u044C \u0435\u0449\u0451 ${r.alternatives.length}: ${r.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C` : "";
|
|
3563
|
+
return ` ${r.alternatives.length ? "\u26A0" : "\u2713"} ${r.repoFullName} \u2192 ${r.path}${how}${alt}`;
|
|
3564
|
+
});
|
|
3565
|
+
return [head, ...body];
|
|
3566
|
+
}
|
|
3567
|
+
function readRepoMapCache(path) {
|
|
3568
|
+
try {
|
|
3569
|
+
const raw = JSON.parse(readFileSync13(path, "utf8"));
|
|
3570
|
+
if (!Array.isArray(raw?.entries)) return [];
|
|
3571
|
+
return raw.entries.filter(
|
|
3572
|
+
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
3573
|
+
);
|
|
3574
|
+
} catch {
|
|
3575
|
+
return [];
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
var SOURCE_TEXT;
|
|
3579
|
+
var init_repoDiscovery = __esm({
|
|
3580
|
+
"src/repoDiscovery.ts"() {
|
|
3581
|
+
"use strict";
|
|
3582
|
+
init_src();
|
|
3583
|
+
init_config();
|
|
3584
|
+
init_linkFailure();
|
|
3585
|
+
init_repoScan();
|
|
3586
|
+
SOURCE_TEXT = {
|
|
3587
|
+
configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
3588
|
+
cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
|
|
3589
|
+
scan: "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C"
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
|
|
3594
|
+
// src/release.ts
|
|
3595
|
+
async function fetchLatestVersion(opts = {}) {
|
|
3596
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
3597
|
+
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
3598
|
+
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
3599
|
+
const ctrl = new AbortController();
|
|
3600
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
3601
|
+
try {
|
|
3602
|
+
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
3603
|
+
signal: ctrl.signal,
|
|
3604
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
3605
|
+
});
|
|
3606
|
+
if (!res.ok) return null;
|
|
3607
|
+
const body = await res.json();
|
|
3608
|
+
return typeof body.version === "string" && body.version ? body.version : null;
|
|
3609
|
+
} catch {
|
|
3610
|
+
return null;
|
|
3611
|
+
} finally {
|
|
3612
|
+
clearTimeout(timer);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
function isOutdated(current, latest) {
|
|
3616
|
+
if (!latest || !current) return false;
|
|
3617
|
+
return compareVersionDirs(latest, current) > 0;
|
|
3618
|
+
}
|
|
3619
|
+
var SWARM_PACKAGE, LATEST_TIMEOUT_MS;
|
|
3620
|
+
var init_release = __esm({
|
|
3621
|
+
"src/release.ts"() {
|
|
3622
|
+
"use strict";
|
|
3623
|
+
init_src();
|
|
3624
|
+
init_claudeBin();
|
|
3625
|
+
SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
3626
|
+
LATEST_TIMEOUT_MS = 4e3;
|
|
3627
|
+
}
|
|
3628
|
+
});
|
|
3629
|
+
|
|
3630
|
+
// src/swarmStatus.ts
|
|
3631
|
+
function agoText(iso, now) {
|
|
3632
|
+
if (!iso) return null;
|
|
3633
|
+
const ms = Date.parse(iso);
|
|
3634
|
+
if (Number.isNaN(ms)) return null;
|
|
3635
|
+
const sec = Math.max(0, Math.round((now - ms) / 1e3));
|
|
3636
|
+
if (sec < 60) return "\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E";
|
|
3637
|
+
const min = Math.round(sec / 60);
|
|
3638
|
+
if (min < 60) return `${min} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434`;
|
|
3639
|
+
const hr = Math.round(min / 60);
|
|
3640
|
+
return hr < 24 ? `${hr} \u0447 \u043D\u0430\u0437\u0430\u0434` : `${Math.round(hr / 24)} \u0434\u043D \u043D\u0430\u0437\u0430\u0434`;
|
|
3641
|
+
}
|
|
3642
|
+
function uptimeText(startedAt, now) {
|
|
3643
|
+
if (!startedAt) return null;
|
|
3644
|
+
const ms = Date.parse(startedAt);
|
|
3645
|
+
if (Number.isNaN(ms)) return null;
|
|
3646
|
+
const min = Math.max(0, Math.floor((now - ms) / 6e4));
|
|
3647
|
+
if (min < 60) return `${min} \u043C\u0438\u043D`;
|
|
3648
|
+
const hr = Math.floor(min / 60);
|
|
3649
|
+
return `${hr} \u0447 ${min % 60} \u043C\u0438\u043D`;
|
|
3650
|
+
}
|
|
3651
|
+
function linkTrouble(link) {
|
|
3652
|
+
if (link.kind === "ok" || link.kind === "no-prod" || link.kind === "starting") return null;
|
|
3653
|
+
if (link.kind === "unmeasured") {
|
|
3654
|
+
return {
|
|
3655
|
+
id: "link-unmeasured",
|
|
3656
|
+
why: `\u043F\u0440\u043E \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u0441\u043A\u0430\u0437\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0434\u0435\u043C\u043E\u043D \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 \u043A\u043E\u0434\u0435 \u0421\u0422\u0410\u0420\u0415\u0415 \u044D\u0442\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B${link.detail ? ` (${link.detail})` : ""} \u2014 \u0442\u043E\u043A\u0435\u043D \u043C\u043E\u0433 \u0431\u044B\u0442\u044C \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442, \u0430 \u0441\u0435\u0442\u044C \u043B\u0435\u0436\u0430\u0442\u044C, \u0438 \u0441\u0442\u0430\u0442\u0443\u0441 \u044D\u0442\u043E\u0433\u043E \u043D\u0435 \u0443\u0432\u0438\u0434\u0438\u0442`,
|
|
3657
|
+
fixes: [
|
|
3658
|
+
{ what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u043C \u043A\u043E\u0434\u0435 \u2014 \u043E\u043D \u0441\u0430\u043C \u043D\u0430 \u043D\u0435\u0433\u043E \u043D\u0435 \u043F\u0435\u0440\u0435\u0435\u0434\u0435\u0442", command: "swarm stop && swarm start" },
|
|
3659
|
+
{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438 \u0442\u043E\u043A\u0435\u043D \u043D\u0430\u043F\u0440\u044F\u043C\u0443\u044E, \u043F\u043E\u043A\u0430 \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u044B\u0439", command: "swarm doctor" }
|
|
3660
|
+
]
|
|
3661
|
+
};
|
|
3662
|
+
}
|
|
3663
|
+
if (link.kind === "no-token") {
|
|
3664
|
+
return {
|
|
3665
|
+
id: "link-no-token",
|
|
3666
|
+
why: "\u0432\u0445\u043E\u0434 \u0432 Taskless \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u2014 \u0431\u0440\u0430\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435\u043E\u0442\u043A\u0443\u0434\u0430",
|
|
3667
|
+
fixes: [{ what: "\u0432\u043E\u0439\u0442\u0438 \u0432 \u0441\u0432\u043E\u0439 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441", command: "swarm login" }]
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
if (link.kind === "revoked") {
|
|
3671
|
+
return {
|
|
3672
|
+
id: "link-revoked",
|
|
3673
|
+
why: "Taskless \u041D\u0415 \u041F\u0420\u0418\u041D\u0418\u041C\u0410\u0415\u0422 \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F (401 invalid_token): \u043E\u043D \u0441\u0431\u0440\u043E\u0448\u0435\u043D \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C \u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D \u043C\u0430\u0441\u0442\u0435\u0440-\u0442\u0443\u043C\u0431\u043B\u0435\u0440\u043E\u043C",
|
|
3674
|
+
fixes: [{ what: "\u0432\u043E\u0439\u0442\u0438 \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u0442\u043E\u043A\u0435\u043D \u0432\u044B\u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043D\u043E\u0432\u044B\u0439", command: "swarm login" }]
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
if (link.kind === "server") {
|
|
3678
|
+
return {
|
|
3679
|
+
id: "link-server",
|
|
3680
|
+
why: `Taskless \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0448\u0438\u0431\u043A\u043E\u0439 (${link.detail}) \u2014 \u044D\u0442\u043E \u043D\u0435 \u043C\u0430\u0448\u0438\u043D\u0430 \u0438 \u043D\u0435 \u0442\u043E\u043A\u0435\u043D`,
|
|
3681
|
+
fixes: [
|
|
3682
|
+
{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0447\u0442\u043E \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441\u0430\u043C \u043F\u0440\u043E\u0434 (\u043E\u0448\u0438\u0431\u043A\u0430 5xx \u2014 \u043D\u0430 \u0435\u0433\u043E \u0441\u0442\u043E\u0440\u043E\u043D\u0435)", command: `curl -sS -o /dev/null -w '%{http_code}\\n' ${link.api}/api/health` },
|
|
3683
|
+
{ what: "\u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u0440\u0438 \u044D\u0442\u043E\u043C \u043D\u0435 \u0442\u0435\u0440\u044F\u0435\u0442\u0441\u044F: \u0431\u0443\u0444\u0435\u0440 \u0434\u043E\u0436\u043C\u0451\u0442\u0441\u044F \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u043E\u0434 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F", command: "swarm status # \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043C\u0438\u043D\u0443\u0442" }
|
|
3684
|
+
]
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
if (link.kind === "down") {
|
|
3688
|
+
return {
|
|
3689
|
+
id: "link-down",
|
|
3690
|
+
why: `\u0441\u0432\u044F\u0437\u0438 \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u043D\u0435\u0442${link.detail ? `: ${link.detail}` : ""} \u2014 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B, \u043E\u0442\u0441\u044E\u0434\u0430 \u0435\u0451 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u0442\u044C \u0442\u043E\u0436\u0435`,
|
|
3691
|
+
fixes: [
|
|
3692
|
+
{ what: "\u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u0441\u0430\u043C \u0434\u0435\u043C\u043E\u043D \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 \u0435\u0433\u043E \u043B\u043E\u0433", command: "swarm logs | tail -50" },
|
|
3693
|
+
{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \u0446\u0435\u043B\u0438\u043A\u043E\u043C (\u0441\u0435\u0442\u044C, \u0442\u043E\u043A\u0435\u043D, \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435)", command: "swarm doctor" }
|
|
3694
|
+
]
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
return {
|
|
3698
|
+
id: "link-network",
|
|
3699
|
+
why: `\u0434\u043E Taskless \u043D\u0435 \u0434\u043E\u0441\u0442\u0443\u0447\u0430\u0442\u044C\u0441\u044F (${link.detail}) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0432\u0438\u0434\u0438\u0442 \u043D\u0438 \u0437\u0430\u0434\u0430\u0447, \u043D\u0438 \u043C\u0430\u0448\u0438\u043D`,
|
|
3700
|
+
fixes: [
|
|
3701
|
+
{
|
|
3702
|
+
what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u0435\u0442\u044C \u0422\u0415\u041C \u0416\u0415 \u0441\u043F\u043E\u0441\u043E\u0431\u043E\u043C, \u043A\u0430\u043A\u0438\u043C \u0445\u043E\u0434\u0438\u0442 \u0434\u0435\u043C\u043E\u043D (\u0432 WSL curl \u043C\u043E\u0436\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C, \u043A\u043E\u0433\u0434\u0430 fetch \u0438\u0437 Node \u2014 \u043D\u0435\u0442: \u0434\u0440\u0443\u0433\u043E\u0439 \u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u0438 \u043F\u0440\u043E\u043A\u0441\u0438)",
|
|
3703
|
+
command: `node -e "fetch('${link.api}/api/health').then(r=>console.log('HTTP',r.status)).catch(e=>console.log('\u041D\u0415 \u0425\u041E\u0414\u0418\u0422:',e.message))"`
|
|
3704
|
+
},
|
|
3705
|
+
{ what: "\u0435\u0441\u043B\u0438 fetch \u043D\u0435 \u0445\u043E\u0434\u0438\u0442, \u0430 curl \u0445\u043E\u0434\u0438\u0442 \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 HTTPS_PROXY/NO_PROXY \u0438 DNS \u0432 \u044D\u0442\u043E\u043C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0438", command: "env | grep -i proxy; cat /etc/resolv.conf" }
|
|
3706
|
+
]
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
function waitingForDrone(work) {
|
|
3710
|
+
return work.queuedReady ?? work.queued;
|
|
3711
|
+
}
|
|
3712
|
+
function blockedTroubles(f) {
|
|
3713
|
+
const out = [];
|
|
3714
|
+
const { work, daemon } = f;
|
|
3715
|
+
if (daemon.restartWaiting) {
|
|
3716
|
+
out.push({
|
|
3717
|
+
id: "restart-waiting",
|
|
3718
|
+
why: "\u0434\u0435\u043C\u043E\u043D \u0436\u0434\u0451\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439, \u0447\u0442\u043E\u0431\u044B \u043F\u043E\u0433\u0430\u0441\u043D\u0443\u0442\u044C/\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\u0441\u044F \u2014 \u043D\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D",
|
|
3719
|
+
fixes: [{ what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u043A\u043E\u0433\u043E \u043E\u043D \u0436\u0434\u0451\u0442", command: "swarm ls" }]
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
if (work.storm?.reason === "limit") {
|
|
3723
|
+
out.push({
|
|
3724
|
+
id: "storm-limit",
|
|
3725
|
+
why: `\u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F${work.storm.hint ? ` (${work.storm.hint})` : ""} \u2014 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u0436\u0438\u0432, \u043D\u043E \u0434\u0440\u043E\u043D\u044B \u043D\u0435 \u043F\u043E\u0435\u0434\u0443\u0442 \u0434\u043E \u0440\u0435\u0437\u0435\u0442\u0430`,
|
|
3726
|
+
fixes: [{ what: "\u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0440\u0435\u0437\u0435\u0442\u0430 \u043B\u0438\u043C\u0438\u0442\u0430; \u0440\u0430\u0431\u043E\u0442\u0430 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043D\u0438\u043A\u0443\u0434\u0430 \u043D\u0435 \u0434\u0435\u043D\u0435\u0442\u0441\u044F", command: "swarm status # \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0440\u0435\u0437\u0435\u0442\u0430" }]
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
if (work.storm?.reason === "provider") {
|
|
3730
|
+
out.push({
|
|
3731
|
+
id: "storm-provider",
|
|
3732
|
+
why: "\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u0448\u0442\u043E\u0440\u043C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0434\u0440\u043E\u043D\u043E\u0432, \u043F\u043E\u043A\u0430 \u043E\u043D \u043B\u0435\u0436\u0438\u0442",
|
|
3733
|
+
fixes: [{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F \u0446\u0435\u043B\u0438\u043A\u043E\u043C", command: "swarm doctor" }]
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
if (work.maxDrones != null && work.maxDrones <= 0) {
|
|
3737
|
+
out.push({
|
|
3738
|
+
id: "cap-zero",
|
|
3739
|
+
why: `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0434\u0440\u043E\u043D\u043E\u0432 \u0440\u043E\u044F \u0440\u0430\u0432\u0435\u043D ${work.maxDrones} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u043D\u0435\u043A\u043E\u0433\u043E \u043F\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435, \u0430 \u043D\u0435 \u043F\u043E \u043F\u043E\u043B\u043E\u043C\u043A\u0435`,
|
|
3740
|
+
fixes: [{ what: "\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 (\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439 \u2192 maxDrones)", command: "swarm hosts # \u043A\u0430\u043A\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B \u0438 \u0441 \u043A\u0430\u043A\u043E\u0439 \u0451\u043C\u043A\u043E\u0441\u0442\u044C\u044E" }]
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
return out;
|
|
3744
|
+
}
|
|
3745
|
+
function idleTrouble(f) {
|
|
3746
|
+
const { work, daemon } = f;
|
|
3747
|
+
if (waitingForDrone(work) === 0 || work.busy > 0) return null;
|
|
3748
|
+
if (daemon.pause?.paused || daemon.restartWaiting || work.storm || work.maxDrones != null && work.maxDrones <= 0) return null;
|
|
3749
|
+
if (work.questions > 0) {
|
|
3750
|
+
return {
|
|
3751
|
+
id: "idle-questions",
|
|
3752
|
+
why: `\u0440\u043E\u0439 \u0436\u0434\u0451\u0442 \u0427\u0415\u041B\u041E\u0412\u0415\u041A\u0410: \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 ${work.questions}`,
|
|
3753
|
+
fixes: [
|
|
3754
|
+
{ what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0432\u043E\u043F\u0440\u043E\u0441\u044B", command: "swarm questions" },
|
|
3755
|
+
{ what: "\u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C", command: "swarm answer <id> <\u043E\u0442\u0432\u0435\u0442>" }
|
|
3756
|
+
]
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
return null;
|
|
3760
|
+
}
|
|
3761
|
+
function troubles(f) {
|
|
3762
|
+
const out = [];
|
|
3763
|
+
if (!f.daemon.alive) {
|
|
3764
|
+
const pause = f.daemon.pause?.paused;
|
|
3765
|
+
const port = f.daemon.webPort ?? 7420;
|
|
3766
|
+
out.push(
|
|
3767
|
+
f.daemon.processAlive ? {
|
|
3768
|
+
// Процесс жив и пульсит, а морда молчит. Советовать здесь `swarm start` —
|
|
3769
|
+
// значит предложить поднять ВТОРОЙ демон на занятый порт: он не поднимется,
|
|
3770
|
+
// а человек решит, что рой не чинится. Ведём туда, где ответ: чем занят порт
|
|
3771
|
+
// и что в логе висящего процесса.
|
|
3772
|
+
id: "web-silent",
|
|
3773
|
+
why: `\u0434\u0435\u043C\u043E\u043D \u0416\u0418\u0412 (\u043F\u0443\u043B\u044C\u0441 \u0441\u0432\u0435\u0436\u0438\u0439), \u043D\u043E \u0435\u0433\u043E \u043C\u043E\u0440\u0434\u0430 127.0.0.1:${port} \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u2014 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0440\u043E\u044F \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435 \u0443 \u043A\u043E\u0433\u043E, \u0438 \u0441\u0430\u043C \u043E\u043D \u043C\u043E\u0433 \u0437\u0430\u0432\u0438\u0441\u043D\u0443\u0442\u044C`,
|
|
3774
|
+
fixes: [
|
|
3775
|
+
{ what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u0447\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u0441\u044F \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0442\u0438\u043A", command: "swarm logs | tail -50" },
|
|
3776
|
+
{ what: `\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u043A\u0442\u043E \u0434\u0435\u0440\u0436\u0438\u0442 \u043F\u043E\u0440\u0442 (\u0447\u0443\u0436\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 / \u0434\u0440\u0443\u0433\u043E\u0439 SWARM_WEB_PORT \u0432 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0438)`, command: `ss -ltnp | grep ${port}` },
|
|
3777
|
+
{ what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430, \u0435\u0441\u043B\u0438 \u043E\u043D \u0437\u0430\u0432\u0438\u0441", command: "swarm stop && swarm start" }
|
|
3778
|
+
]
|
|
3779
|
+
} : {
|
|
3780
|
+
id: "daemon-dead",
|
|
3781
|
+
why: "\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
|
|
3782
|
+
fixes: [
|
|
3783
|
+
{ what: "\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439", command: "swarm start" },
|
|
3784
|
+
...pause ? [{ what: "\u0441\u043D\u044F\u0442\u044C \u043F\u0430\u0443\u0437\u0443 \u2014 \u0438\u043D\u0430\u0447\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0432\u043E\u0437\u044C\u043C\u0451\u0442", command: "swarm resume" }] : []
|
|
3785
|
+
]
|
|
3786
|
+
}
|
|
3787
|
+
);
|
|
3788
|
+
const linkWhenDead = linkTrouble(f.link);
|
|
3789
|
+
if (linkWhenDead) out.push(linkWhenDead);
|
|
3790
|
+
return out;
|
|
3791
|
+
}
|
|
3792
|
+
const link = linkTrouble(f.link);
|
|
3793
|
+
if (link) out.push(link);
|
|
3794
|
+
if (f.daemon.pause?.paused) {
|
|
3795
|
+
out.push({ id: "paused", why: pauseText(f.daemon.pause, f.now), fixes: [{ what: "\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0440\u043E\u0439 \u0432 \u0440\u0430\u0431\u043E\u0442\u0443", command: "swarm resume" }] });
|
|
3796
|
+
}
|
|
3797
|
+
const up = staleMs(f.daemon.startedAt, f.now);
|
|
3798
|
+
const heard = staleMs(f.park.lastHeardAt, f.now);
|
|
3799
|
+
const neverHeard = (f.link.kind === "ok" || f.link.kind === "starting") && f.park.lastHeardAt == null && up != null && up > PARK_STALE_MS;
|
|
3800
|
+
if (!link && (neverHeard || f.park.lastHeardAt && heard != null && heard > PARK_STALE_MS)) {
|
|
3801
|
+
out.push({
|
|
3802
|
+
id: "park-stale",
|
|
3803
|
+
why: neverHeard ? `\u0434\u0435\u043C\u043E\u043D \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 ${uptimeText(f.daemon.startedAt, f.now)}, \u0430 \u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043B\u044B\u0448\u0430\u043B \u044D\u0442\u0443 \u043C\u0430\u0448\u0438\u043D\u0443 \u041D\u0418 \u0420\u0410\u0417\u0423 \u2014 \u043F\u0443\u043B\u044C\u0441 \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u0435\u0442, \u0438 \u0440\u0430\u0431\u043E\u0442\u0443 \u0435\u0439 \u043D\u0435 \u0434\u0430\u044E\u0442` : `\u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432, \u0430 \u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043B\u044B\u0448\u0430\u043B \u044D\u0442\u0443 \u043C\u0430\u0448\u0438\u043D\u0443 ${agoText(f.park.lastHeardAt, f.now)} \u2014 \u043F\u0443\u043B\u044C\u0441 \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u0435\u0442, \u0438 \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D\u0430 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043C\u043E\u043B\u0447\u0430\u0449\u0435\u0439`,
|
|
3804
|
+
fixes: [
|
|
3805
|
+
{ what: "\u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C, \u043D\u0430 \u0447\u0451\u043C \u0441\u043F\u043E\u0442\u044B\u043A\u0430\u0435\u0442\u0441\u044F \u043F\u0443\u043B\u044C\u0441", command: "swarm logs | tail -50" },
|
|
3806
|
+
{ what: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430, \u0435\u0441\u043B\u0438 \u0432 \u043B\u043E\u0433\u0435 \u043F\u0443\u0441\u0442\u043E", command: "swarm stop && swarm start" }
|
|
3807
|
+
]
|
|
3808
|
+
});
|
|
3809
|
+
}
|
|
3810
|
+
out.push(...blockedTroubles(f));
|
|
3811
|
+
const idle = idleTrouble(f);
|
|
3812
|
+
if (idle) out.push(idle);
|
|
3813
|
+
else if (waitingForDrone(f.work) > 0 && f.work.busy === 0 && !link && out.length === 0) {
|
|
3814
|
+
out.push({
|
|
3815
|
+
id: "idle-unknown",
|
|
3816
|
+
why: `\u0436\u0434\u0443\u0442 \u0434\u0440\u043E\u043D\u0430 ${waitingForDrone(f.work)} \u0437\u0430\u0434\u0430\u0447(\u0438) (\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0432\u0441\u0435\u0433\u043E ${f.work.queued}), \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043D\u044F\u0442\u043E 0, \u0430 \u0432\u0438\u0434\u0438\u043C\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u044B \u043D\u0435\u0442 \u2014 \u043E\u0442\u0441\u044E\u0434\u0430 \u0435\u0451 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u0442\u044C. \u0427\u0430\u0441\u0442\u044C \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u043C\u043E\u0433\u043B\u0430 \u0431\u044B\u0442\u044C \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u0430 \u0437\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u043E\u0439`,
|
|
3817
|
+
fixes: [
|
|
3818
|
+
{ what: "\u043E\u0442\u043A\u0430\u0437 \u0441\u043F\u0430\u0432\u043D\u0430 \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u0441\u0430\u043C \u0434\u0435\u043C\u043E\u043D \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 \u0435\u0433\u043E \u043B\u043E\u0433", command: "swarm logs | tail -50" },
|
|
3819
|
+
{ what: "\u0447\u044C\u044F \u044D\u0442\u043E \u043E\u0447\u0435\u0440\u0435\u0434\u044C: \u043A\u0430\u043A\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B \u0432 \u0440\u043E\u0435 \u0438 \u043A\u0442\u043E \u043D\u0430 \u0441\u0432\u044F\u0437\u0438", command: "swarm hosts" },
|
|
3820
|
+
{ what: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043C\u0430\u0448\u0438\u043D\u044B (\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C, gh, \u043A\u043B\u043E\u043D\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432)", command: "swarm doctor" }
|
|
3821
|
+
]
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
return out;
|
|
3825
|
+
}
|
|
3826
|
+
function verdictLine(f, list) {
|
|
3827
|
+
if (list.length === 0) {
|
|
3828
|
+
const busy = f.work.busy;
|
|
3829
|
+
const other = Object.entries(f.work.other).filter(([, n]) => n > 0);
|
|
3830
|
+
const otherTail = other.map(([st, n]) => `${st} ${n}`).join(", ");
|
|
3831
|
+
const tail = busy > 0 ? `${busy} \u0434\u0440\u043E\u043D(\u043E\u0432) \u0432 \u0440\u0430\u0431\u043E\u0442\u0435` : f.work.queued > 0 ? `\u043E\u0447\u0435\u0440\u0435\u0434\u044C \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044F (${f.work.queued} \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438)` : otherTail ? `\u0436\u0438\u0432\u044B\u0445 \u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0435\u0442, \u043D\u043E \u0440\u043E\u0439 \u0437\u0430\u043D\u044F\u0442: ${otherTail}` : "\u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0435\u0442, \u0436\u0434\u0451\u0442 \u0437\u0430\u0434\u0430\u0447";
|
|
3832
|
+
return `\u2713 \u0420\u041E\u0419 \u0420\u0410\u0411\u041E\u0422\u0410\u0415\u0422 \u2014 ${tail}`;
|
|
3833
|
+
}
|
|
3834
|
+
return `\u2715 \u0420\u041E\u0419 \u041D\u0415 \u0420\u0410\u0411\u041E\u0422\u0410\u0415\u0422, \u043F\u043E\u0442\u043E\u043C\u0443 \u0447\u0442\u043E ${list[0].why}`;
|
|
3835
|
+
}
|
|
3836
|
+
function detailLines(f) {
|
|
3837
|
+
const lines = [];
|
|
3838
|
+
const d = f.daemon;
|
|
3839
|
+
if (!d.alive) {
|
|
3840
|
+
lines.push(d.processAlive ? `\u0434\u0435\u043C\u043E\u043D: \u041F\u0420\u041E\u0426\u0415\u0421\u0421 \u0416\u0418\u0412 (\u043F\u0443\u043B\u044C\u0441 \u0441\u0432\u0435\u0436\u0438\u0439), \u043D\u043E \u043C\u043E\u0440\u0434\u0430 127.0.0.1:${d.webPort ?? 7420} \u043C\u043E\u043B\u0447\u0438\u0442` : "\u0434\u0435\u043C\u043E\u043D: \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422");
|
|
3841
|
+
lines.push(` ${d.pause?.paused ? `${pauseText(d.pause, f.now)} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435` : "\u043F\u0430\u0443\u0437\u044B \u043D\u0435\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u0440\u0430\u0431\u043E\u0442\u0443"}`);
|
|
3842
|
+
} else {
|
|
3843
|
+
const up = uptimeText(d.startedAt, f.now);
|
|
3844
|
+
const ver = d.latestVersion == null ? `v${d.version ?? "?"} (\u0441 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u043D\u0435 \u0441\u0432\u0435\u0440\u0438\u043B\u0438 \u2014 \u043D\u0435 \u0434\u043E\u0437\u0432\u043E\u043D\u0438\u043B\u0438\u0441\u044C \u0434\u043E npm)` : isOutdated(d.version ?? "", d.latestVersion) ? `v${d.version} \u2014 \u041E\u0422\u0421\u0422\u0410\u0401\u0422 \u043E\u0442 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 v${d.latestVersion}; \u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432\u0451\u0442 \u043D\u0430 \u043A\u043E\u0434\u0435 \u0441 \u043C\u043E\u043C\u0435\u043D\u0442\u0430 \u0441\u0442\u0430\u0440\u0442\u0430, \u0441\u0430\u043C \u043D\u0435 \u043F\u0435\u0440\u0435\u0435\u0434\u0435\u0442` : d.version === d.latestVersion ? `v${d.version} (\u0441\u043E\u0432\u043F\u0430\u0434\u0430\u0435\u0442 \u0441 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439)` : `v${d.version} (\u043D\u043E\u0432\u0435\u0435 \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 v${d.latestVersion})`;
|
|
3845
|
+
lines.push(`\u0434\u0435\u043C\u043E\u043D: \u0436\u0438\u0432${d.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${d.hostName}\xBB` : ""}${up ? `, \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 ${up}` : ""}`);
|
|
3846
|
+
lines.push(` ${ver}${d.backend ? `, \u0431\u044D\u043A\u0435\u043D\u0434 ${d.backend}` : ""}`);
|
|
3847
|
+
if (d.restartWaiting) lines.push(" \u0416\u0414\u0401\u0422 \u0441\u043B\u0438\u0432\u0430 \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (\u0440\u0435\u0441\u0442\u0430\u0440\u0442/\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430) \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442");
|
|
3848
|
+
lines.push(` ${pauseText(d.pause, f.now)}`);
|
|
3849
|
+
}
|
|
3850
|
+
const buf = f.link.pending == null ? "" : f.link.pending > 0 ? `; \u0432 \u0431\u0443\u0444\u0435\u0440\u0435 ${f.link.pending} \u043E\u043F\u0435\u0440\u0430\u0446(\u0438\u0439) \u041D\u0415 \u0414\u041E\u0415\u0425\u0410\u041B\u041E \u0434\u043E \u043F\u0440\u043E\u0434\u0430` : "; \u0431\u0443\u0444\u0435\u0440 \u043F\u0443\u0441\u0442 \u2014 \u0432\u0441\u0451 \u0434\u043E\u0435\u0445\u0430\u043B\u043E";
|
|
3851
|
+
lines.push(`\u0441\u0432\u044F\u0437\u044C: ${f.link.kind === "no-prod" ? f.link.detail || LINK_TEXT["no-prod"] : `${LINK_TEXT[f.link.kind]}${f.link.detail ? ` (${f.link.detail})` : ""}`}${buf}`);
|
|
3852
|
+
const ok2 = agoText(f.link.lastOkAt, f.now);
|
|
3853
|
+
if (ok2) lines.push(` \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0443\u0434\u0430\u0447\u043D\u044B\u0439 \u043E\u0442\u0432\u0435\u0442 \u043F\u0440\u043E\u0434\u0430: ${ok2}`);
|
|
3854
|
+
const heard = agoText(f.park.lastHeardAt, f.now);
|
|
3855
|
+
const parkLine = heard ? `\u043F\u0440\u043E\u0434 \u0441\u043B\u044B\u0448\u0430\u043B \u0435\u0451 ${heard}` : !f.daemon.alive ? "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u043F\u0443\u043B\u044C\u0441 \u0448\u043B\u0451\u0442 \u0434\u0435\u043C\u043E\u043D, \u0430 \u0435\u0433\u043E \u043D\u0435\u0442" : f.link.kind === "no-prod" || f.link.kind === "unmeasured" ? f.link.kind === "no-prod" ? "\u043F\u0430\u0440\u043A\u0430 \u043C\u0430\u0448\u0438\u043D \u0432 sqlite-\u0440\u0435\u0436\u0438\u043C\u0435 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 \u2014 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" : "\u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u044D\u0442\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0438 \u043F\u0443\u043B\u044C\u0441 \u043D\u0430\u0440\u0443\u0436\u0443 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442" : "\u043F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043B\u044B\u0448\u0430\u043B \u0435\u0451 \u041D\u0418 \u0420\u0410\u0417\u0423 \u2014 \u0441 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u044B \u0435\u0449\u0451 \u043D\u0435 \u0434\u043E\u0435\u0445\u0430\u043B\u043E \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043F\u0443\u043B\u044C\u0441\u0430";
|
|
3856
|
+
lines.push(`\u043C\u0430\u0448\u0438\u043D\u0430: ${parkLine}`);
|
|
3857
|
+
const w = f.work;
|
|
3858
|
+
const queueTail = w.queuedReady != null && w.queuedReady !== w.queued ? ` (\u0436\u0434\u0443\u0442 \u0434\u0440\u043E\u043D\u0430 ${w.queuedReady}, \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u2014 \u0431\u043B\u043E\u043A\u0435\u0440\u043E\u0432)` : "";
|
|
3859
|
+
lines.push(
|
|
3860
|
+
`\u0440\u0430\u0431\u043E\u0442\u0430: \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043D\u044F\u0442\u043E ${w.busy}${w.maxDrones != null ? ` \u0438\u0437 ${w.maxDrones}` : ""}, \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 ${w.queued}${queueTail}, \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 ${w.questions}`
|
|
3861
|
+
);
|
|
3862
|
+
const otherLine = Object.entries(w.other).filter(([, n]) => n > 0);
|
|
3863
|
+
if (otherLine.length) lines.push(` \u0435\u0449\u0451 \u0432 \u0440\u043E\u0435: ${otherLine.map(([st, n]) => `${st} ${n}`).join(", ")}`);
|
|
3864
|
+
if (w.storm) {
|
|
3865
|
+
lines.push(
|
|
3866
|
+
` ${w.storm.reason === "limit" ? `\u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F${w.storm.hint ? ` (${w.storm.hint})` : ""}` : "\u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u0448\u0442\u043E\u0440\u043C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430)"}`
|
|
3867
|
+
);
|
|
3868
|
+
}
|
|
3869
|
+
return lines;
|
|
3870
|
+
}
|
|
3871
|
+
function renderStatus(f) {
|
|
3872
|
+
const list = troubles(f);
|
|
3873
|
+
const lines = [verdictLine(f, list), ""];
|
|
3874
|
+
lines.push(...detailLines(f));
|
|
3875
|
+
if (list.length > 1) {
|
|
3876
|
+
lines.push("");
|
|
3877
|
+
lines.push("\u043A\u0440\u043E\u043C\u0435 \u0442\u043E\u0433\u043E:");
|
|
3878
|
+
for (const t of list.slice(1)) lines.push(` \u2022 ${t.why}`);
|
|
3879
|
+
}
|
|
3880
|
+
const fixes = list.flatMap((t) => t.fixes);
|
|
3881
|
+
if (fixes.length) {
|
|
3882
|
+
lines.push("");
|
|
3883
|
+
lines.push("\u0447\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:");
|
|
3884
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3885
|
+
for (const fix of fixes) {
|
|
3886
|
+
if (seen.has(fix.command)) continue;
|
|
3887
|
+
seen.add(fix.command);
|
|
3888
|
+
lines.push(` ${fix.what}`);
|
|
3889
|
+
lines.push(` $ ${fix.command}`);
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
return { ok: list.length === 0, lines, exitCode: list.length === 0 ? 0 : 1 };
|
|
3893
|
+
}
|
|
3894
|
+
var PARK_STALE_MS, staleMs, LINK_TEXT;
|
|
3895
|
+
var init_swarmStatus = __esm({
|
|
3896
|
+
"src/swarmStatus.ts"() {
|
|
3897
|
+
"use strict";
|
|
3898
|
+
init_pauseState();
|
|
3899
|
+
init_linkFailure();
|
|
3900
|
+
init_release();
|
|
3901
|
+
init_src();
|
|
3902
|
+
PARK_STALE_MS = HOST_HEARTBEAT_STALE_MS;
|
|
3903
|
+
staleMs = (iso, now) => {
|
|
3904
|
+
if (!iso) return null;
|
|
3905
|
+
const ms = Date.parse(iso);
|
|
3906
|
+
return Number.isNaN(ms) ? null : now - ms;
|
|
3907
|
+
};
|
|
3908
|
+
LINK_TEXT = {
|
|
3909
|
+
ok: "\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0442\u043E\u043A\u0435\u043D \u043F\u0440\u0438\u043D\u044F\u0442",
|
|
3910
|
+
"no-token": "\u0432\u0445\u043E\u0434 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D (\u0442\u043E\u043A\u0435\u043D\u0430 \u0440\u043E\u044F \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442)",
|
|
3911
|
+
revoked: "\u0442\u043E\u043A\u0435\u043D \u041E\u0422\u0412\u0415\u0420\u0413\u041D\u0423\u0422 (401 invalid_token)",
|
|
3912
|
+
network: "\u0434\u043E \u043F\u0440\u043E\u0434\u0430 \u043D\u0435 \u0434\u043E\u0441\u0442\u0443\u0447\u0430\u0442\u044C\u0441\u044F",
|
|
3913
|
+
server: "\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
|
|
3914
|
+
down: "\u0441\u0432\u044F\u0437\u0438 \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B",
|
|
3915
|
+
starting: "\u0441\u0432\u044F\u0437\u044C \u0435\u0449\u0451 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0430\u0441\u044C \u2014 \u0434\u0435\u043C\u043E\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F",
|
|
3916
|
+
"no-prod": "\u043F\u0440\u043E\u0434\u0430 \u0443 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435 (sqlite-\u0440\u0435\u0436\u0438\u043C)",
|
|
3917
|
+
unmeasured: "\u0421\u041A\u0410\u0417\u0410\u0422\u042C \u041D\u0415\u0427\u0415\u041C \u2014 \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u044D\u0442\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0438 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0441\u0432\u044F\u0437\u0438 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442"
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
});
|
|
3921
|
+
|
|
3922
|
+
// src/statusCli.ts
|
|
3923
|
+
var statusCli_exports = {};
|
|
3924
|
+
__export(statusCli_exports, {
|
|
3925
|
+
collectStatus: () => collectStatus,
|
|
3926
|
+
outboxPending: () => outboxPending,
|
|
3927
|
+
probeLink: () => probeLink,
|
|
3928
|
+
runStatusCli: () => runStatusCli
|
|
3929
|
+
});
|
|
3307
3930
|
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
3931
|
+
function outboxPending(path) {
|
|
3932
|
+
try {
|
|
3933
|
+
if (!existsSync11(path)) return 0;
|
|
3934
|
+
return readFileSync14(path, "utf8").split("\n").filter((l) => l.trim()).length;
|
|
3935
|
+
} catch {
|
|
3936
|
+
return null;
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
async function probeLink() {
|
|
3940
|
+
const base3 = { lastOkAt: null, pending: outboxPending(SWARM_OUTBOX), api: TASKLESS_API };
|
|
3941
|
+
const store = loadAgentToken();
|
|
3942
|
+
if (!store) return { kind: "no-token", detail: "", ...base3 };
|
|
3943
|
+
const probe = await probeAgentToken({ api: store.api ?? TASKLESS_API, token: store.token });
|
|
3944
|
+
if (probe === "ok") return { kind: "ok", detail: "", ...base3, lastOkAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3945
|
+
if (probe === "revoked") return { kind: "revoked", detail: "401 invalid_token", ...base3 };
|
|
3946
|
+
return { kind: "network", detail: "\u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B (\u0441\u0435\u0442\u044C \u0438\u043B\u0438 \u0441\u0430\u043C \u043F\u0440\u043E\u0434)", ...base3 };
|
|
3947
|
+
}
|
|
3948
|
+
function factsFromDaemon(st, link, now) {
|
|
3949
|
+
const counts = st.counts ?? {};
|
|
3950
|
+
return {
|
|
3951
|
+
daemon: {
|
|
3952
|
+
alive: true,
|
|
3953
|
+
version: st.version ?? null,
|
|
3954
|
+
latestVersion: st.latestVersion ?? null,
|
|
3955
|
+
startedAt: st.startedAt ?? null,
|
|
3956
|
+
hostName: st.hostName ?? null,
|
|
3957
|
+
backend: st.backend ?? null,
|
|
3958
|
+
pause: st.pause ?? (st.paused ? { paused: true, at: "", source: "web" } : null),
|
|
3959
|
+
restartWaiting: st.restart?.phase === "waiting"
|
|
3960
|
+
},
|
|
3961
|
+
link,
|
|
3962
|
+
park: { lastHeardAt: st.link?.heardAt ?? null },
|
|
3963
|
+
work: {
|
|
3964
|
+
busy: st.live?.length ?? 0,
|
|
3965
|
+
queued: counts.queued ?? 0,
|
|
3966
|
+
// Демон старее команды счётчика не отдаёт — тогда null, и меру берём по всей очереди
|
|
3967
|
+
// (см. waitingForDrone): промолчать о вставшем рое хуже, чем сказать лишнее.
|
|
3968
|
+
queuedReady: st.queuedReady ?? null,
|
|
3969
|
+
questions: st.questions ?? 0,
|
|
3970
|
+
// Нетерминальные прогоны, кроме очереди и живых сессий: терминальные знает ядро
|
|
3971
|
+
// (TERMINAL_STATUSES), второго списка тут не заводим.
|
|
3972
|
+
other: Object.fromEntries(
|
|
3973
|
+
Object.entries(counts).filter(([st2, n]) => n > 0 && st2 !== "queued" && st2 !== "running" && !TERMINAL_STATUSES.includes(st2))
|
|
3974
|
+
),
|
|
3975
|
+
maxDrones: st.maxDrones ?? null,
|
|
3976
|
+
storm: st.storm ?? null
|
|
3977
|
+
},
|
|
3978
|
+
now
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
function linkFromDaemon(st) {
|
|
3982
|
+
const api = st.api || TASKLESS_API;
|
|
3983
|
+
if (!st.link) {
|
|
3984
|
+
if (st.authDenied) return { kind: "revoked", detail: st.authDenied, lastOkAt: null, pending: null, api };
|
|
3985
|
+
return {
|
|
3986
|
+
kind: st.backend === "sqlite" ? "no-prod" : "unmeasured",
|
|
3987
|
+
// Только версия: «состояние связи не отдаёт» уже сказано формулировкой состояния, и
|
|
3988
|
+
// дубль в скобках человек перечитывает зря.
|
|
3989
|
+
detail: st.backend === "sqlite" ? "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 sqlite-\u0440\u0435\u0436\u0438\u043C \u2014 \u043F\u0440\u043E\u0434\u0430 \u0443 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 \u0432\u043E\u0432\u0441\u0435" : `\u0434\u0435\u043C\u043E\u043D v${st.version ?? "?"}`,
|
|
3990
|
+
lastOkAt: null,
|
|
3991
|
+
pending: null,
|
|
3992
|
+
api
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
const kind = st.link.kind in KNOWN_LINK_KINDS ? st.link.kind : "unmeasured";
|
|
3996
|
+
return { kind, detail: st.link.detail ?? "", lastOkAt: st.link.lastOkAt ?? null, pending: st.link.pending ?? null, api };
|
|
3997
|
+
}
|
|
3998
|
+
function daemonProcessAlive(now) {
|
|
3999
|
+
const beat = readHeartbeat(HEARTBEAT_PATH);
|
|
4000
|
+
return beat != null && now - beat < HEARTBEAT_FRESH_MS;
|
|
4001
|
+
}
|
|
4002
|
+
async function factsWithoutDaemon(now) {
|
|
4003
|
+
const link = await probeLink();
|
|
4004
|
+
return {
|
|
4005
|
+
daemon: {
|
|
4006
|
+
alive: false,
|
|
4007
|
+
// Ключевое различие (ревью TASWM-325): «морда молчит» — это НЕ «демон не поднят».
|
|
4008
|
+
// Совет `swarm start` на живом процессе даёт второго демона или падение старта.
|
|
4009
|
+
processAlive: daemonProcessAlive(now),
|
|
4010
|
+
webPort: WEB_PORT,
|
|
4011
|
+
version: null,
|
|
4012
|
+
latestVersion: null,
|
|
4013
|
+
startedAt: null,
|
|
4014
|
+
hostName: null,
|
|
4015
|
+
backend: null,
|
|
4016
|
+
pause: loadPauseState(SWARM_HOME),
|
|
4017
|
+
restartWaiting: false
|
|
4018
|
+
},
|
|
4019
|
+
link,
|
|
4020
|
+
// Пульса нет по построению: пульсит демон, а его нет. Не выдаём это за «прод молчит».
|
|
4021
|
+
park: { lastHeardAt: null },
|
|
4022
|
+
work: { busy: 0, queued: 0, queuedReady: null, questions: 0, other: {}, maxDrones: null, storm: null },
|
|
4023
|
+
now
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
async function collectStatus(now = Date.now()) {
|
|
4027
|
+
const st = await daemonStatus2().catch(() => null);
|
|
4028
|
+
if (!st) return { facts: await factsWithoutDaemon(now), daemon: null };
|
|
4029
|
+
return { facts: factsFromDaemon(st, linkFromDaemon(st), now), daemon: st };
|
|
4030
|
+
}
|
|
4031
|
+
async function runStatusCli(_argv = []) {
|
|
4032
|
+
const { facts, daemon } = await collectStatus();
|
|
4033
|
+
const report = renderStatus(facts);
|
|
4034
|
+
for (const line of report.lines) console.log(line);
|
|
4035
|
+
const repoLines = repoMapLines(daemon?.repos);
|
|
4036
|
+
if (repoLines.length) {
|
|
4037
|
+
console.log("");
|
|
4038
|
+
for (const line of repoLines) console.log(line);
|
|
4039
|
+
}
|
|
4040
|
+
if (!facts.daemon.alive) console.log(`
|
|
4041
|
+
(\u043C\u043E\u0440\u0434\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 127.0.0.1:${WEB_PORT} \u043C\u043E\u043B\u0447\u0438\u0442 \u2014 \u0432\u0441\u0451 \u0432\u044B\u0448\u0435 \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0441 \u043C\u0430\u0448\u0438\u043D\u044B)`);
|
|
4042
|
+
return report.exitCode;
|
|
4043
|
+
}
|
|
4044
|
+
var KNOWN_LINK_KINDS, HEARTBEAT_FRESH_MS;
|
|
4045
|
+
var init_statusCli = __esm({
|
|
4046
|
+
"src/statusCli.ts"() {
|
|
4047
|
+
"use strict";
|
|
4048
|
+
init_config();
|
|
4049
|
+
init_heartbeat();
|
|
4050
|
+
init_src();
|
|
4051
|
+
init_daemonLink();
|
|
4052
|
+
init_pauseState();
|
|
4053
|
+
init_agentToken();
|
|
4054
|
+
init_repoDiscovery();
|
|
4055
|
+
init_swarmStatus();
|
|
4056
|
+
KNOWN_LINK_KINDS = {
|
|
4057
|
+
ok: true,
|
|
4058
|
+
starting: true,
|
|
4059
|
+
"no-token": true,
|
|
4060
|
+
revoked: true,
|
|
4061
|
+
network: true,
|
|
4062
|
+
server: true,
|
|
4063
|
+
down: true,
|
|
4064
|
+
"no-prod": true,
|
|
4065
|
+
unmeasured: true
|
|
4066
|
+
};
|
|
4067
|
+
HEARTBEAT_FRESH_MS = 6e4;
|
|
4068
|
+
}
|
|
4069
|
+
});
|
|
4070
|
+
|
|
4071
|
+
// src/platform.ts
|
|
4072
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
3308
4073
|
import { delimiter, join as join19 } from "path";
|
|
3309
4074
|
function safeReadText(path) {
|
|
3310
4075
|
try {
|
|
3311
|
-
return
|
|
4076
|
+
return readFileSync15(path, "utf8");
|
|
3312
4077
|
} catch {
|
|
3313
4078
|
return null;
|
|
3314
4079
|
}
|
|
3315
4080
|
}
|
|
3316
4081
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
3317
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
4082
|
+
if (name.includes("/") || name.includes("\\")) return existsSync12(name) ? name : null;
|
|
3318
4083
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
3319
4084
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
3320
4085
|
for (const dir of dirs) {
|
|
3321
4086
|
for (const ext of exts) {
|
|
3322
4087
|
const candidate = join19(dir, name + ext);
|
|
3323
|
-
if (
|
|
4088
|
+
if (existsSync12(candidate)) return candidate;
|
|
3324
4089
|
}
|
|
3325
4090
|
}
|
|
3326
4091
|
return null;
|
|
@@ -3423,12 +4188,12 @@ var init_platform = __esm({
|
|
|
3423
4188
|
});
|
|
3424
4189
|
|
|
3425
4190
|
// src/envGate.ts
|
|
3426
|
-
import { existsSync as
|
|
4191
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3427
4192
|
import { join as join20 } from "path";
|
|
3428
4193
|
import { createConnection } from "net";
|
|
3429
4194
|
import { execa as execa3 } from "execa";
|
|
3430
4195
|
function detectRequirements(repo, io = {}) {
|
|
3431
|
-
const exists = io.exists ??
|
|
4196
|
+
const exists = io.exists ?? existsSync13;
|
|
3432
4197
|
const marker = COMPOSE_MARKERS.find((f) => exists(join20(repo, f)));
|
|
3433
4198
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
3434
4199
|
}
|
|
@@ -3460,42 +4225,6 @@ var init_envGate = __esm({
|
|
|
3460
4225
|
}
|
|
3461
4226
|
});
|
|
3462
4227
|
|
|
3463
|
-
// src/release.ts
|
|
3464
|
-
async function fetchLatestVersion(opts = {}) {
|
|
3465
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
3466
|
-
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
3467
|
-
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
3468
|
-
const ctrl = new AbortController();
|
|
3469
|
-
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
3470
|
-
try {
|
|
3471
|
-
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
3472
|
-
signal: ctrl.signal,
|
|
3473
|
-
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
3474
|
-
});
|
|
3475
|
-
if (!res.ok) return null;
|
|
3476
|
-
const body = await res.json();
|
|
3477
|
-
return typeof body.version === "string" && body.version ? body.version : null;
|
|
3478
|
-
} catch {
|
|
3479
|
-
return null;
|
|
3480
|
-
} finally {
|
|
3481
|
-
clearTimeout(timer);
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
function isOutdated(current, latest) {
|
|
3485
|
-
if (!latest || !current) return false;
|
|
3486
|
-
return compareVersionDirs(latest, current) > 0;
|
|
3487
|
-
}
|
|
3488
|
-
var SWARM_PACKAGE, LATEST_TIMEOUT_MS;
|
|
3489
|
-
var init_release = __esm({
|
|
3490
|
-
"src/release.ts"() {
|
|
3491
|
-
"use strict";
|
|
3492
|
-
init_src();
|
|
3493
|
-
init_claudeBin();
|
|
3494
|
-
SWARM_PACKAGE = SWARM_PACKAGE_NAME;
|
|
3495
|
-
LATEST_TIMEOUT_MS = 4e3;
|
|
3496
|
-
}
|
|
3497
|
-
});
|
|
3498
|
-
|
|
3499
4228
|
// src/doctor.ts
|
|
3500
4229
|
import { isAbsolute as isAbsolute4, join as join21 } from "path";
|
|
3501
4230
|
function ok(id, title, detail) {
|
|
@@ -3878,12 +4607,12 @@ var init_doctor = __esm({
|
|
|
3878
4607
|
});
|
|
3879
4608
|
|
|
3880
4609
|
// src/repoMemory.ts
|
|
3881
|
-
import { readFileSync as
|
|
4610
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
|
|
3882
4611
|
import { join as join22 } from "path";
|
|
3883
4612
|
function loadRememberedRepos(home) {
|
|
3884
4613
|
let raw;
|
|
3885
4614
|
try {
|
|
3886
|
-
raw =
|
|
4615
|
+
raw = readFileSync16(join22(home, FILE), "utf8");
|
|
3887
4616
|
} catch {
|
|
3888
4617
|
return [];
|
|
3889
4618
|
}
|
|
@@ -3918,7 +4647,7 @@ __export(installCli_exports, {
|
|
|
3918
4647
|
runInstallCli: () => runInstallCli,
|
|
3919
4648
|
shortenError: () => shortenError
|
|
3920
4649
|
});
|
|
3921
|
-
import { existsSync as
|
|
4650
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3922
4651
|
function classifyChromiumError(message) {
|
|
3923
4652
|
if (/playwright-core не установлен|Cannot find (module|package)/i.test(message)) return "no-module";
|
|
3924
4653
|
if (/error while loading shared libraries|cannot open shared object file|missing dependencies|\blib[\w.+-]*\.so[.\d]*\b/i.test(message)) return "no-libs";
|
|
@@ -3973,7 +4702,7 @@ function defaultDoctorIO(packageVersion) {
|
|
|
3973
4702
|
packageVersion,
|
|
3974
4703
|
entry,
|
|
3975
4704
|
hasCommand: (name) => hasCommandInPath(name),
|
|
3976
|
-
exists:
|
|
4705
|
+
exists: existsSync14,
|
|
3977
4706
|
run: (command) => runQuiet(command),
|
|
3978
4707
|
latestVersion: () => latestMemo ??= fetchLatestVersion(),
|
|
3979
4708
|
probeChromium: () => probeChromium(),
|
|
@@ -4183,7 +4912,7 @@ var init_updateCli = __esm({
|
|
|
4183
4912
|
// src/cli.ts
|
|
4184
4913
|
init_cliIo();
|
|
4185
4914
|
import { basename as basename4, join as join23 } from "path";
|
|
4186
|
-
import { existsSync as
|
|
4915
|
+
import { existsSync as existsSync15, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4, rmSync as rmSync2 } from "fs";
|
|
4187
4916
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
4188
4917
|
|
|
4189
4918
|
// src/swarmClient.ts
|
|
@@ -4524,7 +5253,16 @@ var SwarmClient = class _SwarmClient {
|
|
|
4524
5253
|
static REJECT_LIMIT = 5;
|
|
4525
5254
|
static BACKOFF_BASE = 500;
|
|
4526
5255
|
static BACKOFF_MAX = 3e4;
|
|
5256
|
+
// TASWM-325: момент УДАЧНОГО пульса — «когда прод последний раз слышал эту машину»
|
|
5257
|
+
// (прод пишет SwarmHost.lastSeenAt ровно на swarm_heartbeat). Единственное, что про
|
|
5258
|
+
// связь знает КЛИЕНТ: жива ли она вообще — знает транспорт (mcpResilient.link), а
|
|
5259
|
+
// отвергнут ли токен — authState. Здесь их не угадываем: первая редакция считала
|
|
5260
|
+
// успехом любой возврат call и на боевом каллере (который не бросает, а возвращает
|
|
5261
|
+
// isError) всегда показывала «связь есть».
|
|
5262
|
+
heartbeatOkAt = null;
|
|
5263
|
+
now;
|
|
4527
5264
|
constructor(opts) {
|
|
5265
|
+
this.now = opts.now ?? Date.now;
|
|
4528
5266
|
this.call = opts.call;
|
|
4529
5267
|
this.defaultRepo = normalizeRepo(opts.defaultRepo ?? "");
|
|
4530
5268
|
this.autoFlush = opts.autoFlush ?? true;
|
|
@@ -4667,6 +5405,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
4667
5405
|
if ("decision" in patch) out.decision = patch.decision ? JSON.stringify(patch.decision) : null;
|
|
4668
5406
|
if ("kind" in patch && patch.kind !== void 0) out.role = patch.kind;
|
|
4669
5407
|
if ("review_verdict" in patch) out.reviewVerdict = patch.review_verdict ?? null;
|
|
5408
|
+
if ("verify_verdict" in patch) out.verifyVerdict = patch.verify_verdict ?? null;
|
|
4670
5409
|
if ("host_id" in patch && patch.host_id) {
|
|
4671
5410
|
out.hostId = patch.host_id;
|
|
4672
5411
|
out.hostPinned = patch.host_pinned === true;
|
|
@@ -4677,6 +5416,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
4677
5416
|
this.setRunRefField(out, "resolvesRunId", patch, "resolves_run_id");
|
|
4678
5417
|
this.setRunRefField(out, "supersededById", patch, "superseded_by");
|
|
4679
5418
|
this.setRunRefField(out, "reviewsRunId", patch, "reviews_run_id");
|
|
5419
|
+
this.setRunRefField(out, "verifiesRunId", patch, "verifies_run_id");
|
|
4680
5420
|
return out;
|
|
4681
5421
|
}
|
|
4682
5422
|
// Положить self-ref прогона в прод-патч: локальный number → отложенная ссылка
|
|
@@ -4739,6 +5479,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
4739
5479
|
resolves_run_id: input.resolves_run_id ?? null,
|
|
4740
5480
|
reviews_run_id: input.reviews_run_id ?? null,
|
|
4741
5481
|
review_verdict: null,
|
|
5482
|
+
verifies_run_id: input.verifies_run_id ?? null,
|
|
5483
|
+
// прогон-верификатор (TASWM-344)
|
|
5484
|
+
verify_verdict: null,
|
|
4742
5485
|
review_phase: null,
|
|
4743
5486
|
// вычисляемая фаза ревью (TASWM-230) — демон посчитает на review-тике
|
|
4744
5487
|
review_cycle: input.review_cycle ?? 0,
|
|
@@ -4790,6 +5533,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
4790
5533
|
// резолвер конфликта → связь на review-прогон
|
|
4791
5534
|
reviews_run_id: d.reviews_run_id,
|
|
4792
5535
|
// ревьюер (авто-ревью, TASWM-148) → связь на review-прогон
|
|
5536
|
+
verifies_run_id: d.verifies_run_id,
|
|
5537
|
+
// верификатор (TASWM-344) → связь на проверяемый прогон
|
|
4793
5538
|
host_id: d.host_id,
|
|
4794
5539
|
// машина прогона (TASWM-281) — своя с рождения либо заданная --host
|
|
4795
5540
|
host_pinned: d.host_pinned,
|
|
@@ -4797,7 +5542,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
4797
5542
|
base_branch: d.base_branch
|
|
4798
5543
|
// базовая ветка PR (TASWM-285), если задана на спавне
|
|
4799
5544
|
}));
|
|
4800
|
-
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
5545
|
+
const spawnNote = input.resolves_run_id ? `\u0440\u0435\u0437\u043E\u043B\u0432\u0435\u0440 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0430 \u0434\u043B\u044F #${input.resolves_run_id}` : input.reviews_run_id ? `\u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0434\u043B\u044F #${input.reviews_run_id}` : input.verifies_run_id ? `\u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u0434\u043B\u044F #${input.verifies_run_id}` : input.decision ? `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A \u0441 \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \xAB${input.decision.chosen}\xBB` : `run for ${input.task_slug}`;
|
|
4801
5546
|
this.addEvent(id, "spawned", spawnNote);
|
|
4802
5547
|
return this.mat(d);
|
|
4803
5548
|
}
|
|
@@ -4901,6 +5646,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
4901
5646
|
activeReviewersFor(reviewRunId) {
|
|
4902
5647
|
return [...this.runs.values()].filter((r) => r.reviews_run_id === reviewRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
|
|
4903
5648
|
}
|
|
5649
|
+
// Верификация (TASWM-344) — зеркало трёх счётчиков ревью выше по своей связи
|
|
5650
|
+
// verifies_run_id; правила «кто не в счёт» те же (см. db.ts:verifierCountFor).
|
|
5651
|
+
verifierCountFor(verifyRunId) {
|
|
5652
|
+
const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
|
|
5653
|
+
const dead = /* @__PURE__ */ new Set(["failed", "stalled", "stopped", "superseded"]);
|
|
5654
|
+
return [...this.runs.values()].filter(
|
|
5655
|
+
(r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && !dead.has(r.status)
|
|
5656
|
+
).length;
|
|
5657
|
+
}
|
|
5658
|
+
verifierAttemptCountFor(verifyRunId) {
|
|
5659
|
+
const cycle = this.runs.get(verifyRunId)?.review_cycle ?? 0;
|
|
5660
|
+
return [...this.runs.values()].filter(
|
|
5661
|
+
(r) => r.verifies_run_id === verifyRunId && r.review_cycle === cycle && r.status !== "superseded"
|
|
5662
|
+
).length;
|
|
5663
|
+
}
|
|
5664
|
+
activeVerifiersFor(verifyRunId) {
|
|
5665
|
+
return [...this.runs.values()].filter((r) => r.verifies_run_id === verifyRunId && !TERMINAL_STATUSES.includes(r.status)).sort((a, b) => b.id - a.id).map((d) => this.mat(d));
|
|
5666
|
+
}
|
|
4904
5667
|
activeRunForSlug(slug) {
|
|
4905
5668
|
const cands = [...this.runs.values()].filter((r) => r.task_slug === slug && !SLUG_TERMINAL.has(r.status)).sort((a, b) => b.id - a.id);
|
|
4906
5669
|
return cands.length ? this.mat(cands[0]) : null;
|
|
@@ -4936,7 +5699,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
4936
5699
|
if (!depends_on || depends_on.length === 0) return [];
|
|
4937
5700
|
const doneInMirror = /* @__PURE__ */ new Set();
|
|
4938
5701
|
for (const r of this.runs.values()) {
|
|
4939
|
-
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null
|
|
5702
|
+
if (r.status === "done" && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null) {
|
|
5703
|
+
doneInMirror.add(r.task_slug);
|
|
5704
|
+
}
|
|
4940
5705
|
}
|
|
4941
5706
|
return depends_on.map((slug) => {
|
|
4942
5707
|
if (doneInMirror.has(slug)) {
|
|
@@ -4982,7 +5747,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
4982
5747
|
// Служебный сабран под тем же слагом (резолвер конфликта / ревьюер): его финал —
|
|
4983
5748
|
// не финал задачи. Резолвер вычёркивался и до TASWM-361, ревьюер добавлен вместе
|
|
4984
5749
|
// с расширением критерия — иначе остановленный ревьюер закрывал бы зависимость.
|
|
4985
|
-
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId),
|
|
5750
|
+
service: Boolean(r.resolvesRunId) || Boolean(r.reviewsRunId) || Boolean(r.verifiesRunId),
|
|
4986
5751
|
// В проде имя дрона — колонка droneName (см. hydrateRun); `name` терпим как
|
|
4987
5752
|
// запасной вариант ради фикстур и старых ответов.
|
|
4988
5753
|
name: r.droneName ?? r.name ?? null,
|
|
@@ -5198,7 +5963,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5198
5963
|
return this.eventsLog.filter((e) => {
|
|
5199
5964
|
if (e.kind !== "failed" || e.run_id == null || e.at < cutoff) return false;
|
|
5200
5965
|
const r = this.runs.get(e.run_id);
|
|
5201
|
-
return !!r && r.resolves_run_id == null && r.reviews_run_id == null;
|
|
5966
|
+
return !!r && r.resolves_run_id == null && r.reviews_run_id == null && r.verifies_run_id == null;
|
|
5202
5967
|
}).length;
|
|
5203
5968
|
}
|
|
5204
5969
|
recordStormProbe(probe, ext, reason, resetHint) {
|
|
@@ -5323,7 +6088,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5323
6088
|
const workerWt = droneWorktreePath(opts.worktreesDir, d.repo || this.defaultRepo, d.task_slug);
|
|
5324
6089
|
let matchWt = null;
|
|
5325
6090
|
let matchPid = null;
|
|
5326
|
-
if (d.resolves_run_id == null && d.reviews_run_id == null) {
|
|
6091
|
+
if (d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null) {
|
|
5327
6092
|
const p = pidByWorktree.get(workerWt);
|
|
5328
6093
|
if (p != null) {
|
|
5329
6094
|
matchWt = workerWt;
|
|
@@ -5339,9 +6104,10 @@ var SwarmClient = class _SwarmClient {
|
|
|
5339
6104
|
}
|
|
5340
6105
|
}
|
|
5341
6106
|
} else {
|
|
5342
|
-
const
|
|
6107
|
+
const checkedId = d.reviews_run_id ?? d.verifies_run_id;
|
|
6108
|
+
const reviewed = this.runs.get(checkedId);
|
|
5343
6109
|
const reviewedWt = reviewed ? droneWorktreePath(opts.worktreesDir, reviewed.repo || this.defaultRepo, reviewed.task_slug) : workerWt;
|
|
5344
|
-
const prefix = `${reviewedWt}-review-`;
|
|
6110
|
+
const prefix = d.reviews_run_id != null ? `${reviewedWt}-review-` : `${reviewedWt}-verify-`;
|
|
5345
6111
|
for (const [wt, p] of pidByWorktree) {
|
|
5346
6112
|
if (wt.startsWith(prefix)) {
|
|
5347
6113
|
matchWt = wt;
|
|
@@ -5354,7 +6120,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5354
6120
|
this.runtime.set(d.id, { pid: matchPid, worktree_path: matchWt });
|
|
5355
6121
|
alive++;
|
|
5356
6122
|
} else {
|
|
5357
|
-
const wt = d.resolves_run_id == null && d.reviews_run_id == null && wtExists(workerWt) ? workerWt : null;
|
|
6123
|
+
const wt = d.resolves_run_id == null && d.reviews_run_id == null && d.verifies_run_id == null && wtExists(workerWt) ? workerWt : null;
|
|
5358
6124
|
this.runtime.set(d.id, { pid: null, worktree_path: wt });
|
|
5359
6125
|
dead++;
|
|
5360
6126
|
}
|
|
@@ -5375,6 +6141,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
5375
6141
|
if (sup) local.superseded_by = this.localIdForProdId(sup) ?? local.superseded_by;
|
|
5376
6142
|
const rev = pr.reviewsRunId ?? null;
|
|
5377
6143
|
if (rev) local.reviews_run_id = this.localIdForProdId(rev) ?? local.reviews_run_id;
|
|
6144
|
+
const ver = pr.verifiesRunId ?? null;
|
|
6145
|
+
if (ver) local.verifies_run_id = this.localIdForProdId(ver) ?? local.verifies_run_id;
|
|
5378
6146
|
}
|
|
5379
6147
|
/**
|
|
5380
6148
|
* Авто-интейк (TASWM-100): взять следующую claimable-задачу владельца токена через
|
|
@@ -5871,9 +6639,16 @@ var SwarmClient = class _SwarmClient {
|
|
|
5871
6639
|
* простоя, видимого только в логе на самой машине. Не передали (нечем измерить) → ключ
|
|
5872
6640
|
* не шлём, прод прошлое не трогает — та же логика, что у capacity/repoFullNames.
|
|
5873
6641
|
*
|
|
6642
|
+
* TASWM-333: тем же пульсом уходит ШТОРМ этой машины (`storm` — штормим ли сейчас, и с
|
|
6643
|
+
* какой формулировкой мы в него вошли). Состояние живёт в наших событиях и переживает
|
|
6644
|
+
* рестарт, а в проде его не было вовсе: рой стоял, а парк машин показывал живую свободную
|
|
6645
|
+
* машину — 10.08 это стоило десяти часов простоя, замеченных человеком вручную. Шлём
|
|
6646
|
+
* ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
|
|
6647
|
+
* означало бы «прод помнит вчерашний шторм», когда рой давно работает.
|
|
6648
|
+
*
|
|
5874
6649
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
5875
6650
|
*/
|
|
5876
|
-
async heartbeat(daemonVersion, pause, capacity, resources) {
|
|
6651
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm) {
|
|
5877
6652
|
try {
|
|
5878
6653
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
5879
6654
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -5883,12 +6658,17 @@ var SwarmClient = class _SwarmClient {
|
|
|
5883
6658
|
args.paused = !!pause?.paused;
|
|
5884
6659
|
if (pause?.reason) args.pauseReason = pause.reason;
|
|
5885
6660
|
}
|
|
6661
|
+
if (storm !== void 0 && storm !== null) {
|
|
6662
|
+
args.storm = storm.active;
|
|
6663
|
+
if (storm.reason) args.stormReason = storm.reason;
|
|
6664
|
+
}
|
|
5886
6665
|
if (this.host) {
|
|
5887
6666
|
args.hostId = this.host.hostId;
|
|
5888
6667
|
args.hostName = this.host.hostName;
|
|
5889
6668
|
}
|
|
5890
6669
|
const res = await this.call("swarm_heartbeat", args);
|
|
5891
6670
|
if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
|
|
6671
|
+
this.heartbeatOkAt = this.now();
|
|
5892
6672
|
const data = res?.structuredContent ?? {};
|
|
5893
6673
|
return {
|
|
5894
6674
|
commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
|
|
@@ -6100,6 +6880,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
6100
6880
|
resolves_run_id: null,
|
|
6101
6881
|
reviews_run_id: null,
|
|
6102
6882
|
review_verdict: null,
|
|
6883
|
+
verifies_run_id: null,
|
|
6884
|
+
// прогон-верификатор (TASWM-344) — прод-поле, заполняется ниже
|
|
6885
|
+
verify_verdict: null,
|
|
6103
6886
|
review_phase: null,
|
|
6104
6887
|
// вычисляемая фаза ревью (TASWM-230) — прод хранит и раздаёт
|
|
6105
6888
|
// Локальная бухгалтерия (в прод-схему не уходит) — на гидрации из прода дефолт 0;
|
|
@@ -6143,6 +6926,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
6143
6926
|
const revProd = pr.reviewsRunId ?? null;
|
|
6144
6927
|
if (revProd) local.reviews_run_id = this.localIdForProdId(revProd) ?? local.reviews_run_id;
|
|
6145
6928
|
local.review_verdict = pr.reviewVerdict ?? local.review_verdict;
|
|
6929
|
+
const verProd = pr.verifiesRunId ?? null;
|
|
6930
|
+
if (verProd) local.verifies_run_id = this.localIdForProdId(verProd) ?? local.verifies_run_id;
|
|
6931
|
+
local.verify_verdict = toVerifyVerdict(pr.verifyVerdict) ?? local.verify_verdict;
|
|
6146
6932
|
local.review_phase = pr.reviewPhase ?? local.review_phase;
|
|
6147
6933
|
local.repo = this.resolveRepo(pr.repo ?? null);
|
|
6148
6934
|
local.host_id = pr.hostId ?? null;
|
|
@@ -6431,6 +7217,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
6431
7217
|
pendingCount() {
|
|
6432
7218
|
return this.pending.length;
|
|
6433
7219
|
}
|
|
7220
|
+
/**
|
|
7221
|
+
* Когда прод в последний раз ПРИНЯЛ пульс этой машины (ISO) — вход `swarm status`.
|
|
7222
|
+
* null — ни разу за жизнь процесса.
|
|
7223
|
+
*
|
|
7224
|
+
* Почему именно пульс, а не «последний удачный вызов»: в парке машин lastSeenAt прод
|
|
7225
|
+
* пишет ровно на swarm_heartbeat. Спрашивать за тем же самым swarm_list_hosts значило бы
|
|
7226
|
+
* завести второй источник правды и платить прод-вызовом в команде, которую зовут именно
|
|
7227
|
+
* тогда, когда прод не отвечает.
|
|
7228
|
+
*/
|
|
7229
|
+
lastHeardAt() {
|
|
7230
|
+
return this.heartbeatOkAt == null ? null : new Date(this.heartbeatOkAt).toISOString();
|
|
7231
|
+
}
|
|
6434
7232
|
close() {
|
|
6435
7233
|
if (this.flushTimer) {
|
|
6436
7234
|
clearTimeout(this.flushTimer);
|
|
@@ -6878,22 +7676,21 @@ async function cleanupWorktreeProcesses(worktree, opts = {}) {
|
|
|
6878
7676
|
|
|
6879
7677
|
// src/cli.ts
|
|
6880
7678
|
init_repoRegistry();
|
|
6881
|
-
init_repoDiscovery();
|
|
6882
7679
|
|
|
6883
7680
|
// src/hostIdentity.ts
|
|
6884
7681
|
import { hostname } from "os";
|
|
6885
|
-
import { existsSync as existsSync6, readFileSync as
|
|
6886
|
-
import { join as
|
|
7682
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
7683
|
+
import { join as join6 } from "path";
|
|
6887
7684
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6888
7685
|
function hostIdentityPath(home) {
|
|
6889
|
-
return
|
|
7686
|
+
return join6(home, "host.json");
|
|
6890
7687
|
}
|
|
6891
7688
|
function loadHostIdentity(deps) {
|
|
6892
7689
|
const exists = deps.exists ?? existsSync6;
|
|
6893
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
7690
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
|
|
6894
7691
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
6895
|
-
|
|
6896
|
-
|
|
7692
|
+
writeFileSync4(`${p}.tmp`, data);
|
|
7693
|
+
renameSync3(`${p}.tmp`, p);
|
|
6897
7694
|
});
|
|
6898
7695
|
const host = deps.hostname ?? hostname;
|
|
6899
7696
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -7007,8 +7804,8 @@ import { basename as basename2 } from "path";
|
|
|
7007
7804
|
import { serve as serve2 } from "@hono/node-server";
|
|
7008
7805
|
|
|
7009
7806
|
// src/diff.ts
|
|
7010
|
-
import { statSync as
|
|
7011
|
-
import { join as
|
|
7807
|
+
import { statSync as statSync5 } from "fs";
|
|
7808
|
+
import { join as join7 } from "path";
|
|
7012
7809
|
import { $ as $3 } from "execa";
|
|
7013
7810
|
var MAX_FILES = 200;
|
|
7014
7811
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -7151,7 +7948,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
7151
7948
|
}
|
|
7152
7949
|
function mtime(path) {
|
|
7153
7950
|
try {
|
|
7154
|
-
return
|
|
7951
|
+
return statSync5(path).mtimeMs;
|
|
7155
7952
|
} catch {
|
|
7156
7953
|
return 0;
|
|
7157
7954
|
}
|
|
@@ -7176,7 +7973,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
7176
7973
|
files.push(pf);
|
|
7177
7974
|
}
|
|
7178
7975
|
}
|
|
7179
|
-
files.sort((a, b) => mtime(
|
|
7976
|
+
files.sort((a, b) => mtime(join7(worktree, b.path)) - mtime(join7(worktree, a.path)));
|
|
7180
7977
|
const truncatedFiles = files.length > MAX_FILES;
|
|
7181
7978
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
7182
7979
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -7271,10 +8068,23 @@ function makeMergeGateResolver(deps) {
|
|
|
7271
8068
|
const run = findRunByPr(deps.listRuns(), ref);
|
|
7272
8069
|
if (!run) return { ...computeMergeGate({ hasRun: false, autoReview: false, reviewVerdict: null }), run: null };
|
|
7273
8070
|
const autoReview = deps.autoReview ? await deps.autoReview(run.repo).catch(() => deps.fallbackAutoReview) : deps.fallbackAutoReview;
|
|
7274
|
-
const decision = computeMergeGate({
|
|
8071
|
+
const decision = computeMergeGate({
|
|
8072
|
+
hasRun: true,
|
|
8073
|
+
autoReview,
|
|
8074
|
+
reviewVerdict: run.review_verdict,
|
|
8075
|
+
autoVerify: deps.autoVerify === true,
|
|
8076
|
+
verifyVerdict: run.verify_verdict ?? null
|
|
8077
|
+
});
|
|
7275
8078
|
return {
|
|
7276
8079
|
...decision,
|
|
7277
|
-
run: {
|
|
8080
|
+
run: {
|
|
8081
|
+
id: run.id,
|
|
8082
|
+
task_slug: run.task_slug,
|
|
8083
|
+
name: run.name,
|
|
8084
|
+
pr_url: run.pr_url,
|
|
8085
|
+
review_verdict: run.review_verdict,
|
|
8086
|
+
verify_verdict: run.verify_verdict ?? null
|
|
8087
|
+
}
|
|
7278
8088
|
};
|
|
7279
8089
|
};
|
|
7280
8090
|
}
|
|
@@ -7282,25 +8092,55 @@ function makeMergeGateResolver(deps) {
|
|
|
7282
8092
|
// src/web.ts
|
|
7283
8093
|
init_droneChannel();
|
|
7284
8094
|
|
|
8095
|
+
// src/authState.ts
|
|
8096
|
+
init_agentToken();
|
|
8097
|
+
var SHOUT_EVERY_MS = 10 * 6e4;
|
|
8098
|
+
var denial = null;
|
|
8099
|
+
function noteTokenRejected(nowMs = Date.now()) {
|
|
8100
|
+
if (!denial) {
|
|
8101
|
+
denial = { since: nowMs, lastShoutAt: nowMs, count: 1 };
|
|
8102
|
+
return revokedTokenComplaint();
|
|
8103
|
+
}
|
|
8104
|
+
denial.count += 1;
|
|
8105
|
+
if (nowMs - denial.lastShoutAt < SHOUT_EVERY_MS) return null;
|
|
8106
|
+
denial.lastShoutAt = nowMs;
|
|
8107
|
+
return `${revokedTokenComplaint()}
|
|
8108
|
+
${deniedSinceLine(nowMs)}
|
|
8109
|
+
`;
|
|
8110
|
+
}
|
|
8111
|
+
function noteTokenAccepted() {
|
|
8112
|
+
denial = null;
|
|
8113
|
+
}
|
|
8114
|
+
function authStateLine(nowMs = Date.now()) {
|
|
8115
|
+
if (!denial) return null;
|
|
8116
|
+
return `\u0414\u041E\u0421\u0422\u0423\u041F \u041A TASKLESS \u041E\u0422\u041E\u0417\u0412\u0410\u041D \u2014 ${deniedSinceLine(nowMs)}. \u041F\u043E\u0447\u0438\u043D\u0438 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: swarm login`;
|
|
8117
|
+
}
|
|
8118
|
+
function deniedSinceLine(nowMs) {
|
|
8119
|
+
const d = denial;
|
|
8120
|
+
if (!d) return "";
|
|
8121
|
+
const min = Math.max(1, Math.round((nowMs - d.since) / 6e4));
|
|
8122
|
+
return `\u043F\u0440\u043E\u0434 \u043E\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0442\u043E\u043A\u0435\u043D ${min} \u043C\u0438\u043D, \u043E\u0442\u043A\u0430\u0437\u043E\u0432 ${d.count}`;
|
|
8123
|
+
}
|
|
8124
|
+
|
|
7285
8125
|
// src/oauth.ts
|
|
7286
8126
|
init_config();
|
|
7287
8127
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
7288
|
-
import { readFileSync as
|
|
7289
|
-
import { join as
|
|
8128
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
8129
|
+
import { join as join9 } from "path";
|
|
7290
8130
|
var REGISTER = "/api/oauth/register";
|
|
7291
8131
|
var AUTHORIZE = "/api/oauth/authorize";
|
|
7292
8132
|
var TOKEN = "/api/oauth/token";
|
|
7293
8133
|
var SCOPE = "openid profile email taskless:full";
|
|
7294
|
-
var
|
|
7295
|
-
var CLIENT_PATH =
|
|
8134
|
+
var STORE_PATH2 = join9(SWARM_HOME, "oauth.json");
|
|
8135
|
+
var CLIENT_PATH = join9(SWARM_HOME, "oauth-client.json");
|
|
7296
8136
|
function makePkce() {
|
|
7297
8137
|
const verifier = randomBytes2(48).toString("base64url");
|
|
7298
8138
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
7299
8139
|
return { verifier, challenge };
|
|
7300
8140
|
}
|
|
7301
|
-
var
|
|
8141
|
+
var base2 = (api) => api.replace(/\/$/, "");
|
|
7302
8142
|
async function registerClient(args, fetchFn = fetch) {
|
|
7303
|
-
const res = await fetchFn(`${
|
|
8143
|
+
const res = await fetchFn(`${base2(args.api)}${REGISTER}`, {
|
|
7304
8144
|
method: "POST",
|
|
7305
8145
|
headers: { "Content-Type": "application/json" },
|
|
7306
8146
|
body: JSON.stringify({
|
|
@@ -7315,7 +8155,7 @@ async function registerClient(args, fetchFn = fetch) {
|
|
|
7315
8155
|
return j.client_id;
|
|
7316
8156
|
}
|
|
7317
8157
|
function authorizeUrl(args) {
|
|
7318
|
-
const u = new URL(`${
|
|
8158
|
+
const u = new URL(`${base2(args.api)}${AUTHORIZE}`);
|
|
7319
8159
|
u.searchParams.set("response_type", "code");
|
|
7320
8160
|
u.searchParams.set("client_id", args.clientId);
|
|
7321
8161
|
u.searchParams.set("redirect_uri", args.redirectUri);
|
|
@@ -7326,7 +8166,7 @@ function authorizeUrl(args) {
|
|
|
7326
8166
|
return u.toString();
|
|
7327
8167
|
}
|
|
7328
8168
|
async function postToken(api, params, fetchFn) {
|
|
7329
|
-
const res = await fetchFn(`${
|
|
8169
|
+
const res = await fetchFn(`${base2(api)}${TOKEN}`, {
|
|
7330
8170
|
method: "POST",
|
|
7331
8171
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
7332
8172
|
body: new URLSearchParams(params).toString()
|
|
@@ -7377,23 +8217,23 @@ function toStore(clientId, t, now) {
|
|
|
7377
8217
|
}
|
|
7378
8218
|
function loadStore() {
|
|
7379
8219
|
try {
|
|
7380
|
-
return JSON.parse(
|
|
8220
|
+
return JSON.parse(readFileSync8(STORE_PATH2, "utf8"));
|
|
7381
8221
|
} catch {
|
|
7382
8222
|
return null;
|
|
7383
8223
|
}
|
|
7384
8224
|
}
|
|
7385
8225
|
function saveStore(s) {
|
|
7386
|
-
writeFileSync7(
|
|
8226
|
+
writeFileSync7(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
|
|
7387
8227
|
}
|
|
7388
8228
|
function clearStore() {
|
|
7389
8229
|
try {
|
|
7390
|
-
|
|
8230
|
+
unlinkSync3(STORE_PATH2);
|
|
7391
8231
|
} catch {
|
|
7392
8232
|
}
|
|
7393
8233
|
}
|
|
7394
8234
|
function loadClientId() {
|
|
7395
8235
|
try {
|
|
7396
|
-
return JSON.parse(
|
|
8236
|
+
return JSON.parse(readFileSync8(CLIENT_PATH, "utf8")).clientId;
|
|
7397
8237
|
} catch {
|
|
7398
8238
|
return null;
|
|
7399
8239
|
}
|
|
@@ -7428,6 +8268,15 @@ var sqliteIso = (ts) => {
|
|
|
7428
8268
|
const ms = sqliteMs(ts);
|
|
7429
8269
|
return Number.isNaN(ms) ? ts : new Date(ms).toISOString();
|
|
7430
8270
|
};
|
|
8271
|
+
function stormFacts(db2) {
|
|
8272
|
+
if (!db2.stormActive()) return null;
|
|
8273
|
+
const since = db2.currentStormStartedAt();
|
|
8274
|
+
const recs = since ? db2.stormProbeRecordsSince(since) : [];
|
|
8275
|
+
return {
|
|
8276
|
+
reason: lastProbeReason(recs) === "limit" ? "limit" : "provider",
|
|
8277
|
+
hint: recs.length ? recs[recs.length - 1].resetHint ?? null : null
|
|
8278
|
+
};
|
|
8279
|
+
}
|
|
7431
8280
|
function roySnapshot(db2, now = Date.now(), overmindLog = [], paused = false, daemonVersion = null, restart = null, selfHost = null) {
|
|
7432
8281
|
const all = db2.listRuns();
|
|
7433
8282
|
const c = (s) => all.filter((r) => r.status === s).length;
|
|
@@ -7437,11 +8286,11 @@ function roySnapshot(db2, now = Date.now(), overmindLog = [], paused = false, da
|
|
|
7437
8286
|
if (r.status === "done" || r.status === "stopped" || r.status === "failed") return sqliteMs(r.updated_at) >= now - DAY_MS;
|
|
7438
8287
|
return true;
|
|
7439
8288
|
});
|
|
7440
|
-
const
|
|
8289
|
+
const storm = stormFacts(db2);
|
|
8290
|
+
const stormActive = storm !== null;
|
|
7441
8291
|
const stormSince = db2.currentStormStartedAt();
|
|
7442
|
-
const
|
|
7443
|
-
const
|
|
7444
|
-
const stormResetHint = stormRecs.length ? stormRecs[stormRecs.length - 1].resetHint ?? null : null;
|
|
8292
|
+
const stormReason = storm?.reason ?? null;
|
|
8293
|
+
const stormResetHint = storm?.hint ?? null;
|
|
7445
8294
|
return {
|
|
7446
8295
|
metrics: {
|
|
7447
8296
|
running: c("running"),
|
|
@@ -8724,7 +9573,17 @@ function daemonStatus(deps) {
|
|
|
8724
9573
|
const counts = {};
|
|
8725
9574
|
for (const r of deps.db.listRuns()) counts[r.status] = (counts[r.status] ?? 0) + 1;
|
|
8726
9575
|
const live = deps.liveSessionIds?.() ?? [];
|
|
9576
|
+
const ver = deps.daemonVersion?.() ?? null;
|
|
9577
|
+
const queuedReady = deps.db.listRuns("queued").filter((r) => deps.db.unmetDeps(r.depends_on).length === 0).length;
|
|
8727
9578
|
return {
|
|
9579
|
+
api: deps.api ?? TASKLESS_API,
|
|
9580
|
+
queuedReady,
|
|
9581
|
+
startedAt: ver?.startedAt ?? null,
|
|
9582
|
+
latestVersion: ver?.latestVersion ?? null,
|
|
9583
|
+
link: deps.link?.() ?? null,
|
|
9584
|
+
questions: deps.db.openQuestions().length,
|
|
9585
|
+
maxDrones: deps.maxDrones?.() ?? null,
|
|
9586
|
+
storm: stormFacts(deps.db),
|
|
8728
9587
|
hostId: deps.selfHost?.hostId ?? null,
|
|
8729
9588
|
hostName: deps.selfHost?.hostName ?? null,
|
|
8730
9589
|
backend: SWARM_BACKEND,
|
|
@@ -8734,7 +9593,9 @@ function daemonStatus(deps) {
|
|
|
8734
9593
|
counts,
|
|
8735
9594
|
live,
|
|
8736
9595
|
version: PACKAGE_VERSION,
|
|
8737
|
-
repos: deps.repoMap?.() ?? []
|
|
9596
|
+
repos: deps.repoMap?.() ?? [],
|
|
9597
|
+
// TASWM-324: «нас не пускают» — то же состояние процесса, что кричит рамкой в лог.
|
|
9598
|
+
authDenied: authStateLine()
|
|
8738
9599
|
};
|
|
8739
9600
|
}
|
|
8740
9601
|
function eventsStream(db2, narratorLog, control, daemonVersion, restart, liveSessionIds, selfHost) {
|
|
@@ -8834,7 +9695,12 @@ function makeHandler(deps) {
|
|
|
8834
9695
|
if (p === "/merge-gate") {
|
|
8835
9696
|
const ref = url.searchParams.get("pr") ?? "";
|
|
8836
9697
|
if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
|
|
8837
|
-
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
|
|
9698
|
+
const resolve4 = deps.mergeGate ?? makeMergeGateResolver({
|
|
9699
|
+
listRuns: () => deps.db.listRuns(),
|
|
9700
|
+
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
9701
|
+
autoVerify: SWARM_AUTO_VERIFY
|
|
9702
|
+
// TASWM-344
|
|
9703
|
+
});
|
|
8838
9704
|
return Response.json(await resolve4(ref));
|
|
8839
9705
|
}
|
|
8840
9706
|
if (p === "/resources") return Response.json(hostResourceSnapshot());
|
|
@@ -8993,15 +9859,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
8993
9859
|
|
|
8994
9860
|
// src/launcher.ts
|
|
8995
9861
|
import { spawn as spawn2 } from "child_process";
|
|
8996
|
-
import { closeSync as closeSync3, existsSync as existsSync7, mkdirSync as mkdirSync7, openSync as openSync3, readdirSync as
|
|
9862
|
+
import { closeSync as closeSync3, existsSync as existsSync7, mkdirSync as mkdirSync7, openSync as openSync3, readdirSync as readdirSync6 } from "fs";
|
|
8997
9863
|
import { createRequire } from "module";
|
|
8998
|
-
import { homedir as
|
|
8999
|
-
import { dirname as dirname3, join as
|
|
9864
|
+
import { homedir as homedir3 } from "os";
|
|
9865
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
9000
9866
|
import { fileURLToPath } from "url";
|
|
9001
9867
|
|
|
9002
9868
|
// src/droneHome.ts
|
|
9003
|
-
import { lstatSync, mkdirSync as mkdirSync6, readdirSync as
|
|
9004
|
-
import { join as
|
|
9869
|
+
import { lstatSync, mkdirSync as mkdirSync6, readdirSync as readdirSync5, rmSync, statSync as statSync6, symlinkSync as symlinkSync2 } from "fs";
|
|
9870
|
+
import { join as join10, relative, sep } from "path";
|
|
9005
9871
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
9006
9872
|
function droneHomeDeny(env = process.env) {
|
|
9007
9873
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -9020,28 +9886,28 @@ function buildDroneHome(opts) {
|
|
|
9020
9886
|
const deny = opts.deny ?? droneHomeDeny();
|
|
9021
9887
|
const platform = opts.platform ?? process.platform;
|
|
9022
9888
|
mkdirSync6(droneHome, { recursive: true, mode: 448 });
|
|
9023
|
-
const wanted = new Set(mirroredEntries(
|
|
9889
|
+
const wanted = new Set(mirroredEntries(readdirSync5(realHome), deny, platform));
|
|
9024
9890
|
const rel = relative(realHome, droneHome);
|
|
9025
9891
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
9026
|
-
for (const existing of
|
|
9892
|
+
for (const existing of readdirSync5(droneHome)) {
|
|
9027
9893
|
if (wanted.has(existing)) continue;
|
|
9028
|
-
const p =
|
|
9894
|
+
const p = join10(droneHome, existing);
|
|
9029
9895
|
try {
|
|
9030
9896
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
9031
9897
|
} catch {
|
|
9032
9898
|
}
|
|
9033
9899
|
}
|
|
9034
9900
|
for (const name of wanted) {
|
|
9035
|
-
const link =
|
|
9901
|
+
const link = join10(droneHome, name);
|
|
9036
9902
|
try {
|
|
9037
9903
|
lstatSync(link);
|
|
9038
9904
|
continue;
|
|
9039
9905
|
} catch {
|
|
9040
9906
|
}
|
|
9041
|
-
const target =
|
|
9907
|
+
const target = join10(realHome, name);
|
|
9042
9908
|
let isDir;
|
|
9043
9909
|
try {
|
|
9044
|
-
isDir =
|
|
9910
|
+
isDir = statSync6(target).isDirectory();
|
|
9045
9911
|
} catch {
|
|
9046
9912
|
continue;
|
|
9047
9913
|
}
|
|
@@ -9059,7 +9925,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
9059
9925
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
9060
9926
|
];
|
|
9061
9927
|
function realPathEnvPins(realHome) {
|
|
9062
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
9928
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join10(realHome, ...segments)]));
|
|
9063
9929
|
}
|
|
9064
9930
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
9065
9931
|
return [
|
|
@@ -9157,7 +10023,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
9157
10023
|
const env = { ...base3 };
|
|
9158
10024
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
9159
10025
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
9160
|
-
env.TURBO_CACHE_DIR =
|
|
10026
|
+
env.TURBO_CACHE_DIR = join11(worktree, ".turbo", "cache");
|
|
9161
10027
|
}
|
|
9162
10028
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
9163
10029
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -9185,20 +10051,20 @@ var DAEMON_STATE_ENV = [
|
|
|
9185
10051
|
"TASKLESS_TOKEN"
|
|
9186
10052
|
];
|
|
9187
10053
|
function bundledRoleDir(kind) {
|
|
9188
|
-
return
|
|
10054
|
+
return join11(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
9189
10055
|
}
|
|
9190
10056
|
function roleDir(worktree, kind) {
|
|
9191
|
-
return
|
|
10057
|
+
return join11(worktree, ".claude", "roles", kind);
|
|
9192
10058
|
}
|
|
9193
10059
|
function roleDirs(worktree, kind) {
|
|
9194
10060
|
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
|
|
9195
10061
|
}
|
|
9196
10062
|
function nextRunLogPath(runLogsDir, runId) {
|
|
9197
|
-
const dir =
|
|
10063
|
+
const dir = join11(runLogsDir, String(runId));
|
|
9198
10064
|
mkdirSync7(dir, { recursive: true });
|
|
9199
10065
|
let files = [];
|
|
9200
10066
|
try {
|
|
9201
|
-
files =
|
|
10067
|
+
files = readdirSync6(dir);
|
|
9202
10068
|
} catch {
|
|
9203
10069
|
}
|
|
9204
10070
|
let next = 0;
|
|
@@ -9206,7 +10072,7 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
9206
10072
|
const m = /^(\d+)\.json/.exec(f);
|
|
9207
10073
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
9208
10074
|
}
|
|
9209
|
-
return
|
|
10075
|
+
return join11(dir, `${next}.json`);
|
|
9210
10076
|
}
|
|
9211
10077
|
function openNextRunLog(runLogsDir, runId) {
|
|
9212
10078
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -9274,6 +10140,9 @@ function makeLauncher(cfg) {
|
|
|
9274
10140
|
SWARM_RUN_NAME: run.name,
|
|
9275
10141
|
...run.workspace_id ? { SWARM_WORKSPACE_ID: run.workspace_id } : {},
|
|
9276
10142
|
...run.reviews_run_id != null ? { SWARM_REVIEWS_RUN_ID: String(run.reviews_run_id) } : {},
|
|
10143
|
+
// TASWM-344: та же роль, что у SWARM_REVIEWS_RUN_ID выше, для верификатора — без неё
|
|
10144
|
+
// его done() в client-режиме пошёл бы веткой обычной сдачи PR, и вердикт пропал бы.
|
|
10145
|
+
...run.verifies_run_id != null ? { SWARM_VERIFIES_RUN_ID: String(run.verifies_run_id) } : {},
|
|
9277
10146
|
...run.hero_run_id != null ? { SWARM_HERO_RUN_ID: String(run.hero_run_id) } : {}
|
|
9278
10147
|
}
|
|
9279
10148
|
};
|
|
@@ -9284,11 +10153,11 @@ function makeLauncher(cfg) {
|
|
|
9284
10153
|
} catch (e) {
|
|
9285
10154
|
console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0441\u0438\u043B\u0438\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
|
|
9286
10155
|
}
|
|
9287
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
10156
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir3();
|
|
9288
10157
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
9289
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(
|
|
10158
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join11(cfg.droneIsolation.swarmHome, "token"))) {
|
|
9290
10159
|
console.error(
|
|
9291
|
-
`[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 ${
|
|
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 ${join11(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.`
|
|
9292
10161
|
);
|
|
9293
10162
|
}
|
|
9294
10163
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -9343,14 +10212,14 @@ function makeLauncher(cfg) {
|
|
|
9343
10212
|
init_droneChannel();
|
|
9344
10213
|
|
|
9345
10214
|
// src/git.ts
|
|
9346
|
-
import { existsSync as existsSync8, readdirSync as
|
|
10215
|
+
import { existsSync as existsSync8, readdirSync as readdirSync7, statSync as statSync7 } from "fs";
|
|
9347
10216
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
9348
|
-
import { isAbsolute
|
|
10217
|
+
import { isAbsolute, join as join12, sep as sep2 } from "path";
|
|
9349
10218
|
import { $ as $5 } from "execa";
|
|
9350
10219
|
init_swarmFiles();
|
|
9351
10220
|
function isLinkedWorktree(dir) {
|
|
9352
10221
|
try {
|
|
9353
|
-
return
|
|
10222
|
+
return statSync7(join12(dir, ".git")).isFile();
|
|
9354
10223
|
} catch {
|
|
9355
10224
|
return false;
|
|
9356
10225
|
}
|
|
@@ -9405,7 +10274,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
9405
10274
|
|
|
9406
10275
|
// src/attachments.ts
|
|
9407
10276
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
9408
|
-
import { join as
|
|
10277
|
+
import { join as join13 } from "path";
|
|
9409
10278
|
init_swarmFiles();
|
|
9410
10279
|
var ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
|
|
9411
10280
|
var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
|
|
@@ -9417,28 +10286,29 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
|
|
|
9417
10286
|
|
|
9418
10287
|
// src/shotUpload.ts
|
|
9419
10288
|
import { readFile as fsReadFile } from "fs/promises";
|
|
9420
|
-
import { basename as basename3, isAbsolute as
|
|
10289
|
+
import { basename as basename3, isAbsolute as isAbsolute2, join as join14 } from "path";
|
|
9421
10290
|
var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
9422
10291
|
|
|
9423
10292
|
// src/taskless.ts
|
|
9424
10293
|
init_config();
|
|
9425
10294
|
init_agentToken();
|
|
10295
|
+
init_linkFailure();
|
|
9426
10296
|
|
|
9427
10297
|
// src/workspace.ts
|
|
9428
10298
|
init_config();
|
|
9429
|
-
import { readFileSync as
|
|
9430
|
-
import { join as
|
|
9431
|
-
var WORKSPACE_PATH =
|
|
10299
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
|
|
10300
|
+
import { join as join15 } from "path";
|
|
10301
|
+
var WORKSPACE_PATH = join15(SWARM_HOME, "workspace.json");
|
|
9432
10302
|
function loadWorkspace() {
|
|
9433
10303
|
try {
|
|
9434
|
-
const w = JSON.parse(
|
|
10304
|
+
const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
|
|
9435
10305
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
9436
10306
|
} catch {
|
|
9437
10307
|
return null;
|
|
9438
10308
|
}
|
|
9439
10309
|
}
|
|
9440
10310
|
function saveWorkspace(w) {
|
|
9441
|
-
|
|
10311
|
+
writeFileSync8(WORKSPACE_PATH, JSON.stringify(w, null, 2), { mode: 384 });
|
|
9442
10312
|
}
|
|
9443
10313
|
function clearWorkspace() {
|
|
9444
10314
|
try {
|
|
@@ -9476,6 +10346,7 @@ function parseJsonContent(res) {
|
|
|
9476
10346
|
return null;
|
|
9477
10347
|
}
|
|
9478
10348
|
}
|
|
10349
|
+
var TASKLESS_CONNECT_TIMEOUT_MS = 1e4;
|
|
9479
10350
|
async function resolveApiToken(api, explicit) {
|
|
9480
10351
|
if (explicit) return explicit;
|
|
9481
10352
|
const agent = loadAgentToken();
|
|
@@ -9503,22 +10374,37 @@ async function connectSwarmMcp(cfg) {
|
|
|
9503
10374
|
const api = cfg?.api ?? TASKLESS_API;
|
|
9504
10375
|
const workspace = cfg?.workspace ?? resolvedWorkspaceId();
|
|
9505
10376
|
const token = await resolveApiToken(api, cfg?.token);
|
|
9506
|
-
if (!token)
|
|
10377
|
+
if (!token) {
|
|
10378
|
+
swarmConnectFailure = "\u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u0430 \u0440\u043E\u044F \u2014 \u043D\u0443\u0436\u0435\u043D \u0432\u0445\u043E\u0434: swarm login";
|
|
10379
|
+
return null;
|
|
10380
|
+
}
|
|
9507
10381
|
const url = new URL(mcpEndpoint(api));
|
|
9508
10382
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
9509
10383
|
requestInit: { headers: { Authorization: `Bearer ${token}` } }
|
|
9510
10384
|
});
|
|
9511
10385
|
const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
|
|
9512
10386
|
try {
|
|
9513
|
-
await client.connect(transport);
|
|
10387
|
+
await client.connect(transport, { timeout: TASKLESS_CONNECT_TIMEOUT_MS });
|
|
9514
10388
|
} catch (e) {
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
10389
|
+
if (isRevokedTokenError(e)) {
|
|
10390
|
+
swarmConnectFailure = revokedTokenLine();
|
|
10391
|
+
const shout = noteTokenRejected();
|
|
10392
|
+
if (shout) console.error(`${shout}
|
|
10393
|
+
\u0421\u043E\u0431\u044B\u0442\u0438\u044F \u043F\u043E\u043A\u0430 \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox \u0438 \u0443\u0435\u0434\u0443\u0442, \u043A\u043E\u0433\u0434\u0430 \u0434\u043E\u0441\u0442\u0443\u043F \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F.`);
|
|
10394
|
+
} else {
|
|
10395
|
+
swarmConnectFailure = describeError(e);
|
|
10396
|
+
console.warn(`Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${swarmConnectFailure}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`);
|
|
10397
|
+
}
|
|
9518
10398
|
return null;
|
|
9519
10399
|
}
|
|
10400
|
+
swarmConnectFailure = null;
|
|
10401
|
+
noteTokenAccepted();
|
|
9520
10402
|
return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
|
|
9521
10403
|
}
|
|
10404
|
+
var swarmConnectFailure = null;
|
|
10405
|
+
function lastSwarmConnectFailure() {
|
|
10406
|
+
return swarmConnectFailure;
|
|
10407
|
+
}
|
|
9522
10408
|
function swarmToolArgs(name, args, workspace) {
|
|
9523
10409
|
if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
|
|
9524
10410
|
return { workspaceId: workspace, ...args };
|
|
@@ -9526,8 +10412,10 @@ function swarmToolArgs(name, args, workspace) {
|
|
|
9526
10412
|
var WS_SCOPED_SWARM_TOOLS = /* @__PURE__ */ new Set(["swarm_run_upsert", "get_task"]);
|
|
9527
10413
|
|
|
9528
10414
|
// src/mcpResilient.ts
|
|
10415
|
+
init_linkFailure();
|
|
9529
10416
|
var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
|
|
9530
10417
|
var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
|
|
10418
|
+
var DEFAULT_RETRY_DELAYS_MS = [200, 800];
|
|
9531
10419
|
function errorResult(reason) {
|
|
9532
10420
|
return { isError: true, content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }] };
|
|
9533
10421
|
}
|
|
@@ -9535,11 +10423,16 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
9535
10423
|
const connect = opts.connect ?? (() => connectSwarmMcp());
|
|
9536
10424
|
const backoff = opts.backoffMs?.length ? opts.backoffMs : DEFAULT_BACKOFF_MS;
|
|
9537
10425
|
const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
|
|
10426
|
+
const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
10427
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
9538
10428
|
let caller = null;
|
|
9539
10429
|
let lastTryAt = 0;
|
|
9540
10430
|
let failures = 0;
|
|
9541
10431
|
let downSince = null;
|
|
9542
10432
|
let downReported = false;
|
|
10433
|
+
let lastConnectReason = null;
|
|
10434
|
+
let lastOkAt = null;
|
|
10435
|
+
let lastCallReason = null;
|
|
9543
10436
|
function maybeReportDown() {
|
|
9544
10437
|
if (downReported || downSince === null) return;
|
|
9545
10438
|
const downMs = Date.now() - downSince;
|
|
@@ -9547,45 +10440,65 @@ function resilientSwarmMcp(opts = {}) {
|
|
|
9547
10440
|
downReported = true;
|
|
9548
10441
|
opts.onDown?.(downMs);
|
|
9549
10442
|
}
|
|
9550
|
-
async function ensureCaller() {
|
|
10443
|
+
async function ensureCaller(force = false) {
|
|
9551
10444
|
if (caller) return caller;
|
|
9552
10445
|
const now = Date.now();
|
|
9553
10446
|
const wait = backoff[Math.max(0, Math.min(failures - 1, backoff.length - 1))];
|
|
9554
|
-
if (lastTryAt !== 0 && now - lastTryAt < wait) return null;
|
|
9555
|
-
lastTryAt = now;
|
|
10447
|
+
if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
|
|
10448
|
+
if (!force) lastTryAt = now;
|
|
9556
10449
|
let fresh = null;
|
|
9557
10450
|
try {
|
|
9558
10451
|
fresh = await connect();
|
|
9559
|
-
|
|
10452
|
+
if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
|
|
10453
|
+
} catch (e) {
|
|
9560
10454
|
fresh = null;
|
|
10455
|
+
lastConnectReason = describeError(e);
|
|
9561
10456
|
}
|
|
9562
10457
|
if (fresh) {
|
|
9563
10458
|
caller = fresh;
|
|
9564
10459
|
failures = 0;
|
|
10460
|
+
lastConnectReason = null;
|
|
9565
10461
|
if (downReported) opts.onUp?.();
|
|
9566
10462
|
downSince = null;
|
|
9567
10463
|
downReported = false;
|
|
9568
10464
|
return caller;
|
|
9569
10465
|
}
|
|
9570
|
-
failures += 1;
|
|
10466
|
+
if (!force) failures += 1;
|
|
9571
10467
|
if (downSince === null) downSince = now;
|
|
9572
10468
|
return null;
|
|
9573
10469
|
}
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
10470
|
+
const call = async (name, args) => {
|
|
10471
|
+
for (let attempt = 0; ; attempt++) {
|
|
10472
|
+
const active = await ensureCaller(attempt > 0);
|
|
10473
|
+
if (!active) {
|
|
10474
|
+
maybeReportDown();
|
|
10475
|
+
return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
|
|
10476
|
+
}
|
|
10477
|
+
try {
|
|
10478
|
+
const res = await active(name, args);
|
|
10479
|
+
lastOkAt = Date.now();
|
|
10480
|
+
lastCallReason = null;
|
|
10481
|
+
return res;
|
|
10482
|
+
} catch (e) {
|
|
10483
|
+
caller = null;
|
|
10484
|
+
if (downSince === null) downSince = Date.now();
|
|
10485
|
+
const { kind, detail: reason } = explainLinkFailure(e);
|
|
10486
|
+
lastCallReason = reason;
|
|
10487
|
+
const delay = retryDelays[attempt];
|
|
10488
|
+
if (delay === void 0 || kind !== "offline") {
|
|
10489
|
+
maybeReportDown();
|
|
10490
|
+
return errorResult(reason);
|
|
10491
|
+
}
|
|
10492
|
+
opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
|
|
10493
|
+
await sleep3(delay);
|
|
10494
|
+
}
|
|
9587
10495
|
}
|
|
9588
10496
|
};
|
|
10497
|
+
return Object.assign(call, {
|
|
10498
|
+
// Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
|
|
10499
|
+
// подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
|
|
10500
|
+
link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
|
|
10501
|
+
});
|
|
9589
10502
|
}
|
|
9590
10503
|
|
|
9591
10504
|
// src/repoScout.ts
|
|
@@ -9972,81 +10885,8 @@ async function runShotCli(argv, deps = {}) {
|
|
|
9972
10885
|
|
|
9973
10886
|
// src/cli.ts
|
|
9974
10887
|
init_daemonLink();
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
import { existsSync as existsSync9, readFileSync as readFileSync12, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
9978
|
-
import { join as join18 } from "path";
|
|
9979
|
-
function pauseStatePath(home) {
|
|
9980
|
-
return join18(home, "pause.json");
|
|
9981
|
-
}
|
|
9982
|
-
var ACTIVE = { paused: false, at: "", source: "cli" };
|
|
9983
|
-
function loadPauseState(home, io = {}) {
|
|
9984
|
-
const exists = io.exists ?? existsSync9;
|
|
9985
|
-
const readFile2 = io.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
9986
|
-
const path = pauseStatePath(home);
|
|
9987
|
-
if (!exists(path)) return ACTIVE;
|
|
9988
|
-
try {
|
|
9989
|
-
const parsed = JSON.parse(readFile2(path));
|
|
9990
|
-
if (parsed.paused !== true) return ACTIVE;
|
|
9991
|
-
return {
|
|
9992
|
-
paused: true,
|
|
9993
|
-
at: typeof parsed.at === "string" ? parsed.at : "",
|
|
9994
|
-
source: parsed.source === "web" || parsed.source === "remote" ? parsed.source : "cli",
|
|
9995
|
-
...typeof parsed.reason === "string" && parsed.reason.trim() ? { reason: parsed.reason.trim() } : {}
|
|
9996
|
-
};
|
|
9997
|
-
} catch {
|
|
9998
|
-
return ACTIVE;
|
|
9999
|
-
}
|
|
10000
|
-
}
|
|
10001
|
-
function savePauseState(home, state, io = {}) {
|
|
10002
|
-
const path = pauseStatePath(home);
|
|
10003
|
-
if (!state.paused) {
|
|
10004
|
-
const remove = io.remove ?? ((p) => existsSync9(p) && unlinkSync5(p));
|
|
10005
|
-
remove(path);
|
|
10006
|
-
return;
|
|
10007
|
-
}
|
|
10008
|
-
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
10009
|
-
writeFileSync10(`${p}.tmp`, data);
|
|
10010
|
-
renameSync5(`${p}.tmp`, p);
|
|
10011
|
-
});
|
|
10012
|
-
writeFile3(path, JSON.stringify(state, null, 2));
|
|
10013
|
-
}
|
|
10014
|
-
function makePauseState(source, reason, now = Date.now()) {
|
|
10015
|
-
return {
|
|
10016
|
-
paused: true,
|
|
10017
|
-
at: new Date(now).toISOString(),
|
|
10018
|
-
source,
|
|
10019
|
-
...reason && reason.trim() ? { reason: reason.trim() } : {}
|
|
10020
|
-
};
|
|
10021
|
-
}
|
|
10022
|
-
function pauseText(state, now = Date.now()) {
|
|
10023
|
-
if (!state?.paused) return "\u0440\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u0435\u043D \u2014 \u0431\u0435\u0440\u0451\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443";
|
|
10024
|
-
const since = state.at ? sinceText(state.at, now) : "";
|
|
10025
|
-
const who = { cli: "\u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435", web: "\u0441 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0439 \u043C\u043E\u0440\u0434\u044B", remote: "\u0438\u0437 Taskless" }[state.source];
|
|
10026
|
-
const why = state.reason ? ` \u2014 ${state.reason}` : "";
|
|
10027
|
-
return `\u0440\u043E\u0439 \u041D\u0410 \u041F\u0410\u0423\u0417\u0415${since} (${who})${why}`;
|
|
10028
|
-
}
|
|
10029
|
-
function sinceText(at, now) {
|
|
10030
|
-
const ms = Date.parse(at);
|
|
10031
|
-
if (Number.isNaN(ms)) return "";
|
|
10032
|
-
const min = Math.max(0, Math.round((now - ms) / 6e4));
|
|
10033
|
-
if (min < 1) return " (\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E)";
|
|
10034
|
-
if (min < 60) return ` (${min} \u043C\u0438\u043D)`;
|
|
10035
|
-
return ` (${Math.round(min / 60)} \u0447)`;
|
|
10036
|
-
}
|
|
10037
|
-
|
|
10038
|
-
// src/heartbeat.ts
|
|
10039
|
-
import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
10040
|
-
function readHeartbeat(path) {
|
|
10041
|
-
try {
|
|
10042
|
-
const ms = Number(readFileSync13(path, "utf8").trim());
|
|
10043
|
-
return Number.isFinite(ms) ? ms : null;
|
|
10044
|
-
} catch {
|
|
10045
|
-
return null;
|
|
10046
|
-
}
|
|
10047
|
-
}
|
|
10048
|
-
|
|
10049
|
-
// src/cli.ts
|
|
10888
|
+
init_pauseState();
|
|
10889
|
+
init_heartbeat();
|
|
10050
10890
|
init_src();
|
|
10051
10891
|
init_version();
|
|
10052
10892
|
function takeRepo(argv) {
|
|
@@ -10159,14 +10999,24 @@ async function runLoginFlow(db2, opts) {
|
|
|
10159
10999
|
const web = startWeb({ db: db2 });
|
|
10160
11000
|
try {
|
|
10161
11001
|
let agent = loadAgentToken();
|
|
11002
|
+
if (agent && opts.force) {
|
|
11003
|
+
console.log("--force: \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E, \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u0441\u0442\u0430\u0440\u044B\u0439 \u0442\u043E\u043A\u0435\u043D.");
|
|
11004
|
+
agent = null;
|
|
11005
|
+
}
|
|
10162
11006
|
if (agent) {
|
|
10163
11007
|
const probe = await probeAgentToken({ api: TASKLESS_API, token: agent.token });
|
|
10164
|
-
if (probe === "
|
|
10165
|
-
console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0435\u043C (\u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E.");
|
|
10166
|
-
clearAgentToken();
|
|
10167
|
-
agent = null;
|
|
10168
|
-
} else {
|
|
11008
|
+
if (probe === "ok") {
|
|
10169
11009
|
console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u044E \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430.");
|
|
11010
|
+
} else {
|
|
11011
|
+
if (probe === "revoked") {
|
|
11012
|
+
console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (\u0438\u043B\u0438 \u0440\u043E\u0439 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E.");
|
|
11013
|
+
clearAgentToken();
|
|
11014
|
+
} else {
|
|
11015
|
+
console.log(
|
|
11016
|
+
"\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0434/\u043E\u0442\u043A\u0430\u0437) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E: \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043D\u0435\u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0437\u0430 \xAB\u0432\u0441\u0451 \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435\xBB \u043D\u0435\u043B\u044C\u0437\u044F."
|
|
11017
|
+
);
|
|
11018
|
+
}
|
|
11019
|
+
agent = null;
|
|
10170
11020
|
}
|
|
10171
11021
|
}
|
|
10172
11022
|
if (!agent) {
|
|
@@ -10201,13 +11051,16 @@ async function runLoginFlow(db2, opts) {
|
|
|
10201
11051
|
const minted = await mintAgentToken({
|
|
10202
11052
|
api: TASKLESS_API,
|
|
10203
11053
|
bearer: fresh.accessToken,
|
|
10204
|
-
label: thisHost.hostName
|
|
11054
|
+
label: thisHost.hostName,
|
|
11055
|
+
hostId: thisHost.hostId,
|
|
11056
|
+
hostName: thisHost.hostName
|
|
10205
11057
|
});
|
|
10206
11058
|
saveAgentToken({
|
|
10207
11059
|
token: minted.token,
|
|
10208
11060
|
...minted.tokenId ? { tokenId: minted.tokenId } : {},
|
|
10209
11061
|
issuedAt: Date.now(),
|
|
10210
|
-
api: TASKLESS_API
|
|
11062
|
+
api: TASKLESS_API,
|
|
11063
|
+
hostId: thisHost.hostId
|
|
10211
11064
|
});
|
|
10212
11065
|
} catch (e) {
|
|
10213
11066
|
console.error(
|
|
@@ -10220,7 +11073,9 @@ async function runLoginFlow(db2, opts) {
|
|
|
10220
11073
|
console.log(
|
|
10221
11074
|
`\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043F\u043E\u043B\u0443\u0447\u0435\u043D \u2192 ${SWARM_HOME}/agent.json (\u043F\u0440\u0430\u0432\u0430 \u0443\u0436\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u0447\u0435\u0441\u043A\u0438\u0445). \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u0441\u043A\u0438\u0439 OAuth \u0441\u0442\u0451\u0440\u0442 \u2014 \u0440\u043E\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0445\u043E\u0434\u0438\u0442 \u0442\u0432\u043E\u0438\u043C \u0442\u043E\u043A\u0435\u043D\u043E\u043C (TASWM-296).`
|
|
10222
11075
|
);
|
|
10223
|
-
console.log(
|
|
11076
|
+
console.log(
|
|
11077
|
+
`\u0442\u043E\u043A\u0435\u043D \u043F\u0440\u0438\u0432\u044F\u0437\u0430\u043D \u043A \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435 (${thisHost.hostName}) \u2014 \u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 \u043C\u0430\u0448\u0438\u043D\u0430\u0445 \u0440\u043E\u0439 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C. \u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043E\u0434\u043D\u0443 \u043C\u0430\u0448\u0438\u043D\u0443 \u2014 \u0432 \u043F\u0430\u0440\u043A\u0435 \u043C\u0430\u0448\u0438\u043D (/swarm \u2192 \u0424\u043B\u043E\u0442), \u0441\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u2014 \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439.`
|
|
11078
|
+
);
|
|
10224
11079
|
}
|
|
10225
11080
|
const call = await connectSwarmMcp();
|
|
10226
11081
|
if (!call) {
|
|
@@ -10334,7 +11189,7 @@ async function runDaemonLogs(argv) {
|
|
|
10334
11189
|
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.`);
|
|
10335
11190
|
return 1;
|
|
10336
11191
|
}
|
|
10337
|
-
if (!
|
|
11192
|
+
if (!existsSync15(DAEMON_LOG_PATH)) {
|
|
10338
11193
|
console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
|
|
10339
11194
|
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.");
|
|
10340
11195
|
return 1;
|
|
@@ -10518,16 +11373,22 @@ function usage() {
|
|
|
10518
11373
|
swarm doctor \u0442\u0430 \u0436\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0411\u0415\u0417 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438: \u0447\u0442\u043E \u043D\u0435 \u0442\u0430\u043A \u0438 \u043A\u0430\u043A \u0447\u0438\u043D\u0438\u0442\u044C
|
|
10519
11374
|
swarm update \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0440\u043E\u0439 \u0434\u043E \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 (\u0442\u0435\u043C \u0436\u0435 \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u043E\u043C,
|
|
10520
11375
|
\u043A\u0430\u043A\u0438\u043C \u043E\u043D \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D)
|
|
10521
|
-
swarm login [--workspace <slug|id>] [--no-open]
|
|
11376
|
+
swarm login [--workspace <slug|id>] [--no-open] [--force]
|
|
10522
11377
|
\u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441. \u0414\u0435\u043C\u043E\u043D\u0430 \u0433\u0430\u0441\u0438\u0442 \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442
|
|
10523
11378
|
\u0441\u0430\u043C\u0430 (\u0436\u0438\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043E\u0438\u0433\u0440\u044B\u0432\u0430\u044E\u0442) \u2014 \u043F\u0435\u0440\u0435\u043B\u043E\u0433\u0438\u043D \u044D\u0442\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430,
|
|
10524
|
-
\u0431\u0435\u0437 stop/start \u0440\u0443\u043A\u0430\u043C\u0438
|
|
11379
|
+
\u0431\u0435\u0437 stop/start \u0440\u0443\u043A\u0430\u043C\u0438. \u0422\u043E\u043A\u0435\u043D \u0432\u044B\u0434\u0430\u0451\u0442\u0441\u044F \u042D\u0422\u041E\u0419 \u043C\u0430\u0448\u0438\u043D\u0435: \u0432\u0445\u043E\u0434 \u0437\u0434\u0435\u0441\u044C
|
|
11380
|
+
\u043D\u0435 \u0432\u044B\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0440\u043E\u0439 \u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 (TASWM-324). --force \u0432\u0445\u043E\u0434\u0438\u0442 \u0437\u0430\u043D\u043E\u0432\u043E,
|
|
11381
|
+
\u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u0441\u0442\u0430\u0440\u044B\u0439 \u0442\u043E\u043A\u0435\u043D
|
|
10525
11382
|
swarm start [--repo <path>] [--foreground]
|
|
10526
11383
|
\u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C, \u0441\u043F\u0430\u0432\u043D\u0438\u0442 \u0438 \u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u043E\u0432.
|
|
10527
11384
|
\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0430\u0445\u043E\u0434\u0438\u0442 \u0441\u0430\u043C \u2014 \u043A\u043B\u043E\u043D\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 Taskless \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435;
|
|
10528
11385
|
--repo \u043D\u0443\u0436\u0435\u043D, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043B\u0435\u0436\u0430\u0442 \u0432 \u043D\u0435\u043E\u0431\u044B\u0447\u043D\u043E\u043C \u043C\u0435\u0441\u0442\u0435
|
|
10529
|
-
swarm status \
|
|
10530
|
-
\
|
|
11386
|
+
swarm status \u0420\u0410\u0411\u041E\u0422\u0410\u0415\u0422 \u041B\u0418 \u0420\u041E\u0419 \u2014 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0438 \u043F\u0435\u0440\u0432\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439. \u0414\u0430\u043B\u044C\u0448\u0435
|
|
11387
|
+
\u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438 \u0441\u043D\u0438\u0437\u0443 \u0432\u0432\u0435\u0440\u0445: \u0434\u0435\u043C\u043E\u043D, \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C (\u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439:
|
|
11388
|
+
\u0442\u043E\u043A\u0435\u043D/\u0441\u0435\u0442\u044C/\u043E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u043E\u0434\u0430) \u0438 \u0431\u0443\u0444\u0435\u0440, \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u043E\u0434 \u0441\u043B\u044B\u0448\u0430\u043B \u044D\u0442\u0443
|
|
11389
|
+
\u043C\u0430\u0448\u0438\u043D\u0443, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0437\u0430\u043D\u044F\u0442\u043E \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0441\u0442\u043E\u0438\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C.
|
|
11390
|
+
\u041F\u0440\u0438 \u043D\u0435\u043F\u043E\u043B\u0430\u0434\u043A\u0435 \u0433\u043E\u0432\u043E\u0440\u0438\u0442, \u0427\u0422\u041E \u0414\u0415\u041B\u0410\u0422\u042C. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0438 \u043A\u043E\u0433\u0434\u0430 \u043F\u0440\u043E\u0434
|
|
11391
|
+
\u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u0442\u043E\u0433\u0434\u0430 \u0447\u0435\u0441\u0442\u043D\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0432\u0438\u0434\u0438\u0442 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E
|
|
10531
11392
|
|
|
10532
11393
|
\u0414\u0410\u0422\u042C \u0420\u041E\u042E \u0420\u0410\u0411\u041E\u0422\u0423
|
|
10533
11394
|
swarm spawn <slug> [area] [\u0444\u043B\u0430\u0433\u0438 \u043D\u0438\u0436\u0435]
|
|
@@ -10549,6 +11410,8 @@ function usage() {
|
|
|
10549
11410
|
swarm merge-gate <pr> \u043C\u043E\u0436\u043D\u043E \u043B\u0438 \u043C\u0435\u0440\u0436\u0438\u0442\u044C \u044D\u0442\u043E\u0442 PR (\u0441\u0441\u044B\u043B\u043A\u0430 \u0438\u043B\u0438 \u043D\u043E\u043C\u0435\u0440): \u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0441
|
|
10550
11411
|
\u0430\u0432\u0442\u043E\u0440\u0435\u0432\u044C\u044E \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430. \u0412\u044B\u0445\u043E\u0434 1 = \u043D\u0435\u043B\u044C\u0437\u044F.
|
|
10551
11412
|
\u0422\u043E\u0442 \u0436\u0435 \u043E\u0442\u0432\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 \u0445\u0443\u043A, \u043F\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044B\u0432\u0430\u044E\u0449\u0438\u0439 gh pr merge
|
|
11413
|
+
swarm verdicts \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u044B \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u044B\u0445 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u043E\u0432: \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0437\u0430\u044F\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E \u043D\u0435
|
|
11414
|
+
\u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u043B\u043E\u0441\u044C \u0438\u043B\u0438 \u0441\u043B\u043E\u043C\u0430\u043B\u043E\u0441\u044C \u0440\u044F\u0434\u043E\u043C \u2014 \u043F\u043E \u0440\u043E\u044E \u0438 \u043F\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F\u043C
|
|
10552
11415
|
swarm questions \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0434\u0440\u043E\u043D\u043E\u0432
|
|
10553
11416
|
swarm answer <qid> <\u0442\u0435\u043A\u0441\u0442\u2026>
|
|
10554
11417
|
\u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430
|
|
@@ -10598,7 +11461,7 @@ function effectiveWorktreesDir() {
|
|
|
10598
11461
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
10599
11462
|
targetDir: WORKTREES_DIR,
|
|
10600
11463
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
10601
|
-
exists: (d) =>
|
|
11464
|
+
exists: (d) => existsSync15(d),
|
|
10602
11465
|
isNonEmptyDir: (d) => {
|
|
10603
11466
|
try {
|
|
10604
11467
|
return readdirSync9(d).length > 0;
|
|
@@ -10658,7 +11521,7 @@ async function runScoutCommand(repo) {
|
|
|
10658
11521
|
});
|
|
10659
11522
|
child.on("exit", (c) => resolve4(c ?? 1));
|
|
10660
11523
|
}),
|
|
10661
|
-
readOut: () =>
|
|
11524
|
+
readOut: () => existsSync15(outFile) ? readFileSync17(outFile, "utf8") : null,
|
|
10662
11525
|
clearOut: () => rmSync2(outFile, { force: true }),
|
|
10663
11526
|
report: (report) => client.reportRepoScout(repoFullName, report, name),
|
|
10664
11527
|
log: (line) => console.log(line)
|
|
@@ -10852,25 +11715,8 @@ if (cmd === "cancel") {
|
|
|
10852
11715
|
console.warn(NO_DAEMON_NOTE);
|
|
10853
11716
|
}
|
|
10854
11717
|
if (cmd === "status") {
|
|
10855
|
-
const
|
|
10856
|
-
|
|
10857
|
-
const local = loadPauseState(SWARM_HOME);
|
|
10858
|
-
console.log(`\u0434\u0435\u043C\u043E\u043D \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT} \u043C\u043E\u043B\u0447\u0438\u0442). \u041F\u043E\u0434\u043D\u044F\u0442\u044C: swarm start`);
|
|
10859
|
-
console.log(local.paused ? `${pauseText(local)} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435, \u0441\u043D\u044F\u0442\u044C: swarm resume` : "\u043F\u0430\u0443\u0437\u044B \u043D\u0435\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u0440\u0430\u0431\u043E\u0442\u0443");
|
|
10860
|
-
process.exit(1);
|
|
10861
|
-
}
|
|
10862
|
-
console.log(`\u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432 (v${st.version})${st.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${st.hostName}\xBB` : ""}, \u0431\u044D\u043A\u0435\u043D\u0434 ${st.backend}`);
|
|
10863
|
-
console.log(pauseText(st.pause ?? (st.paused ? { paused: true, at: "", source: "web" } : null)));
|
|
10864
|
-
if (st.restart?.phase === "waiting") {
|
|
10865
|
-
console.log(
|
|
10866
|
-
`\u0434\u0435\u043C\u043E\u043D \u0416\u0414\u0401\u0422 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (${st.restart.sessions.length}), \u0447\u0442\u043E\u0431\u044B ${st.restart.intent === "stop" ? "\u043F\u043E\u0433\u0430\u0441\u043D\u0443\u0442\u044C" : "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\u0441\u044F"} \u2014 \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043D\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F`
|
|
10867
|
-
);
|
|
10868
|
-
}
|
|
10869
|
-
const counts = Object.entries(st.counts).filter(([, n]) => n > 0);
|
|
10870
|
-
console.log(counts.length ? `\u043F\u0440\u043E\u0433\u043E\u043D\u044B: ${counts.map(([s, n]) => `${s} ${n}`).join(", ")}` : "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u043D\u0435\u0442");
|
|
10871
|
-
console.log(st.live.length ? `\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u0441\u0435\u0439\u0447\u0430\u0441: ${st.live.length} (#${st.live.join(", #")})` : "\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435\u0442");
|
|
10872
|
-
for (const line of repoMapLines(st.repos)) console.log(line);
|
|
10873
|
-
process.exit(0);
|
|
11718
|
+
const { runStatusCli: runStatusCli2 } = await Promise.resolve().then(() => (init_statusCli(), statusCli_exports));
|
|
11719
|
+
process.exit(await runStatusCli2(rawArgs));
|
|
10874
11720
|
}
|
|
10875
11721
|
if (cmd === "merge-gate") {
|
|
10876
11722
|
const ref = rawArgs[0];
|
|
@@ -10889,6 +11735,23 @@ if (cmd === "merge-gate") {
|
|
|
10889
11735
|
console.log(`${mergeGateLine(answer)}${who}`);
|
|
10890
11736
|
process.exit(answer.allowed ? 0 : 1);
|
|
10891
11737
|
}
|
|
11738
|
+
if (cmd === "verdicts") {
|
|
11739
|
+
const runs = (await makeCliStore(SWARM_REPO)).listRuns();
|
|
11740
|
+
const overall = verifyStats(runs);
|
|
11741
|
+
console.log(verifyStatsLine(overall));
|
|
11742
|
+
if (overall.total > 0) {
|
|
11743
|
+
console.log("");
|
|
11744
|
+
console.log("\u043F\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F\u043C (\u043A\u043E\u0433\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B\u0438):");
|
|
11745
|
+
for (const row of verifyStatsByDrone(runs)) {
|
|
11746
|
+
const pct = Math.round(row.negativeShare * 100);
|
|
11747
|
+
console.log(` ${row.name.padEnd(12)} \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u043E\u0432 ${row.total}, \u043E\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0445 ${row.negative} (${pct}%)`);
|
|
11748
|
+
}
|
|
11749
|
+
}
|
|
11750
|
+
const waiting = runs.filter((r) => r.status === "review" && r.kind === "drone" && r.pr_url && r.verify_verdict == null).length;
|
|
11751
|
+
if (waiting > 0) console.log(`
|
|
11752
|
+
PR \u0431\u0435\u0437 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430: ${waiting} \u2014 \u043F\u0440\u0438\u0451\u043C\u043A\u0443 \u043E\u043D\u0438 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0438`);
|
|
11753
|
+
process.exit(0);
|
|
11754
|
+
}
|
|
10892
11755
|
if (cmd === "pause" || cmd === "resume") {
|
|
10893
11756
|
const wantPause = cmd === "pause";
|
|
10894
11757
|
const reason = wantPause ? rawArgs.join(" ").trim() || null : null;
|
|
@@ -11149,7 +12012,8 @@ switch (cmd) {
|
|
|
11149
12012
|
case "login": {
|
|
11150
12013
|
const { workspace, rest } = takeWorkspace(rawArgs);
|
|
11151
12014
|
const noOpen = rest.includes("--no-open");
|
|
11152
|
-
const
|
|
12015
|
+
const force = rest.includes("--force");
|
|
12016
|
+
const code = await runLogin(db, { workspace, noOpen, force });
|
|
11153
12017
|
db.close();
|
|
11154
12018
|
process.exit(code);
|
|
11155
12019
|
}
|