@taskless-app/swarm 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -16
- package/dist/cli.js +1762 -616
- package/dist/overmind.js +1022 -243
- package/package.json +1 -1
package/dist/overmind.js
CHANGED
|
@@ -397,9 +397,14 @@ var init_windows = __esm({
|
|
|
397
397
|
});
|
|
398
398
|
|
|
399
399
|
// ../swarm-core/src/slug.ts
|
|
400
|
+
function isTaskSlug(s) {
|
|
401
|
+
return !!s && TASK_SLUG_RE.test(s);
|
|
402
|
+
}
|
|
403
|
+
var TASK_SLUG_RE;
|
|
400
404
|
var init_slug = __esm({
|
|
401
405
|
"../swarm-core/src/slug.ts"() {
|
|
402
406
|
"use strict";
|
|
407
|
+
TASK_SLUG_RE = /^(?:[A-Za-z0-9]{1,8}-\d+|#[A-Za-z0-9]{4,})$/;
|
|
403
408
|
}
|
|
404
409
|
});
|
|
405
410
|
|
|
@@ -419,14 +424,20 @@ function extractText(d) {
|
|
|
419
424
|
function sanitizeEventText(text) {
|
|
420
425
|
if (text == null) return null;
|
|
421
426
|
const cleaned = text.replace(ANSI_CSI_RE, "").replace(CONTROL_CHARS_RE, " ").replace(/ {2,}/g, " ").trim();
|
|
422
|
-
|
|
427
|
+
if (!cleaned.length) return null;
|
|
428
|
+
return cleaned.length <= EVENT_TEXT_MAX ? cleaned : clampEventText(cleaned);
|
|
429
|
+
}
|
|
430
|
+
function clampEventText(text) {
|
|
431
|
+
const note = ` \u2026 (\u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043E, \u0431\u044B\u043B\u043E ${text.length} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432)`;
|
|
432
|
+
return text.slice(0, Math.max(0, EVENT_TEXT_MAX - note.length)) + note;
|
|
423
433
|
}
|
|
424
|
-
var ANSI_CSI_RE, CONTROL_CHARS_RE;
|
|
434
|
+
var ANSI_CSI_RE, CONTROL_CHARS_RE, EVENT_TEXT_MAX;
|
|
425
435
|
var init_text = __esm({
|
|
426
436
|
"../swarm-core/src/text.ts"() {
|
|
427
437
|
"use strict";
|
|
428
438
|
ANSI_CSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
429
439
|
CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/g;
|
|
440
|
+
EVENT_TEXT_MAX = 16e3;
|
|
430
441
|
}
|
|
431
442
|
});
|
|
432
443
|
|
|
@@ -709,6 +720,50 @@ var init_hosts = __esm({
|
|
|
709
720
|
}
|
|
710
721
|
});
|
|
711
722
|
|
|
723
|
+
// ../swarm-core/src/hostCommands.ts
|
|
724
|
+
function isHostCommandKind(v) {
|
|
725
|
+
return typeof v === "string" && HOST_COMMAND_KINDS.includes(v);
|
|
726
|
+
}
|
|
727
|
+
function isHostCommandExpired(expiresAt, nowMs) {
|
|
728
|
+
if (!expiresAt) return false;
|
|
729
|
+
const at = Date.parse(expiresAt);
|
|
730
|
+
return Number.isFinite(at) && at <= nowMs;
|
|
731
|
+
}
|
|
732
|
+
function hostCommandLabel(cmd) {
|
|
733
|
+
const base3 = HOST_COMMAND_LABELS[cmd.kind];
|
|
734
|
+
if (cmd.kind === "set_max_drones" && cmd.maxDrones != null) return `${base3} \u2192 ${cmd.maxDrones}`;
|
|
735
|
+
if (cmd.kind === "stop_run" && cmd.runSlug) return `${base3} ${cmd.runSlug}`;
|
|
736
|
+
return base3;
|
|
737
|
+
}
|
|
738
|
+
var HOST_COMMAND_KINDS, HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, UPDATE_STEP_LABELS, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, HOST_COMMAND_LABELS, MAX_DRONES_LIMIT;
|
|
739
|
+
var init_hostCommands = __esm({
|
|
740
|
+
"../swarm-core/src/hostCommands.ts"() {
|
|
741
|
+
"use strict";
|
|
742
|
+
HOST_COMMAND_KINDS = ["pause", "resume", "restart", "update", "set_max_drones", "stop_run"];
|
|
743
|
+
HOST_COMMAND_STATUSES = ["pending", "delivered", "running", "done", "failed", "rejected", "expired"];
|
|
744
|
+
TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "rejected", "expired"]);
|
|
745
|
+
HOST_COMMAND_OPEN_STATUSES = HOST_COMMAND_STATUSES.filter((s) => !TERMINAL.has(s));
|
|
746
|
+
UPDATE_STEP_LABELS = {
|
|
747
|
+
fetch: "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434",
|
|
748
|
+
checkout: "\u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u044E\u0441\u044C \u043D\u0430 \u0441\u0432\u0435\u0436\u0438\u0439 main",
|
|
749
|
+
install: "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438",
|
|
750
|
+
build: "\u0441\u043E\u0431\u0438\u0440\u0430\u044E \u043F\u0430\u043A\u0435\u0442\u044B \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430",
|
|
751
|
+
restart: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u044E\u0441\u044C"
|
|
752
|
+
};
|
|
753
|
+
HOST_COMMAND_TTL_MS = 30 * 6e4;
|
|
754
|
+
HOST_COMMAND_KEEPALIVE_MS = 10 * 6e4;
|
|
755
|
+
HOST_COMMAND_LABELS = {
|
|
756
|
+
pause: "\u043F\u0430\u0443\u0437\u0430",
|
|
757
|
+
resume: "\u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C",
|
|
758
|
+
restart: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A",
|
|
759
|
+
update: "\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0434\u043E \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u043A\u043E\u0434\u0430",
|
|
760
|
+
set_max_drones: "\u0441\u043C\u0435\u043D\u0430 \u0447\u0438\u0441\u043B\u0430 \u0434\u0440\u043E\u043D\u043E\u0432",
|
|
761
|
+
stop_run: "\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430"
|
|
762
|
+
};
|
|
763
|
+
MAX_DRONES_LIMIT = 8;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
|
|
712
767
|
// ../swarm-core/src/relief.ts
|
|
713
768
|
var RELIEF_PAUSE_MARK;
|
|
714
769
|
var init_relief = __esm({
|
|
@@ -735,6 +790,7 @@ var init_src = __esm({
|
|
|
735
790
|
init_names();
|
|
736
791
|
init_baseBranch();
|
|
737
792
|
init_hosts();
|
|
793
|
+
init_hostCommands();
|
|
738
794
|
init_relief();
|
|
739
795
|
}
|
|
740
796
|
});
|
|
@@ -742,6 +798,9 @@ var init_src = __esm({
|
|
|
742
798
|
// src/config.ts
|
|
743
799
|
var config_exports = {};
|
|
744
800
|
__export(config_exports, {
|
|
801
|
+
DAEMON_LOG_CHECK_MS: () => DAEMON_LOG_CHECK_MS,
|
|
802
|
+
DAEMON_LOG_KEEP: () => DAEMON_LOG_KEEP,
|
|
803
|
+
DAEMON_LOG_MAX_BYTES: () => DAEMON_LOG_MAX_BYTES,
|
|
745
804
|
DAEMON_LOG_PATH: () => DAEMON_LOG_PATH,
|
|
746
805
|
DB_PATH: () => DB_PATH,
|
|
747
806
|
DRONE_CHANNEL_ENABLED: () => DRONE_CHANNEL_ENABLED,
|
|
@@ -754,6 +813,7 @@ __export(config_exports, {
|
|
|
754
813
|
DRONE_ISOLATION: () => DRONE_ISOLATION,
|
|
755
814
|
HEARTBEAT_PATH: () => HEARTBEAT_PATH,
|
|
756
815
|
HEARTBEAT_STALE_MS: () => HEARTBEAT_STALE_MS,
|
|
816
|
+
REPO_MAP_PATH: () => REPO_MAP_PATH,
|
|
757
817
|
REVIEW_CUTOFF_PATH: () => REVIEW_CUTOFF_PATH,
|
|
758
818
|
RUN_LOGS_DIR: () => RUN_LOGS_DIR,
|
|
759
819
|
SWARM_ANOMALY_FAILED_SPIKE_COUNT: () => SWARM_ANOMALY_FAILED_SPIKE_COUNT,
|
|
@@ -791,9 +851,11 @@ __export(config_exports, {
|
|
|
791
851
|
SWARM_RELIEF_MAX_FD_LOAD_FACTOR: () => SWARM_RELIEF_MAX_FD_LOAD_FACTOR,
|
|
792
852
|
SWARM_RELIEF_MIN_FREE_GB: () => SWARM_RELIEF_MIN_FREE_GB,
|
|
793
853
|
SWARM_REPO: () => SWARM_REPO,
|
|
854
|
+
SWARM_REPO_MAP_MS: () => SWARM_REPO_MAP_MS,
|
|
794
855
|
SWARM_REPO_PATHS: () => SWARM_REPO_PATHS,
|
|
795
856
|
SWARM_REVIEW_CAP: () => SWARM_REVIEW_CAP,
|
|
796
857
|
SWARM_SCAN_DEPTH: () => SWARM_SCAN_DEPTH,
|
|
858
|
+
SWARM_SCAN_ROOTS_RAW: () => SWARM_SCAN_ROOTS_RAW,
|
|
797
859
|
SWARM_SHOT_TIMEOUT_MS: () => SWARM_SHOT_TIMEOUT_MS,
|
|
798
860
|
SWARM_STORM_URL: () => SWARM_STORM_URL,
|
|
799
861
|
SWARM_SWEEP_INTERVAL_MS: () => SWARM_SWEEP_INTERVAL_MS,
|
|
@@ -893,7 +955,7 @@ function reviewCutoffAt() {
|
|
|
893
955
|
}
|
|
894
956
|
return at;
|
|
895
957
|
}
|
|
896
|
-
var SWARM_HOME, DB_PATH, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, 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_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, 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;
|
|
958
|
+
var SWARM_HOME, DB_PATH, 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_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;
|
|
897
959
|
var init_config = __esm({
|
|
898
960
|
"src/config.ts"() {
|
|
899
961
|
"use strict";
|
|
@@ -909,6 +971,9 @@ var init_config = __esm({
|
|
|
909
971
|
WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
|
|
910
972
|
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join2(SWARM_HOME, "run-logs");
|
|
911
973
|
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join2(SWARM_HOME, "overmind.log");
|
|
974
|
+
DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
|
|
975
|
+
DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
|
|
976
|
+
DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
|
|
912
977
|
DIST_BUILD = true;
|
|
913
978
|
SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
|
|
914
979
|
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join2(SWARM_HOME, "outbox.jsonl");
|
|
@@ -943,6 +1008,9 @@ var init_config = __esm({
|
|
|
943
1008
|
SWARM_REPO = process.env.SWARM_REPO || "";
|
|
944
1009
|
SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
|
|
945
1010
|
SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
|
|
1011
|
+
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
1012
|
+
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join2(SWARM_HOME, "repo-map.json");
|
|
1013
|
+
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
946
1014
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
947
1015
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
948
1016
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
@@ -1435,7 +1503,7 @@ var init_procList = __esm({
|
|
|
1435
1503
|
|
|
1436
1504
|
// src/procCleanup.ts
|
|
1437
1505
|
function sleep(ms) {
|
|
1438
|
-
return new Promise((
|
|
1506
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1439
1507
|
}
|
|
1440
1508
|
function parseLsofCwd(output) {
|
|
1441
1509
|
const rows = [];
|
|
@@ -1498,8 +1566,8 @@ function pathUnderInCmd(cmd, dir) {
|
|
|
1498
1566
|
if (before && !/["'\s=;]/.test(before)) continue;
|
|
1499
1567
|
const after = hay.slice(at + needle.length);
|
|
1500
1568
|
if (after && !after.startsWith("/")) continue;
|
|
1501
|
-
const
|
|
1502
|
-
return needle +
|
|
1569
|
+
const tail2 = after.match(/^(?:\/[^"';]*)?/)[0];
|
|
1570
|
+
return needle + tail2;
|
|
1503
1571
|
}
|
|
1504
1572
|
return null;
|
|
1505
1573
|
}
|
|
@@ -1653,16 +1721,16 @@ var init_procCleanup = __esm({
|
|
|
1653
1721
|
});
|
|
1654
1722
|
|
|
1655
1723
|
// src/git.ts
|
|
1656
|
-
import { existsSync as
|
|
1724
|
+
import { existsSync as existsSync8, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
|
|
1657
1725
|
import { appendFile, mkdir, readFile, rm } from "fs/promises";
|
|
1658
|
-
import { isAbsolute, join as
|
|
1726
|
+
import { isAbsolute as isAbsolute2, join as join12 } from "path";
|
|
1659
1727
|
import { $ as $2 } from "execa";
|
|
1660
1728
|
async function reclaimStaleWorktree(repo, worktree) {
|
|
1661
1729
|
await detachWorktree(repo, worktree);
|
|
1662
1730
|
}
|
|
1663
1731
|
function isLinkedWorktree(dir) {
|
|
1664
1732
|
try {
|
|
1665
|
-
return
|
|
1733
|
+
return statSync6(join12(dir, ".git")).isFile();
|
|
1666
1734
|
} catch {
|
|
1667
1735
|
return false;
|
|
1668
1736
|
}
|
|
@@ -1674,7 +1742,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
1674
1742
|
}
|
|
1675
1743
|
}
|
|
1676
1744
|
async function ensureWorktreeDirGone(worktree) {
|
|
1677
|
-
if (!
|
|
1745
|
+
if (!existsSync8(worktree)) return false;
|
|
1678
1746
|
if (!isLinkedWorktree(worktree)) {
|
|
1679
1747
|
console.error(
|
|
1680
1748
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -1689,7 +1757,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
1689
1757
|
);
|
|
1690
1758
|
return true;
|
|
1691
1759
|
}
|
|
1692
|
-
if (
|
|
1760
|
+
if (existsSync8(worktree)) {
|
|
1693
1761
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
1694
1762
|
}
|
|
1695
1763
|
return true;
|
|
@@ -1732,7 +1800,7 @@ async function addWorktree(repo, path, branch, opts = {}) {
|
|
|
1732
1800
|
}
|
|
1733
1801
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
1734
1802
|
if (busyAt) {
|
|
1735
|
-
const exists =
|
|
1803
|
+
const exists = existsSync8(busyAt);
|
|
1736
1804
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
1737
1805
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
1738
1806
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -1768,9 +1836,9 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
1768
1836
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse --git-common-dir`;
|
|
1769
1837
|
const raw = out.stdout.trim();
|
|
1770
1838
|
if (out.exitCode !== 0 || !raw) return false;
|
|
1771
|
-
const commonDir =
|
|
1772
|
-
const infoDir =
|
|
1773
|
-
const excludeFile =
|
|
1839
|
+
const commonDir = isAbsolute2(raw) ? raw : join12(worktree, raw);
|
|
1840
|
+
const infoDir = join12(commonDir, "info");
|
|
1841
|
+
const excludeFile = join12(infoDir, "exclude");
|
|
1774
1842
|
let current = "";
|
|
1775
1843
|
try {
|
|
1776
1844
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -1803,7 +1871,7 @@ async function worktreeDirty(path) {
|
|
|
1803
1871
|
async function repairMovedWorktrees(targetDir) {
|
|
1804
1872
|
const subdirs = (dir) => {
|
|
1805
1873
|
try {
|
|
1806
|
-
return readdirSync8(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) =>
|
|
1874
|
+
return readdirSync8(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join12(dir, d.name));
|
|
1807
1875
|
} catch {
|
|
1808
1876
|
return [];
|
|
1809
1877
|
}
|
|
@@ -1915,18 +1983,18 @@ __export(droneChannel_exports, {
|
|
|
1915
1983
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
1916
1984
|
});
|
|
1917
1985
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
1918
|
-
import { chmodSync, mkdirSync as mkdirSync5, readFileSync as
|
|
1986
|
+
import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
1919
1987
|
import { dirname as dirname3 } from "path";
|
|
1920
1988
|
import { serve } from "@hono/node-server";
|
|
1921
1989
|
function loadChannelSecret(path) {
|
|
1922
1990
|
try {
|
|
1923
|
-
const s =
|
|
1991
|
+
const s = readFileSync13(path, "utf8").trim();
|
|
1924
1992
|
if (s.length >= 32) return s;
|
|
1925
1993
|
} catch {
|
|
1926
1994
|
}
|
|
1927
1995
|
const secret = randomBytes(32).toString("hex");
|
|
1928
1996
|
mkdirSync5(dirname3(path), { recursive: true });
|
|
1929
|
-
|
|
1997
|
+
writeFileSync9(path, secret, { mode: 384 });
|
|
1930
1998
|
try {
|
|
1931
1999
|
chmodSync(path, 384);
|
|
1932
2000
|
} catch {
|
|
@@ -2133,8 +2201,8 @@ function startDroneChannel(deps, port) {
|
|
|
2133
2201
|
return { port, server };
|
|
2134
2202
|
}
|
|
2135
2203
|
function closeDroneChannel(server) {
|
|
2136
|
-
return new Promise((
|
|
2137
|
-
server.close(() =>
|
|
2204
|
+
return new Promise((resolve4) => {
|
|
2205
|
+
server.close(() => resolve4());
|
|
2138
2206
|
server.closeAllConnections?.();
|
|
2139
2207
|
});
|
|
2140
2208
|
}
|
|
@@ -2256,7 +2324,7 @@ var init_droneChannel = __esm({
|
|
|
2256
2324
|
|
|
2257
2325
|
// src/attachments.ts
|
|
2258
2326
|
import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
|
|
2259
|
-
import { join as
|
|
2327
|
+
import { join as join14 } from "path";
|
|
2260
2328
|
function attachmentsFromDoc(doc, origin) {
|
|
2261
2329
|
const out = [];
|
|
2262
2330
|
const walk2 = (node) => {
|
|
@@ -2347,7 +2415,7 @@ async function materializeAttachments(opts) {
|
|
|
2347
2415
|
};
|
|
2348
2416
|
const result = { files: [], skipped: [] };
|
|
2349
2417
|
if (opts.refs.length === 0) return result;
|
|
2350
|
-
const dir =
|
|
2418
|
+
const dir = join14(opts.worktree, ATTACHMENTS_DIR);
|
|
2351
2419
|
await io.removeDir(dir);
|
|
2352
2420
|
let dirReady = false;
|
|
2353
2421
|
let total = 0;
|
|
@@ -2393,7 +2461,7 @@ async function materializeAttachments(opts) {
|
|
|
2393
2461
|
await io.mkdir(dir);
|
|
2394
2462
|
dirReady = true;
|
|
2395
2463
|
}
|
|
2396
|
-
await io.writeFile(
|
|
2464
|
+
await io.writeFile(join14(dir, name), file.bytes);
|
|
2397
2465
|
} catch (e) {
|
|
2398
2466
|
result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
|
|
2399
2467
|
continue;
|
|
@@ -2464,8 +2532,8 @@ function pathOf(url) {
|
|
|
2464
2532
|
return url.split(/[?#]/)[0] ?? "";
|
|
2465
2533
|
}
|
|
2466
2534
|
function extOf(nameOrPath) {
|
|
2467
|
-
const
|
|
2468
|
-
const m = /\.([A-Za-z0-9]{1,8})$/.exec(
|
|
2535
|
+
const tail2 = nameOrPath.split("/").pop() ?? "";
|
|
2536
|
+
const m = /\.([A-Za-z0-9]{1,8})$/.exec(tail2);
|
|
2469
2537
|
return m ? m[1].toLowerCase() : "";
|
|
2470
2538
|
}
|
|
2471
2539
|
function mb(bytes) {
|
|
@@ -2546,7 +2614,7 @@ var init_attachments = __esm({
|
|
|
2546
2614
|
|
|
2547
2615
|
// src/shotUpload.ts
|
|
2548
2616
|
import { readFile as fsReadFile } from "fs/promises";
|
|
2549
|
-
import { basename as basename2, isAbsolute as
|
|
2617
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join15 } from "path";
|
|
2550
2618
|
function shotMimetype(path) {
|
|
2551
2619
|
const dot = path.lastIndexOf(".");
|
|
2552
2620
|
if (dot < 0) return null;
|
|
@@ -2572,7 +2640,7 @@ async function readShots(paths, opts) {
|
|
|
2572
2640
|
}
|
|
2573
2641
|
let bytes;
|
|
2574
2642
|
try {
|
|
2575
|
-
bytes = await read(
|
|
2643
|
+
bytes = await read(isAbsolute3(path) ? path : join15(opts.worktree, path));
|
|
2576
2644
|
} catch (e) {
|
|
2577
2645
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
2578
2646
|
continue;
|
|
@@ -2650,8 +2718,8 @@ var init_shotUpload = __esm({
|
|
|
2650
2718
|
|
|
2651
2719
|
// src/oauth.ts
|
|
2652
2720
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
2653
|
-
import { readFileSync as
|
|
2654
|
-
import { join as
|
|
2721
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
2722
|
+
import { join as join16 } from "path";
|
|
2655
2723
|
function makePkce() {
|
|
2656
2724
|
const verifier = randomBytes2(48).toString("base64url");
|
|
2657
2725
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -2735,23 +2803,23 @@ function toStore(clientId, t, now) {
|
|
|
2735
2803
|
}
|
|
2736
2804
|
function loadStore() {
|
|
2737
2805
|
try {
|
|
2738
|
-
return JSON.parse(
|
|
2806
|
+
return JSON.parse(readFileSync14(STORE_PATH, "utf8"));
|
|
2739
2807
|
} catch {
|
|
2740
2808
|
return null;
|
|
2741
2809
|
}
|
|
2742
2810
|
}
|
|
2743
2811
|
function saveStore(s) {
|
|
2744
|
-
|
|
2812
|
+
writeFileSync10(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
|
|
2745
2813
|
}
|
|
2746
2814
|
function clearStore() {
|
|
2747
2815
|
try {
|
|
2748
|
-
|
|
2816
|
+
unlinkSync3(STORE_PATH);
|
|
2749
2817
|
} catch {
|
|
2750
2818
|
}
|
|
2751
2819
|
}
|
|
2752
2820
|
function loadClientId() {
|
|
2753
2821
|
try {
|
|
2754
|
-
return JSON.parse(
|
|
2822
|
+
return JSON.parse(readFileSync14(CLIENT_PATH, "utf8")).clientId;
|
|
2755
2823
|
} catch {
|
|
2756
2824
|
return null;
|
|
2757
2825
|
}
|
|
@@ -2760,7 +2828,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
2760
2828
|
const existing = loadClientId();
|
|
2761
2829
|
if (existing) return existing;
|
|
2762
2830
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
2763
|
-
|
|
2831
|
+
writeFileSync10(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
2764
2832
|
return clientId;
|
|
2765
2833
|
}
|
|
2766
2834
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -2786,18 +2854,18 @@ var init_oauth = __esm({
|
|
|
2786
2854
|
AUTHORIZE = "/api/oauth/authorize";
|
|
2787
2855
|
TOKEN = "/api/oauth/token";
|
|
2788
2856
|
SCOPE = "openid profile email taskless:full";
|
|
2789
|
-
STORE_PATH =
|
|
2790
|
-
CLIENT_PATH =
|
|
2857
|
+
STORE_PATH = join16(SWARM_HOME, "oauth.json");
|
|
2858
|
+
CLIENT_PATH = join16(SWARM_HOME, "oauth-client.json");
|
|
2791
2859
|
base = (api) => api.replace(/\/$/, "");
|
|
2792
2860
|
}
|
|
2793
2861
|
});
|
|
2794
2862
|
|
|
2795
2863
|
// src/agentToken.ts
|
|
2796
|
-
import { readFileSync as
|
|
2797
|
-
import { join as
|
|
2864
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
|
|
2865
|
+
import { join as join17 } from "path";
|
|
2798
2866
|
function loadAgentToken() {
|
|
2799
2867
|
try {
|
|
2800
|
-
const s = JSON.parse(
|
|
2868
|
+
const s = JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
|
|
2801
2869
|
return s.token ? s : null;
|
|
2802
2870
|
} catch {
|
|
2803
2871
|
return null;
|
|
@@ -2834,17 +2902,17 @@ var init_agentToken = __esm({
|
|
|
2834
2902
|
"src/agentToken.ts"() {
|
|
2835
2903
|
"use strict";
|
|
2836
2904
|
init_config();
|
|
2837
|
-
STORE_PATH2 =
|
|
2905
|
+
STORE_PATH2 = join17(SWARM_HOME, "agent.json");
|
|
2838
2906
|
base2 = (api) => api.replace(/\/$/, "");
|
|
2839
2907
|
}
|
|
2840
2908
|
});
|
|
2841
2909
|
|
|
2842
2910
|
// src/workspace.ts
|
|
2843
|
-
import { readFileSync as
|
|
2844
|
-
import { join as
|
|
2911
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
|
|
2912
|
+
import { join as join18 } from "path";
|
|
2845
2913
|
function loadWorkspace() {
|
|
2846
2914
|
try {
|
|
2847
|
-
const w = JSON.parse(
|
|
2915
|
+
const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
|
|
2848
2916
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
2849
2917
|
} catch {
|
|
2850
2918
|
return null;
|
|
@@ -2858,7 +2926,7 @@ var init_workspace = __esm({
|
|
|
2858
2926
|
"src/workspace.ts"() {
|
|
2859
2927
|
"use strict";
|
|
2860
2928
|
init_config();
|
|
2861
|
-
WORKSPACE_PATH =
|
|
2929
|
+
WORKSPACE_PATH = join18(SWARM_HOME, "workspace.json");
|
|
2862
2930
|
}
|
|
2863
2931
|
});
|
|
2864
2932
|
|
|
@@ -3051,14 +3119,14 @@ async function fetchTaskContext(client, task) {
|
|
|
3051
3119
|
const parts = [];
|
|
3052
3120
|
const comments = task.comments ?? [];
|
|
3053
3121
|
if (comments.length > 0) {
|
|
3054
|
-
const
|
|
3055
|
-
const skipped = comments.length -
|
|
3056
|
-
const lines =
|
|
3122
|
+
const tail2 = comments.slice(-TASK_CONTEXT_MAX_COMMENTS);
|
|
3123
|
+
const skipped = comments.length - tail2.length;
|
|
3124
|
+
const lines = tail2.map((c) => {
|
|
3057
3125
|
const cut = c.text.length > TASK_CONTEXT_MAX_COMMENT_CHARS;
|
|
3058
3126
|
const text = cut ? `${c.text.slice(0, TASK_CONTEXT_MAX_COMMENT_CHARS)}\u2026 (\u043E\u0431\u0440\u0435\u0437\u0430\u043D\u043E)` : c.text;
|
|
3059
3127
|
return `\u2014 [${c.createdAt} \xB7 ${c.author}] ${text}`;
|
|
3060
3128
|
});
|
|
3061
|
-
const header = skipped > 0 ? `\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 (${comments.length}, \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u044B \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 ${
|
|
3129
|
+
const header = skipped > 0 ? `\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 (${comments.length}, \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u044B \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 ${tail2.length} \u2014 \u0441\u0430\u043C\u044B\u0435 \u0441\u0432\u0435\u0436\u0438\u0435):` : `\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 (${comments.length}):`;
|
|
3062
3130
|
parts.push([header, ...lines].join("\n"));
|
|
3063
3131
|
}
|
|
3064
3132
|
const documents = task.documents ?? [];
|
|
@@ -3285,8 +3353,8 @@ __export(db_exports, {
|
|
|
3285
3353
|
SwarmDb: () => SwarmDb
|
|
3286
3354
|
});
|
|
3287
3355
|
import { createRequire as createRequire2 } from "module";
|
|
3288
|
-
import { existsSync as
|
|
3289
|
-
import { resolve as
|
|
3356
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3357
|
+
import { resolve as resolve3 } from "path";
|
|
3290
3358
|
function rowToQuestion(row) {
|
|
3291
3359
|
return {
|
|
3292
3360
|
id: row.id,
|
|
@@ -3766,11 +3834,11 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3766
3834
|
* транзакции: прервётся — маркер не встанет, повтор безопасен.
|
|
3767
3835
|
*/
|
|
3768
3836
|
importDb(sourcePath, repo) {
|
|
3769
|
-
const abs =
|
|
3837
|
+
const abs = resolve3(sourcePath);
|
|
3770
3838
|
const repoN = normalizeRepo(repo);
|
|
3771
3839
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
3772
3840
|
if (already) return { imported: false, runs: already.run_count };
|
|
3773
|
-
if (!
|
|
3841
|
+
if (!existsSync10(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
3774
3842
|
const src = new this.databaseCtor(abs);
|
|
3775
3843
|
try {
|
|
3776
3844
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -4138,9 +4206,9 @@ var init_droneStore = __esm({
|
|
|
4138
4206
|
});
|
|
4139
4207
|
|
|
4140
4208
|
// src/overmind.ts
|
|
4141
|
-
import { homedir as
|
|
4142
|
-
import { existsSync as
|
|
4143
|
-
import { basename as basename4, dirname as dirname5, join as
|
|
4209
|
+
import { homedir as homedir4 } from "os";
|
|
4210
|
+
import { existsSync as existsSync15, readFileSync as readFileSync20, readdirSync as readdirSync10, renameSync as renameSync10 } from "fs";
|
|
4211
|
+
import { basename as basename4, dirname as dirname5, join as join25 } from "path";
|
|
4144
4212
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
4145
4213
|
import { spawn as spawn3 } from "child_process";
|
|
4146
4214
|
|
|
@@ -4291,25 +4359,25 @@ function runLogTail(runLogsDir, localRunId, opts) {
|
|
|
4291
4359
|
const parts = [];
|
|
4292
4360
|
let budget = opts.maxBytes;
|
|
4293
4361
|
if (launch.err) {
|
|
4294
|
-
const
|
|
4295
|
-
if (
|
|
4296
|
-
const block = `${fileHeader(`${launch.index}.err.log`,
|
|
4297
|
-
${
|
|
4362
|
+
const tail2 = readTail(launch.err, { lines: opts.lines, maxBytes: Math.floor(opts.maxBytes / 2) });
|
|
4363
|
+
if (tail2 && tail2.text.trim() !== "") {
|
|
4364
|
+
const block = `${fileHeader(`${launch.index}.err.log`, tail2.size, tail2.shown)}
|
|
4365
|
+
${tail2.text}`;
|
|
4298
4366
|
parts.push(block);
|
|
4299
4367
|
budget -= Buffer.byteLength(block, "utf8");
|
|
4300
4368
|
}
|
|
4301
4369
|
}
|
|
4302
4370
|
if (launch.out) {
|
|
4303
|
-
const
|
|
4304
|
-
if (
|
|
4371
|
+
const tail2 = readTail(launch.out, { lines: opts.lines, maxBytes: Math.max(1024, budget) });
|
|
4372
|
+
if (tail2) {
|
|
4305
4373
|
parts.push(
|
|
4306
|
-
|
|
4374
|
+
tail2.size === 0 ? (
|
|
4307
4375
|
// Пустой stdout при живом дроне — норма, а не поломка: claude в headless-режиме
|
|
4308
4376
|
// (`--output-format json`) печатает результат ОДНИМ объектом в самом конце.
|
|
4309
4377
|
`=== ${launch.index}.json (\u043F\u0443\u0441\u0442) ===
|
|
4310
4378
|
Stdout \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D: claude \u043E\u0442\u0434\u0430\u0451\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043E\u0434\u043D\u0438\u043C JSON \u0432 \u043A\u043E\u043D\u0446\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430. \u0414\u0440\u043E\u043D, \u0441\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u0435\u0449\u0451 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u2014 \u0441\u043C\u043E\u0442\u0440\u0438 stderr \u0432\u044B\u0448\u0435 \u0438 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0430.`
|
|
4311
|
-
) : `${fileHeader(`${launch.index}.json`,
|
|
4312
|
-
${
|
|
4379
|
+
) : `${fileHeader(`${launch.index}.json`, tail2.size, tail2.shown)}
|
|
4380
|
+
${tail2.text}`
|
|
4313
4381
|
);
|
|
4314
4382
|
}
|
|
4315
4383
|
}
|
|
@@ -4319,15 +4387,15 @@ ${tail.text}`
|
|
|
4319
4387
|
return { text: head + parts.join("\n\n") };
|
|
4320
4388
|
}
|
|
4321
4389
|
function daemonLogTail(path, opts) {
|
|
4322
|
-
const
|
|
4323
|
-
if (!
|
|
4390
|
+
const tail2 = readTail(path, opts);
|
|
4391
|
+
if (!tail2) {
|
|
4324
4392
|
return {
|
|
4325
4393
|
error: `\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${path} \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F. \u0414\u0435\u043C\u043E\u043D \u043F\u0438\u0448\u0435\u0442 \u0432 stdout \u2014 \u0437\u0430\u0432\u0435\u0434\u0438 \u0444\u0430\u0439\u043B \u043F\u0435\u0440\u0435\u043D\u0430\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C (\`swarm start >> ${path} 2>&1\`) \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438 \u0441\u0432\u043E\u0439 \u043F\u0443\u0442\u044C \u0432 SWARM_DAEMON_LOG.`
|
|
4326
4394
|
};
|
|
4327
4395
|
}
|
|
4328
|
-
if (
|
|
4329
|
-
return { text: `${fileHeader(path,
|
|
4330
|
-
${
|
|
4396
|
+
if (tail2.size === 0) return { error: `\u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430 \u043F\u0443\u0441\u0442 (${path})` };
|
|
4397
|
+
return { text: `${fileHeader(path, tail2.size, tail2.shown)}
|
|
4398
|
+
${tail2.text}` };
|
|
4331
4399
|
}
|
|
4332
4400
|
async function serveLogRequests(deps, commands) {
|
|
4333
4401
|
for (const cmd of commands) {
|
|
@@ -4366,6 +4434,11 @@ function runRef(runKey) {
|
|
|
4366
4434
|
function isRunRef(v) {
|
|
4367
4435
|
return typeof v === "object" && v !== null && "__runRef" in v;
|
|
4368
4436
|
}
|
|
4437
|
+
function isHostCommandShape(v) {
|
|
4438
|
+
const c = v;
|
|
4439
|
+
return !!c && typeof c === "object" && typeof c.id === "string" && !!c.id && typeof c.kind === "string";
|
|
4440
|
+
}
|
|
4441
|
+
var EMPTY_HEARTBEAT_REPLY = { commands: [], logRequests: [] };
|
|
4369
4442
|
function jsonFromTextContent(res) {
|
|
4370
4443
|
const text = res?.content?.find((c) => c.type === "text")?.text;
|
|
4371
4444
|
if (text) {
|
|
@@ -4545,6 +4618,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
4545
4618
|
runKeyFor(localId) {
|
|
4546
4619
|
return this.keyByLocal.get(localId);
|
|
4547
4620
|
}
|
|
4621
|
+
/**
|
|
4622
|
+
* TASWM-336: заменить репо-реестр на лету. До этой задачи реестр собирался ОДИН РАЗ на
|
|
4623
|
+
* старте из окружения и жил до рестарта демона; теперь машина сама находит клоны своих
|
|
4624
|
+
* репозиториев и переспрашивает прод по TTL — у владельца появился новый swarm-проект,
|
|
4625
|
+
* и машина обязана подхватить его без рестарта. Меняем обе стороны разом: и список для
|
|
4626
|
+
* claim/heartbeat, и обратную карту `owner/repo` → путь (иначе прогон нового репо
|
|
4627
|
+
* пришёл бы именем, которое не во что перевести).
|
|
4628
|
+
*/
|
|
4629
|
+
setRepoRegistry(registry) {
|
|
4630
|
+
this.repoFullNames = registry.map((e) => e.repoFullName);
|
|
4631
|
+
this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
|
|
4632
|
+
}
|
|
4548
4633
|
// cuid прода → локальный id (по runKey гидрированного прогона `p:<cuid>`).
|
|
4549
4634
|
// Для маппинга прод heroId → локальный hero_run_id при гидрации. Резолвится, когда
|
|
4550
4635
|
// прогон-герой присутствует в зеркале; иначе null (уточнится следующим рефрешем).
|
|
@@ -5537,17 +5622,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
5537
5622
|
* прислал предыдущий (более свежий по коду) процесс демона.
|
|
5538
5623
|
*/
|
|
5539
5624
|
/**
|
|
5540
|
-
* TASWM-
|
|
5541
|
-
*
|
|
5542
|
-
*
|
|
5543
|
-
*
|
|
5544
|
-
|
|
5545
|
-
|
|
5625
|
+
* TASWM-321: ВОЗВРАЩАЕТ команды этой машине (пауза/рестарт/обновление/…), приехавшие
|
|
5626
|
+
* в ответе на пульс. Это единственный момент, когда прод может что-то сказать демону:
|
|
5627
|
+
* тот живёт за домашним роутером без белого адреса, и связь всегда инициирует он сам.
|
|
5628
|
+
* Прод, отдав команды, метит их доставленными — повторный пульс их не вернёт, поэтому
|
|
5629
|
+
* потерять ответ здесь значит потерять команду; парсим бережно и не бросаем.
|
|
5630
|
+
*
|
|
5631
|
+
* TASWM-322: тем же ответом приезжают запросы «пришли хвост лога» (logRequests) —
|
|
5632
|
+
* канал «прод → демон» один на оба вида поручений, поэтому и разбираем их вместе.
|
|
5633
|
+
* Возвращаем ОБА списка объектом: два разных потребителя в overmind, и слепить их в
|
|
5634
|
+
* один список значило бы заставить каждого фильтровать чужое.
|
|
5635
|
+
*
|
|
5546
5636
|
* TASWM-326: тем же пульсом уходит ПАУЗА этой машины (`pause` — durable-запись из
|
|
5547
5637
|
* pause.json либо null). В парке машин «на паузе» обязано отличаться от «нет работы»
|
|
5548
5638
|
* и от «молчит», а единственный канал «демон → прод» с нужной кадансностью — пульс.
|
|
5549
5639
|
* Шлём ВСЕГДА (в т.ч. `paused:false`): это состояние, а не команда, и молчание тут
|
|
5550
5640
|
* означало бы «прод помнит вчерашнюю паузу», когда рой давно работает.
|
|
5641
|
+
*
|
|
5642
|
+
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
5551
5643
|
*/
|
|
5552
5644
|
async heartbeat(daemonVersion, pause) {
|
|
5553
5645
|
try {
|
|
@@ -5562,9 +5654,33 @@ var SwarmClient = class _SwarmClient {
|
|
|
5562
5654
|
args.hostName = this.host.hostName;
|
|
5563
5655
|
}
|
|
5564
5656
|
const res = await this.call("swarm_heartbeat", args);
|
|
5565
|
-
|
|
5657
|
+
if (res?.isError) return EMPTY_HEARTBEAT_REPLY;
|
|
5658
|
+
const data = res?.structuredContent ?? {};
|
|
5659
|
+
return {
|
|
5660
|
+
commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
|
|
5661
|
+
logRequests: parseLogRequests(data.logRequests)
|
|
5662
|
+
};
|
|
5663
|
+
} catch {
|
|
5664
|
+
return EMPTY_HEARTBEAT_REPLY;
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5667
|
+
/**
|
|
5668
|
+
* TASWM-321: подтвердить исполнение команды (`swarm_command_ack`). ПРЯМОЙ вызов, не
|
|
5669
|
+
* outbox: подтверждение — «скажи СЕЙЧАС», ради него команду и заводили («без
|
|
5670
|
+
* подтверждения непонятно, обновился он или команда потерялась по дороге»), а
|
|
5671
|
+
* промежуточные шаги обновления, доехавшие после самого обновления, бессмысленны.
|
|
5672
|
+
* Не бросает: сорвавшийся ack не должен рушить тик демона — команда останется в
|
|
5673
|
+
* прошлом состоянии, следующий шаг/финал её всё равно подвинут.
|
|
5674
|
+
*/
|
|
5675
|
+
async ackCommand(commandId, status, patch = {}) {
|
|
5676
|
+
try {
|
|
5677
|
+
await this.call("swarm_command_ack", {
|
|
5678
|
+
commandId,
|
|
5679
|
+
status,
|
|
5680
|
+
...patch.step ? { step: patch.step } : {},
|
|
5681
|
+
...patch.result ? { result: patch.result.slice(0, 2e3) } : {}
|
|
5682
|
+
});
|
|
5566
5683
|
} catch {
|
|
5567
|
-
return [];
|
|
5568
5684
|
}
|
|
5569
5685
|
}
|
|
5570
5686
|
/**
|
|
@@ -5733,8 +5849,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
5733
5849
|
this.runs.set(id, local);
|
|
5734
5850
|
this.keyByLocal.set(id, runKey);
|
|
5735
5851
|
}
|
|
5736
|
-
const
|
|
5737
|
-
local.task_slug =
|
|
5852
|
+
const prodSlug = pr.taskSlug ?? pr.task?.slug ?? null;
|
|
5853
|
+
if (prodSlug) local.task_slug = prodSlug;
|
|
5854
|
+
else if (!isTaskSlug(local.task_slug)) local.task_slug = pr.taskId || local.task_slug;
|
|
5738
5855
|
local.task_title = pr.taskTitle ?? local.task_title;
|
|
5739
5856
|
local.task_desc = pr.taskDesc ?? local.task_desc;
|
|
5740
5857
|
local.name = pr.droneName ?? local.name;
|
|
@@ -6053,6 +6170,66 @@ function parseDecision(v) {
|
|
|
6053
6170
|
init_config();
|
|
6054
6171
|
init_heartbeat();
|
|
6055
6172
|
|
|
6173
|
+
// src/logRotate.ts
|
|
6174
|
+
import { copyFileSync, renameSync as renameSync4, statSync as statSync2, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync4 } from "fs";
|
|
6175
|
+
var realIo = {
|
|
6176
|
+
size: (path) => {
|
|
6177
|
+
try {
|
|
6178
|
+
return statSync2(path).size;
|
|
6179
|
+
} catch {
|
|
6180
|
+
return null;
|
|
6181
|
+
}
|
|
6182
|
+
},
|
|
6183
|
+
copy: copyFileSync,
|
|
6184
|
+
rename: renameSync4,
|
|
6185
|
+
truncate: (path) => truncateSync(path, 0),
|
|
6186
|
+
unlink: unlinkSync2,
|
|
6187
|
+
exists: existsSync4
|
|
6188
|
+
};
|
|
6189
|
+
var realOwnerIo = {
|
|
6190
|
+
statFd: (fd) => {
|
|
6191
|
+
try {
|
|
6192
|
+
const s = fstatSync(fd);
|
|
6193
|
+
return { dev: s.dev, ino: s.ino, isFile: s.isFile() };
|
|
6194
|
+
} catch {
|
|
6195
|
+
return null;
|
|
6196
|
+
}
|
|
6197
|
+
},
|
|
6198
|
+
statPath: (path) => {
|
|
6199
|
+
try {
|
|
6200
|
+
const s = statSync2(path);
|
|
6201
|
+
return { dev: s.dev, ino: s.ino };
|
|
6202
|
+
} catch {
|
|
6203
|
+
return null;
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
};
|
|
6207
|
+
function stdoutGoesTo(path, io = realOwnerIo) {
|
|
6208
|
+
const out = io.statFd(1);
|
|
6209
|
+
if (!out || !out.isFile) return false;
|
|
6210
|
+
const file = io.statPath(path);
|
|
6211
|
+
return file != null && file.dev === out.dev && file.ino === out.ino;
|
|
6212
|
+
}
|
|
6213
|
+
var STRAY_GENERATIONS = 8;
|
|
6214
|
+
function rotateDaemonLog(path, opts, io = realIo) {
|
|
6215
|
+
const size = io.size(path);
|
|
6216
|
+
if (size == null) return "missing";
|
|
6217
|
+
if (size <= opts.maxBytes) return "kept";
|
|
6218
|
+
const keep = Math.max(0, Math.trunc(opts.keep));
|
|
6219
|
+
const gen = (n) => `${path}.${n}`;
|
|
6220
|
+
for (let i = keep; i >= 1; i--) {
|
|
6221
|
+
if (!io.exists(gen(i))) continue;
|
|
6222
|
+
if (i === keep) io.unlink(gen(i));
|
|
6223
|
+
else io.rename(gen(i), gen(i + 1));
|
|
6224
|
+
}
|
|
6225
|
+
for (let i = keep + 1; i <= keep + STRAY_GENERATIONS; i++) {
|
|
6226
|
+
if (io.exists(gen(i))) io.unlink(gen(i));
|
|
6227
|
+
}
|
|
6228
|
+
if (keep >= 1) io.copy(path, gen(1));
|
|
6229
|
+
io.truncate(path);
|
|
6230
|
+
return "rotated";
|
|
6231
|
+
}
|
|
6232
|
+
|
|
6056
6233
|
// src/repoRegistry.ts
|
|
6057
6234
|
init_config();
|
|
6058
6235
|
import { $ } from "execa";
|
|
@@ -6122,8 +6299,15 @@ function pickRepoPaths(args) {
|
|
|
6122
6299
|
return { paths: [], source: "none" };
|
|
6123
6300
|
}
|
|
6124
6301
|
|
|
6302
|
+
// src/repoDiscovery.ts
|
|
6303
|
+
init_src();
|
|
6304
|
+
init_config();
|
|
6305
|
+
import { homedir as homedir2 } from "os";
|
|
6306
|
+
import { readFileSync as readFileSync8, renameSync as renameSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
6307
|
+
import { isAbsolute, join as join6, resolve as resolve2 } from "path";
|
|
6308
|
+
|
|
6125
6309
|
// src/repoScan.ts
|
|
6126
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as
|
|
6310
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
6127
6311
|
import { join as join5 } from "path";
|
|
6128
6312
|
var SKIP = /* @__PURE__ */ new Set([
|
|
6129
6313
|
"node_modules",
|
|
@@ -6159,7 +6343,11 @@ function originOf(dir) {
|
|
|
6159
6343
|
return null;
|
|
6160
6344
|
}
|
|
6161
6345
|
}
|
|
6162
|
-
function
|
|
6346
|
+
function repoFullNameAt(dir) {
|
|
6347
|
+
const origin = originOf(dir);
|
|
6348
|
+
return origin ? parseRepoFullName(origin) : null;
|
|
6349
|
+
}
|
|
6350
|
+
function scanRootsAll(roots, opts = {}) {
|
|
6163
6351
|
const maxDepth = opts.maxDepth ?? 3;
|
|
6164
6352
|
const found = /* @__PURE__ */ new Map();
|
|
6165
6353
|
for (const root of roots) walk(root, 0, maxDepth, found);
|
|
@@ -6170,7 +6358,11 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
6170
6358
|
const origin = originOf(dir);
|
|
6171
6359
|
if (origin) {
|
|
6172
6360
|
const full = parseRepoFullName(origin);
|
|
6173
|
-
if (full
|
|
6361
|
+
if (full) {
|
|
6362
|
+
const paths = found.get(full) ?? [];
|
|
6363
|
+
if (!paths.includes(dir)) paths.push(dir);
|
|
6364
|
+
found.set(full, paths);
|
|
6365
|
+
}
|
|
6174
6366
|
return;
|
|
6175
6367
|
}
|
|
6176
6368
|
let entries;
|
|
@@ -6183,7 +6375,7 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
6183
6375
|
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
6184
6376
|
const child = join5(dir, name);
|
|
6185
6377
|
try {
|
|
6186
|
-
if (!
|
|
6378
|
+
if (!statSync3(child).isDirectory()) continue;
|
|
6187
6379
|
} catch {
|
|
6188
6380
|
continue;
|
|
6189
6381
|
}
|
|
@@ -6208,20 +6400,173 @@ function defaultScanRoots(home, knownPaths = []) {
|
|
|
6208
6400
|
return out;
|
|
6209
6401
|
}
|
|
6210
6402
|
|
|
6403
|
+
// src/repoDiscovery.ts
|
|
6404
|
+
function resolveScanRoots(raw, home = homedir2(), knownPaths = []) {
|
|
6405
|
+
const v = (raw ?? "").trim();
|
|
6406
|
+
if (!v) return defaultScanRoots(home, knownPaths);
|
|
6407
|
+
if (["off", "none", "0"].includes(v.toLowerCase())) return [];
|
|
6408
|
+
const out = [];
|
|
6409
|
+
for (const part of v.split(",")) {
|
|
6410
|
+
const p = part.trim();
|
|
6411
|
+
if (!p) continue;
|
|
6412
|
+
const abs = p === "~" ? home : p.startsWith("~/") ? join6(home, p.slice(2)) : isAbsolute(p) ? p : resolve2(home, p);
|
|
6413
|
+
if (!out.includes(abs)) out.push(abs);
|
|
6414
|
+
}
|
|
6415
|
+
return out;
|
|
6416
|
+
}
|
|
6417
|
+
function normalizeFullNames(list) {
|
|
6418
|
+
const out = [];
|
|
6419
|
+
for (const raw of list) {
|
|
6420
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
6421
|
+
if (v && !out.includes(v)) out.push(v);
|
|
6422
|
+
}
|
|
6423
|
+
return out;
|
|
6424
|
+
}
|
|
6425
|
+
async function fetchSwarmRepos(call) {
|
|
6426
|
+
let res;
|
|
6427
|
+
try {
|
|
6428
|
+
res = await call("swarm_my_repos", {});
|
|
6429
|
+
} catch {
|
|
6430
|
+
return null;
|
|
6431
|
+
}
|
|
6432
|
+
if (!res || res.isError) return null;
|
|
6433
|
+
const raw = res.structuredContent?.repos;
|
|
6434
|
+
if (!Array.isArray(raw)) return null;
|
|
6435
|
+
const out = [];
|
|
6436
|
+
for (const item of raw) {
|
|
6437
|
+
if (!item || typeof item !== "object") continue;
|
|
6438
|
+
const r = item;
|
|
6439
|
+
const repoFullName = typeof r.repoFullName === "string" ? r.repoFullName.trim().toLowerCase() : "";
|
|
6440
|
+
if (!repoFullName) continue;
|
|
6441
|
+
out.push({
|
|
6442
|
+
repoFullName,
|
|
6443
|
+
taskPrefix: typeof r.taskPrefix === "string" ? r.taskPrefix : null,
|
|
6444
|
+
workspaceSlug: typeof r.workspaceSlug === "string" ? r.workspaceSlug : ""
|
|
6445
|
+
});
|
|
6446
|
+
}
|
|
6447
|
+
return out;
|
|
6448
|
+
}
|
|
6449
|
+
function emptyRepoMapState(roots) {
|
|
6450
|
+
return { clones: [], missing: [], roots: [...roots], at: null, unknown: true };
|
|
6451
|
+
}
|
|
6452
|
+
async function refreshRepoMap(opts) {
|
|
6453
|
+
const log = opts.log ?? (() => {
|
|
6454
|
+
});
|
|
6455
|
+
const scan = opts.scan ?? scanRootsAll;
|
|
6456
|
+
const repoAt = opts.repoAt ?? repoFullNameAt;
|
|
6457
|
+
const wanted = await fetchSwarmRepos(opts.call);
|
|
6458
|
+
const clones = /* @__PURE__ */ new Map();
|
|
6459
|
+
const take = (repoFullName, path, source, alternatives = []) => {
|
|
6460
|
+
if (clones.has(repoFullName)) return;
|
|
6461
|
+
clones.set(repoFullName, { repoFullName, path, source, alternatives });
|
|
6462
|
+
};
|
|
6463
|
+
for (const e of opts.configured) take(e.repoFullName.toLowerCase(), e.path, "configured");
|
|
6464
|
+
for (const e of readRepoMapCache(opts.cachePath)) {
|
|
6465
|
+
const name = e.repoFullName.toLowerCase();
|
|
6466
|
+
if (clones.has(name)) continue;
|
|
6467
|
+
if (repoAt(e.path) === name) take(name, e.path, "cache");
|
|
6468
|
+
}
|
|
6469
|
+
const wantedNames = wanted ? normalizeFullNames(wanted.map((r) => r.repoFullName)) : [];
|
|
6470
|
+
const needScan = wanted ? wantedNames.some((n) => !clones.has(n)) : clones.size === 0;
|
|
6471
|
+
if (needScan) {
|
|
6472
|
+
const startedAt = Date.now();
|
|
6473
|
+
const scanned = scan(opts.roots, opts.maxDepth !== void 0 ? { maxDepth: opts.maxDepth } : {});
|
|
6474
|
+
let added = 0;
|
|
6475
|
+
for (const [full, paths] of scanned) {
|
|
6476
|
+
if (clones.has(full) || !paths.length) continue;
|
|
6477
|
+
take(full, paths[0], "scan", paths.slice(1));
|
|
6478
|
+
added++;
|
|
6479
|
+
}
|
|
6480
|
+
if (added > 0) {
|
|
6481
|
+
log(`[overmind] \u043D\u0430\u0448\u0451\u043B \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 ${added} \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439(\u0435\u0432) \u0441\u043A\u0430\u043D\u043E\u043C \u0437\u0430 ${Date.now() - startedAt}\u043C\u0441`);
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
const registry = [...clones.values()].map((c) => ({
|
|
6485
|
+
repoFullName: c.repoFullName,
|
|
6486
|
+
path: normalizeRepo(c.path)
|
|
6487
|
+
}));
|
|
6488
|
+
writeRepoMapCache(
|
|
6489
|
+
opts.cachePath,
|
|
6490
|
+
[...clones.values()].map((c) => ({ repoFullName: c.repoFullName, path: c.path }))
|
|
6491
|
+
);
|
|
6492
|
+
if (!wanted) {
|
|
6493
|
+
log(
|
|
6494
|
+
"[overmind] \u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F (swarm_my_repos) \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435. \u0421\u0435\u0440\u0432\u0435\u0440 \u0441\u0442\u0430\u0440\u0435\u0435 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u044D\u0442\u043E \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u043E \u0438 \u043B\u0435\u0447\u0438\u0442\u0441\u044F \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u043F\u0440\u043E\u0434\u0430."
|
|
6495
|
+
);
|
|
6496
|
+
return registry;
|
|
6497
|
+
}
|
|
6498
|
+
opts.state.unknown = false;
|
|
6499
|
+
opts.state.clones = wantedNames.map((n) => clones.get(n)).filter((c) => !!c);
|
|
6500
|
+
opts.state.missing = wantedNames.filter((n) => !clones.has(n));
|
|
6501
|
+
opts.state.roots = [...opts.roots];
|
|
6502
|
+
opts.state.at = stamp();
|
|
6503
|
+
if (wantedNames.length === 0) {
|
|
6504
|
+
log("[overmind] \u0443 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u043C \u0440\u043E\u0435\u043C \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u043F\u043E \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u043E\u043C\u0443 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435");
|
|
6505
|
+
return registry;
|
|
6506
|
+
}
|
|
6507
|
+
log(`[overmind] \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0440\u043E\u044F (${wantedNames.length}), \u043A\u0430\u0440\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435:`);
|
|
6508
|
+
for (const c of opts.state.clones) log(`[overmind] ${describeClone(c)}`);
|
|
6509
|
+
const prefixOf = new Map(wanted.map((r) => [r.repoFullName, r.taskPrefix]));
|
|
6510
|
+
for (const name of opts.state.missing) {
|
|
6511
|
+
const prefix = prefixOf.get(name);
|
|
6512
|
+
log(`[overmind] ${describeMissing(prefix ? `${name} (\u043F\u0440\u043E\u0435\u043A\u0442 ${prefix})` : name, opts.roots)}`);
|
|
6513
|
+
}
|
|
6514
|
+
return registry;
|
|
6515
|
+
}
|
|
6516
|
+
function describeClone(c) {
|
|
6517
|
+
const how = c.source === "configured" ? "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C" : c.source === "cache" ? "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B" : "\u043D\u0430\u0439\u0434\u0435\u043D \u0441\u043A\u0430\u043D\u043E\u043C";
|
|
6518
|
+
const alt = c.alternatives.length ? ` \u26A0 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0435\u0449\u0451 ${c.alternatives.length}: ${c.alternatives.join(", ")} \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0432 \u043F\u0435\u0440\u0432\u043E\u043C \u043F\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0443 \u043A\u043E\u0440\u043D\u0435\u0439` : "";
|
|
6519
|
+
return `${c.repoFullName} \u2192 ${c.path} (${how})${alt}`;
|
|
6520
|
+
}
|
|
6521
|
+
function describeMissing(repoFullName, roots) {
|
|
6522
|
+
const where = roots.length ? `\u0438\u0441\u043A\u0430\u043B \u0432: ${roots.slice(0, 6).join(", ")}` : "\u043F\u043E\u0438\u0441\u043A \u043F\u043E \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u043C \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D (SWARM_SCAN_ROOTS)";
|
|
6523
|
+
return `${repoFullName} \u2014 \u041A\u041B\u041E\u041D\u0410 \u041D\u0410 \u042D\u0422\u041E\u0419 \u041C\u0410\u0428\u0418\u041D\u0415 \u041D\u0415\u0422 (${where}). \u0421\u0430\u043C \u043D\u0435 \u043A\u043B\u043E\u043D\u0438\u0440\u0443\u044E: \u0441\u043A\u043B\u043E\u043D\u0438\u0440\u0443\u0439 \u0440\u0443\u043A\u0430\u043C\u0438 \u043B\u0438\u0431\u043E \u0443\u043A\u0430\u0436\u0438 \u043F\u0443\u0442\u044C \`swarm start --repo <\u043F\u0443\u0442\u044C>\`.`;
|
|
6524
|
+
}
|
|
6525
|
+
function repoMapRows(state) {
|
|
6526
|
+
return [
|
|
6527
|
+
...state.clones.map((c) => ({
|
|
6528
|
+
repoFullName: c.repoFullName,
|
|
6529
|
+
path: c.path,
|
|
6530
|
+
source: c.source,
|
|
6531
|
+
alternatives: c.alternatives
|
|
6532
|
+
})),
|
|
6533
|
+
...state.missing.map((repoFullName) => ({ repoFullName, path: null, source: null, alternatives: [] }))
|
|
6534
|
+
];
|
|
6535
|
+
}
|
|
6536
|
+
function readRepoMapCache(path) {
|
|
6537
|
+
try {
|
|
6538
|
+
const raw = JSON.parse(readFileSync8(path, "utf8"));
|
|
6539
|
+
if (!Array.isArray(raw?.entries)) return [];
|
|
6540
|
+
return raw.entries.filter(
|
|
6541
|
+
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
6542
|
+
);
|
|
6543
|
+
} catch {
|
|
6544
|
+
return [];
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
function writeRepoMapCache(path, entries) {
|
|
6548
|
+
try {
|
|
6549
|
+
const tmp = `${path}.tmp`;
|
|
6550
|
+
writeFileSync6(tmp, JSON.stringify({ at: stamp(), entries }, null, 2));
|
|
6551
|
+
renameSync5(tmp, path);
|
|
6552
|
+
} catch {
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6211
6556
|
// src/hostIdentity.ts
|
|
6212
6557
|
import { hostname } from "os";
|
|
6213
|
-
import { existsSync as
|
|
6214
|
-
import { join as
|
|
6558
|
+
import { existsSync as existsSync5, readFileSync as readFileSync9, renameSync as renameSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
6559
|
+
import { join as join7 } from "path";
|
|
6215
6560
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6216
6561
|
function hostIdentityPath(home) {
|
|
6217
|
-
return
|
|
6562
|
+
return join7(home, "host.json");
|
|
6218
6563
|
}
|
|
6219
6564
|
function loadHostIdentity(deps) {
|
|
6220
|
-
const exists = deps.exists ??
|
|
6221
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
6565
|
+
const exists = deps.exists ?? existsSync5;
|
|
6566
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
|
|
6222
6567
|
const writeFile2 = deps.writeFile ?? ((p, data) => {
|
|
6223
|
-
|
|
6224
|
-
|
|
6568
|
+
writeFileSync7(`${p}.tmp`, data);
|
|
6569
|
+
renameSync6(`${p}.tmp`, p);
|
|
6225
6570
|
});
|
|
6226
6571
|
const host = deps.hostname ?? hostname;
|
|
6227
6572
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -6403,14 +6748,14 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
6403
6748
|
|
|
6404
6749
|
// src/launcher.ts
|
|
6405
6750
|
import { spawn } from "child_process";
|
|
6406
|
-
import { closeSync as closeSync2, existsSync as
|
|
6751
|
+
import { closeSync as closeSync2, existsSync as existsSync7, mkdirSync as mkdirSync4, openSync as openSync2, readdirSync as readdirSync5, statSync as statSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
6407
6752
|
import { createRequire } from "module";
|
|
6408
|
-
import { homedir as
|
|
6409
|
-
import { dirname as dirname2, join as
|
|
6753
|
+
import { homedir as homedir3 } from "os";
|
|
6754
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
6410
6755
|
import { fileURLToPath } from "url";
|
|
6411
6756
|
|
|
6412
6757
|
// src/claudeBin.ts
|
|
6413
|
-
import { existsSync as
|
|
6758
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
6414
6759
|
import { win32 as winPath } from "path";
|
|
6415
6760
|
var CLAUDE_FALLBACK = "claude";
|
|
6416
6761
|
function compareVersionDirs(a, b) {
|
|
@@ -6447,7 +6792,7 @@ function winVersionRoots(env, listDir) {
|
|
|
6447
6792
|
function findWindowsClaude(opts = {}) {
|
|
6448
6793
|
const env = opts.env ?? process.env;
|
|
6449
6794
|
const listDir = opts.listDir ?? realListDir;
|
|
6450
|
-
const fileExists = opts.fileExists ??
|
|
6795
|
+
const fileExists = opts.fileExists ?? existsSync6;
|
|
6451
6796
|
let best = null;
|
|
6452
6797
|
for (const root of winVersionRoots(env, listDir)) {
|
|
6453
6798
|
for (const version of listDir(root)) {
|
|
@@ -6482,8 +6827,8 @@ function resolveClaudeBin(opts = {}) {
|
|
|
6482
6827
|
}
|
|
6483
6828
|
|
|
6484
6829
|
// src/droneHome.ts
|
|
6485
|
-
import { lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, rmSync, statSync as
|
|
6486
|
-
import { join as
|
|
6830
|
+
import { lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, rmSync, statSync as statSync4, symlinkSync } from "fs";
|
|
6831
|
+
import { join as join8, relative, sep } from "path";
|
|
6487
6832
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
6488
6833
|
function droneHomeDeny(env = process.env) {
|
|
6489
6834
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -6507,23 +6852,23 @@ function buildDroneHome(opts) {
|
|
|
6507
6852
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
6508
6853
|
for (const existing of readdirSync4(droneHome)) {
|
|
6509
6854
|
if (wanted.has(existing)) continue;
|
|
6510
|
-
const p =
|
|
6855
|
+
const p = join8(droneHome, existing);
|
|
6511
6856
|
try {
|
|
6512
6857
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
6513
6858
|
} catch {
|
|
6514
6859
|
}
|
|
6515
6860
|
}
|
|
6516
6861
|
for (const name of wanted) {
|
|
6517
|
-
const link =
|
|
6862
|
+
const link = join8(droneHome, name);
|
|
6518
6863
|
try {
|
|
6519
6864
|
lstatSync(link);
|
|
6520
6865
|
continue;
|
|
6521
6866
|
} catch {
|
|
6522
6867
|
}
|
|
6523
|
-
const target =
|
|
6868
|
+
const target = join8(realHome, name);
|
|
6524
6869
|
let isDir;
|
|
6525
6870
|
try {
|
|
6526
|
-
isDir =
|
|
6871
|
+
isDir = statSync4(target).isDirectory();
|
|
6527
6872
|
} catch {
|
|
6528
6873
|
continue;
|
|
6529
6874
|
}
|
|
@@ -6541,7 +6886,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
6541
6886
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
6542
6887
|
];
|
|
6543
6888
|
function realPathEnvPins(realHome) {
|
|
6544
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
6889
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join8(realHome, ...segments)]));
|
|
6545
6890
|
}
|
|
6546
6891
|
function droneDenyRules(realHome, swarmHome) {
|
|
6547
6892
|
const paths = [
|
|
@@ -6633,7 +6978,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
6633
6978
|
const env = { ...base3 };
|
|
6634
6979
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
6635
6980
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
6636
|
-
env.TURBO_CACHE_DIR =
|
|
6981
|
+
env.TURBO_CACHE_DIR = join9(worktree, ".turbo", "cache");
|
|
6637
6982
|
}
|
|
6638
6983
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
6639
6984
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -6661,25 +7006,25 @@ var DAEMON_STATE_ENV = [
|
|
|
6661
7006
|
"TASKLESS_TOKEN"
|
|
6662
7007
|
];
|
|
6663
7008
|
function bundledRoleDir(kind) {
|
|
6664
|
-
return
|
|
7009
|
+
return join9(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
6665
7010
|
}
|
|
6666
7011
|
function roleDir(worktree, kind) {
|
|
6667
|
-
return
|
|
7012
|
+
return join9(worktree, ".claude", "roles", kind);
|
|
6668
7013
|
}
|
|
6669
7014
|
function roleDirs(worktree, kind) {
|
|
6670
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
7015
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
|
|
6671
7016
|
}
|
|
6672
7017
|
function transcriptDir(worktree, projectsDir) {
|
|
6673
|
-
return
|
|
7018
|
+
return join9(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
6674
7019
|
}
|
|
6675
|
-
function sessionTranscriptExists(worktree, projectsDir =
|
|
7020
|
+
function sessionTranscriptExists(worktree, projectsDir = join9(homedir3(), ".claude", "projects")) {
|
|
6676
7021
|
try {
|
|
6677
7022
|
return readdirSync5(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
|
|
6678
7023
|
} catch {
|
|
6679
7024
|
return false;
|
|
6680
7025
|
}
|
|
6681
7026
|
}
|
|
6682
|
-
function transcriptMtimeMs(worktree, projectsDir =
|
|
7027
|
+
function transcriptMtimeMs(worktree, projectsDir = join9(homedir3(), ".claude", "projects")) {
|
|
6683
7028
|
const dir = transcriptDir(worktree, projectsDir);
|
|
6684
7029
|
let files;
|
|
6685
7030
|
try {
|
|
@@ -6691,7 +7036,7 @@ function transcriptMtimeMs(worktree, projectsDir = join8(homedir2(), ".claude",
|
|
|
6691
7036
|
for (const f of files) {
|
|
6692
7037
|
if (!f.endsWith(".jsonl")) continue;
|
|
6693
7038
|
try {
|
|
6694
|
-
const mtime2 =
|
|
7039
|
+
const mtime2 = statSync5(join9(dir, f)).mtimeMs;
|
|
6695
7040
|
if (latest === null || mtime2 > latest) latest = mtime2;
|
|
6696
7041
|
} catch {
|
|
6697
7042
|
}
|
|
@@ -6699,7 +7044,7 @@ function transcriptMtimeMs(worktree, projectsDir = join8(homedir2(), ".claude",
|
|
|
6699
7044
|
return latest;
|
|
6700
7045
|
}
|
|
6701
7046
|
function nextRunLogPath(runLogsDir, runId) {
|
|
6702
|
-
const dir =
|
|
7047
|
+
const dir = join9(runLogsDir, String(runId));
|
|
6703
7048
|
mkdirSync4(dir, { recursive: true });
|
|
6704
7049
|
let files = [];
|
|
6705
7050
|
try {
|
|
@@ -6711,7 +7056,7 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
6711
7056
|
const m = /^(\d+)\.json/.exec(f);
|
|
6712
7057
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
6713
7058
|
}
|
|
6714
|
-
return
|
|
7059
|
+
return join9(dir, `${next}.json`);
|
|
6715
7060
|
}
|
|
6716
7061
|
function openNextRunLog(runLogsDir, runId) {
|
|
6717
7062
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -6785,16 +7130,16 @@ function makeLauncher(cfg) {
|
|
|
6785
7130
|
}
|
|
6786
7131
|
}
|
|
6787
7132
|
};
|
|
6788
|
-
const cfgPath =
|
|
6789
|
-
|
|
7133
|
+
const cfgPath = join9(worktree, ".swarm-mcp.json");
|
|
7134
|
+
writeFileSync8(cfgPath, JSON.stringify(mcpConfig, null, 2));
|
|
6790
7135
|
const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
|
|
6791
7136
|
const pluginDirs = roleDirs(worktree, run.kind);
|
|
6792
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
7137
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir3();
|
|
6793
7138
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
6794
7139
|
const settings = cfg.droneIsolation ? droneSettingsJson(realHome, cfg.droneIsolation.swarmHome) : void 0;
|
|
6795
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
7140
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join9(cfg.droneIsolation.swarmHome, "token"))) {
|
|
6796
7141
|
console.error(
|
|
6797
|
-
`[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 ${
|
|
7142
|
+
`[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 ${join9(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.`
|
|
6798
7143
|
);
|
|
6799
7144
|
}
|
|
6800
7145
|
let stdio = "ignore";
|
|
@@ -6828,8 +7173,8 @@ function makeLauncher(cfg) {
|
|
|
6828
7173
|
init_procCleanup();
|
|
6829
7174
|
|
|
6830
7175
|
// src/cost.ts
|
|
6831
|
-
import { readFileSync as
|
|
6832
|
-
import { join as
|
|
7176
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync6, renameSync as renameSync7 } from "fs";
|
|
7177
|
+
import { join as join10 } from "path";
|
|
6833
7178
|
|
|
6834
7179
|
// src/pricing.ts
|
|
6835
7180
|
var MODEL_PRICING = {
|
|
@@ -6886,17 +7231,17 @@ function unprocessedLogFiles(dir) {
|
|
|
6886
7231
|
}
|
|
6887
7232
|
}
|
|
6888
7233
|
function accountRunCost(deps, run) {
|
|
6889
|
-
const dir =
|
|
7234
|
+
const dir = join10(deps.runLogsDir, String(run.id));
|
|
6890
7235
|
const files = unprocessedLogFiles(dir);
|
|
6891
7236
|
if (files.length === 0) return;
|
|
6892
7237
|
const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
|
|
6893
7238
|
let added = 0;
|
|
6894
7239
|
const processed = [];
|
|
6895
7240
|
for (const f of files) {
|
|
6896
|
-
const full =
|
|
7241
|
+
const full = join10(dir, f);
|
|
6897
7242
|
let raw = null;
|
|
6898
7243
|
try {
|
|
6899
|
-
raw =
|
|
7244
|
+
raw = readFileSync10(full, "utf8");
|
|
6900
7245
|
} catch {
|
|
6901
7246
|
continue;
|
|
6902
7247
|
}
|
|
@@ -6913,7 +7258,7 @@ function accountRunCost(deps, run) {
|
|
|
6913
7258
|
if (added > 0) deps.db.updateRun(run.id, { cost_usd: run.cost_usd + added });
|
|
6914
7259
|
for (const full of processed) {
|
|
6915
7260
|
try {
|
|
6916
|
-
|
|
7261
|
+
renameSync7(full, `${full}.done`);
|
|
6917
7262
|
} catch {
|
|
6918
7263
|
}
|
|
6919
7264
|
}
|
|
@@ -6923,11 +7268,11 @@ function makeCostAccountant(deps) {
|
|
|
6923
7268
|
}
|
|
6924
7269
|
|
|
6925
7270
|
// src/runOutcome.ts
|
|
6926
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
6927
|
-
import { join as
|
|
7271
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync11 } from "fs";
|
|
7272
|
+
import { join as join11 } from "path";
|
|
6928
7273
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
6929
7274
|
function classifyExit(runLogsDir, runId) {
|
|
6930
|
-
const dir =
|
|
7275
|
+
const dir = join11(runLogsDir, String(runId));
|
|
6931
7276
|
let files;
|
|
6932
7277
|
try {
|
|
6933
7278
|
files = readdirSync7(dir);
|
|
@@ -6944,7 +7289,7 @@ function classifyExit(runLogsDir, runId) {
|
|
|
6944
7289
|
if (!latest) return "unknown";
|
|
6945
7290
|
let raw;
|
|
6946
7291
|
try {
|
|
6947
|
-
raw =
|
|
7292
|
+
raw = readFileSync11(join11(dir, latest.name), "utf8");
|
|
6948
7293
|
} catch {
|
|
6949
7294
|
return "unknown";
|
|
6950
7295
|
}
|
|
@@ -6955,11 +7300,12 @@ function makeExitClassifier(runLogsDir) {
|
|
|
6955
7300
|
}
|
|
6956
7301
|
|
|
6957
7302
|
// src/orchestrator.ts
|
|
7303
|
+
init_src();
|
|
6958
7304
|
init_git();
|
|
6959
7305
|
|
|
6960
7306
|
// src/deps.ts
|
|
6961
|
-
import { existsSync as
|
|
6962
|
-
import { join as
|
|
7307
|
+
import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync12 } from "fs";
|
|
7308
|
+
import { join as join13 } from "path";
|
|
6963
7309
|
import { execa } from "execa";
|
|
6964
7310
|
var PREPARE_TIMEOUT_MS = 15 * 6e4;
|
|
6965
7311
|
var MAX_PLAN_COMMANDS = 12;
|
|
@@ -6975,7 +7321,7 @@ async function runCommand(cmd, worktree) {
|
|
|
6975
7321
|
}
|
|
6976
7322
|
function safeReadText(p) {
|
|
6977
7323
|
try {
|
|
6978
|
-
return
|
|
7324
|
+
return readFileSync12(p, "utf8");
|
|
6979
7325
|
} catch {
|
|
6980
7326
|
return null;
|
|
6981
7327
|
}
|
|
@@ -7015,21 +7361,21 @@ function overridePlan(override) {
|
|
|
7015
7361
|
};
|
|
7016
7362
|
}
|
|
7017
7363
|
function detectPackageManager(worktree, io = {}) {
|
|
7018
|
-
const exists = io.exists ??
|
|
7364
|
+
const exists = io.exists ?? existsSync9;
|
|
7019
7365
|
const readText = io.readText ?? safeReadText;
|
|
7020
|
-
const hasPkgJson = exists(
|
|
7366
|
+
const hasPkgJson = exists(join13(worktree, "package.json"));
|
|
7021
7367
|
if (!hasPkgJson) {
|
|
7022
|
-
if (exists(
|
|
7368
|
+
if (exists(join13(worktree, "deno.json")) || exists(join13(worktree, "deno.jsonc")) || exists(join13(worktree, "deno.lock"))) return "deno";
|
|
7023
7369
|
return null;
|
|
7024
7370
|
}
|
|
7025
|
-
const pkg = readJson(readText,
|
|
7371
|
+
const pkg = readJson(readText, join13(worktree, "package.json"));
|
|
7026
7372
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
7027
7373
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
7028
|
-
if (exists(
|
|
7029
|
-
if (exists(
|
|
7030
|
-
if (exists(
|
|
7031
|
-
if (exists(
|
|
7032
|
-
if (exists(
|
|
7374
|
+
if (exists(join13(worktree, "bun.lock")) || exists(join13(worktree, "bun.lockb"))) return "bun";
|
|
7375
|
+
if (exists(join13(worktree, "pnpm-lock.yaml"))) return "pnpm";
|
|
7376
|
+
if (exists(join13(worktree, "yarn.lock"))) return "yarn";
|
|
7377
|
+
if (exists(join13(worktree, "package-lock.json"))) return "npm";
|
|
7378
|
+
if (exists(join13(worktree, "deno.lock"))) return "deno";
|
|
7033
7379
|
return "npm";
|
|
7034
7380
|
}
|
|
7035
7381
|
function jsInstallCommand(pm) {
|
|
@@ -7082,7 +7428,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
7082
7428
|
// конфигурация — дело сборки, не подготовки
|
|
7083
7429
|
];
|
|
7084
7430
|
function detectPlan(worktree, io) {
|
|
7085
|
-
const exists = io.exists ??
|
|
7431
|
+
const exists = io.exists ?? existsSync9;
|
|
7086
7432
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
7087
7433
|
const pm = detectPackageManager(worktree, io);
|
|
7088
7434
|
if (pm) {
|
|
@@ -7090,7 +7436,7 @@ function detectPlan(worktree, io) {
|
|
|
7090
7436
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
7091
7437
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
7092
7438
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
7093
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
7439
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join13(worktree, "node_modules"), source: "detect" }
|
|
7094
7440
|
];
|
|
7095
7441
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
7096
7442
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -7098,12 +7444,12 @@ function detectPlan(worktree, io) {
|
|
|
7098
7444
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
7099
7445
|
}
|
|
7100
7446
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
7101
|
-
const hit = marker.files.some((f) => exists(
|
|
7447
|
+
const hit = marker.files.some((f) => exists(join13(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
|
|
7102
7448
|
if (!hit) continue;
|
|
7103
7449
|
let ecosystem = marker.ecosystem;
|
|
7104
7450
|
let command = marker.command;
|
|
7105
7451
|
for (const refine of marker.refine ?? []) {
|
|
7106
|
-
if (!exists(
|
|
7452
|
+
if (!exists(join13(worktree, refine.file))) continue;
|
|
7107
7453
|
ecosystem = refine.ecosystem;
|
|
7108
7454
|
command = refine.command;
|
|
7109
7455
|
break;
|
|
@@ -7160,12 +7506,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
7160
7506
|
function ciWorkflowPlan(worktree, io) {
|
|
7161
7507
|
const readText = io.readText ?? safeReadText;
|
|
7162
7508
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
7163
|
-
const dir =
|
|
7509
|
+
const dir = join13(worktree, ".github", "workflows");
|
|
7164
7510
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
7165
7511
|
const seen = /* @__PURE__ */ new Set();
|
|
7166
7512
|
const commands = [];
|
|
7167
7513
|
for (const name of files.sort()) {
|
|
7168
|
-
const yaml = readText(
|
|
7514
|
+
const yaml = readText(join13(dir, name));
|
|
7169
7515
|
if (yaml === null) continue;
|
|
7170
7516
|
for (const raw of workflowRunLines(yaml)) {
|
|
7171
7517
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -7180,7 +7526,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
7180
7526
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
7181
7527
|
}
|
|
7182
7528
|
function workspaceGlobs(worktree, readText) {
|
|
7183
|
-
const yaml = readText(
|
|
7529
|
+
const yaml = readText(join13(worktree, "pnpm-workspace.yaml"));
|
|
7184
7530
|
if (yaml !== null) {
|
|
7185
7531
|
const globs = [];
|
|
7186
7532
|
let inPackages = false;
|
|
@@ -7200,7 +7546,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
7200
7546
|
}
|
|
7201
7547
|
if (globs.length) return globs;
|
|
7202
7548
|
}
|
|
7203
|
-
const pkg = readJson(readText,
|
|
7549
|
+
const pkg = readJson(readText, join13(worktree, "package.json"));
|
|
7204
7550
|
const ws = pkg?.workspaces;
|
|
7205
7551
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
7206
7552
|
return list.filter((g) => typeof g === "string");
|
|
@@ -7211,14 +7557,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
7211
7557
|
if (glob.startsWith("!")) continue;
|
|
7212
7558
|
const star = glob.indexOf("*");
|
|
7213
7559
|
if (star === -1) {
|
|
7214
|
-
const dir =
|
|
7215
|
-
if (io.exists(
|
|
7560
|
+
const dir = join13(worktree, glob);
|
|
7561
|
+
if (io.exists(join13(dir, "package.json"))) dirs.add(dir);
|
|
7216
7562
|
continue;
|
|
7217
7563
|
}
|
|
7218
|
-
const base3 =
|
|
7564
|
+
const base3 = join13(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
7219
7565
|
for (const name of io.listDirs(base3)) {
|
|
7220
|
-
const dir =
|
|
7221
|
-
if (io.exists(
|
|
7566
|
+
const dir = join13(base3, name);
|
|
7567
|
+
if (io.exists(join13(dir, "package.json"))) dirs.add(dir);
|
|
7222
7568
|
}
|
|
7223
7569
|
}
|
|
7224
7570
|
return [...dirs];
|
|
@@ -7239,18 +7585,18 @@ function entryPaths(pkg) {
|
|
|
7239
7585
|
return out;
|
|
7240
7586
|
}
|
|
7241
7587
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
7242
|
-
const exists = io.exists ??
|
|
7588
|
+
const exists = io.exists ?? existsSync9;
|
|
7243
7589
|
const readText = io.readText ?? safeReadText;
|
|
7244
7590
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
7245
7591
|
const globs = workspaceGlobs(worktree, readText);
|
|
7246
7592
|
if (!globs.length) return [];
|
|
7247
7593
|
const pkgs = [];
|
|
7248
7594
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
7249
|
-
const pkg = readJson(readText,
|
|
7595
|
+
const pkg = readJson(readText, join13(dir, "package.json"));
|
|
7250
7596
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
7251
7597
|
const scripts = pkg.scripts ?? {};
|
|
7252
7598
|
const entries = entryPaths(pkg);
|
|
7253
|
-
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(
|
|
7599
|
+
const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join13(dir, e)));
|
|
7254
7600
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
7255
7601
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
7256
7602
|
}
|
|
@@ -7278,7 +7624,7 @@ function planPrepare(worktree, io = {}) {
|
|
|
7278
7624
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
7279
7625
|
}
|
|
7280
7626
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
7281
|
-
const exists = io.exists ??
|
|
7627
|
+
const exists = io.exists ?? existsSync9;
|
|
7282
7628
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
7283
7629
|
const approve = io.approve ?? (() => true);
|
|
7284
7630
|
const plan = planPrepare(worktree, io);
|
|
@@ -7377,6 +7723,12 @@ var AreaBusyError = class extends Error {
|
|
|
7377
7723
|
}
|
|
7378
7724
|
area;
|
|
7379
7725
|
};
|
|
7726
|
+
function healTaskSlug(db, run, task) {
|
|
7727
|
+
const slug = task?.slug;
|
|
7728
|
+
if (!slug || slug === run.task_slug || !isTaskSlug(slug) || isTaskSlug(run.task_slug)) return run;
|
|
7729
|
+
db.addEvent(run.id, "step", `\u0441\u043B\u0430\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: ${run.task_slug} \u2192 ${slug} (\u043F\u0440\u043E\u0434 \u043E\u0442\u0434\u0430\u043B \u043F\u0440\u043E\u0433\u043E\u043D \u0431\u0435\u0437 \u0441\u043B\u0430\u0433\u0430 \u2014 TASWM-329)`);
|
|
7730
|
+
return db.updateRun(run.id, { task_slug: slug }) ?? run;
|
|
7731
|
+
}
|
|
7380
7732
|
async function spawnDrone(deps, run) {
|
|
7381
7733
|
const repo = normalizeRepo(run.repo || deps.repo || "");
|
|
7382
7734
|
if (!repo) throw new Error(`\u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id} (${run.task_slug}) \u043D\u0435\u0442 repo \u0438 \u043D\u0435\u0442 \u0434\u0435\u0444\u043E\u043B\u0442\u0430 SWARM_REPO`);
|
|
@@ -7564,8 +7916,8 @@ function fullCommitHeadline(headline, body) {
|
|
|
7564
7916
|
const head = typeof headline === "string" ? headline : "";
|
|
7565
7917
|
if (!head) return null;
|
|
7566
7918
|
if (!head.endsWith("\u2026") || typeof body !== "string" || !body.startsWith("\u2026")) return head;
|
|
7567
|
-
const
|
|
7568
|
-
return head.slice(0, -1) +
|
|
7919
|
+
const tail2 = body.slice(1).split("\n", 1)[0] ?? "";
|
|
7920
|
+
return head.slice(0, -1) + tail2;
|
|
7569
7921
|
}
|
|
7570
7922
|
var NO_CHECKS_STDERR = /no checks reported/i;
|
|
7571
7923
|
var realChecksRunner = async (pr) => {
|
|
@@ -8823,10 +9175,31 @@ function makeExclusiveReader(opts) {
|
|
|
8823
9175
|
}
|
|
8824
9176
|
|
|
8825
9177
|
// src/envGate.ts
|
|
8826
|
-
import { existsSync as
|
|
8827
|
-
import { join as
|
|
9178
|
+
import { existsSync as existsSync12 } from "fs";
|
|
9179
|
+
import { join as join20 } from "path";
|
|
8828
9180
|
import { createConnection } from "net";
|
|
8829
9181
|
import { execa as execa2 } from "execa";
|
|
9182
|
+
|
|
9183
|
+
// src/platform.ts
|
|
9184
|
+
import { existsSync as existsSync11, readFileSync as readFileSync17 } from "fs";
|
|
9185
|
+
import { delimiter, join as join19 } from "path";
|
|
9186
|
+
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
9187
|
+
if (name.includes("/") || name.includes("\\")) return existsSync11(name) ? name : null;
|
|
9188
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
9189
|
+
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
9190
|
+
for (const dir of dirs) {
|
|
9191
|
+
for (const ext of exts) {
|
|
9192
|
+
const candidate = join19(dir, name + ext);
|
|
9193
|
+
if (existsSync11(candidate)) return candidate;
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
9196
|
+
return null;
|
|
9197
|
+
}
|
|
9198
|
+
function hasCommandInPath(name, env = process.env, platform = process.platform) {
|
|
9199
|
+
return resolveCommandPath(name, env, platform) !== null;
|
|
9200
|
+
}
|
|
9201
|
+
|
|
9202
|
+
// src/envGate.ts
|
|
8830
9203
|
var PROBE_TIMEOUT_MS = 2500;
|
|
8831
9204
|
var PORT_TIMEOUT_MS = 1e3;
|
|
8832
9205
|
function parseRequirements(lines) {
|
|
@@ -8875,8 +9248,8 @@ var COMPOSE_MARKERS = [
|
|
|
8875
9248
|
"compose.yaml"
|
|
8876
9249
|
];
|
|
8877
9250
|
function detectRequirements(repo, io = {}) {
|
|
8878
|
-
const exists = io.exists ??
|
|
8879
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
9251
|
+
const exists = io.exists ?? existsSync12;
|
|
9252
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join20(repo, f)));
|
|
8880
9253
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
8881
9254
|
}
|
|
8882
9255
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -8895,17 +9268,14 @@ async function dockerAlive() {
|
|
|
8895
9268
|
}
|
|
8896
9269
|
}
|
|
8897
9270
|
async function hasCommand(name, env) {
|
|
8898
|
-
|
|
8899
|
-
const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
8900
|
-
const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
8901
|
-
return dirs.some((dir) => exts.some((ext) => existsSync10(join18(dir, name + ext))));
|
|
9271
|
+
return hasCommandInPath(name, env);
|
|
8902
9272
|
}
|
|
8903
9273
|
async function portOpen(host, port) {
|
|
8904
|
-
return new Promise((
|
|
9274
|
+
return new Promise((resolve4) => {
|
|
8905
9275
|
const socket = createConnection({ host, port });
|
|
8906
9276
|
const finish = (ok) => {
|
|
8907
9277
|
socket.destroy();
|
|
8908
|
-
|
|
9278
|
+
resolve4(ok);
|
|
8909
9279
|
};
|
|
8910
9280
|
socket.setTimeout(PORT_TIMEOUT_MS);
|
|
8911
9281
|
socket.once("connect", () => finish(true));
|
|
@@ -9015,8 +9385,8 @@ import { basename as basename3 } from "path";
|
|
|
9015
9385
|
import { serve as serve2 } from "@hono/node-server";
|
|
9016
9386
|
|
|
9017
9387
|
// src/diff.ts
|
|
9018
|
-
import { statSync as
|
|
9019
|
-
import { join as
|
|
9388
|
+
import { statSync as statSync7 } from "fs";
|
|
9389
|
+
import { join as join21 } from "path";
|
|
9020
9390
|
import { $ as $4 } from "execa";
|
|
9021
9391
|
var MAX_FILES = 200;
|
|
9022
9392
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -9159,7 +9529,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
9159
9529
|
}
|
|
9160
9530
|
function mtime(path) {
|
|
9161
9531
|
try {
|
|
9162
|
-
return
|
|
9532
|
+
return statSync7(path).mtimeMs;
|
|
9163
9533
|
} catch {
|
|
9164
9534
|
return 0;
|
|
9165
9535
|
}
|
|
@@ -9184,7 +9554,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9184
9554
|
files.push(pf);
|
|
9185
9555
|
}
|
|
9186
9556
|
}
|
|
9187
|
-
files.sort((a, b) => mtime(
|
|
9557
|
+
files.sort((a, b) => mtime(join21(worktree, b.path)) - mtime(join21(worktree, a.path)));
|
|
9188
9558
|
const truncatedFiles = files.length > MAX_FILES;
|
|
9189
9559
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
9190
9560
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -9203,7 +9573,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9203
9573
|
// package.json
|
|
9204
9574
|
var package_default = {
|
|
9205
9575
|
name: "@taskless-app/swarm",
|
|
9206
|
-
version: "0.
|
|
9576
|
+
version: "0.5.0",
|
|
9207
9577
|
type: "module",
|
|
9208
9578
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
9209
9579
|
keywords: [
|
|
@@ -9255,13 +9625,16 @@ var package_default = {
|
|
|
9255
9625
|
}
|
|
9256
9626
|
};
|
|
9257
9627
|
|
|
9628
|
+
// src/version.ts
|
|
9629
|
+
var PACKAGE_VERSION = package_default.version;
|
|
9630
|
+
var MIN_NODE_MAJOR = Number(/(\d+)/.exec(package_default.engines?.node ?? "")?.[1] ?? 0);
|
|
9631
|
+
|
|
9258
9632
|
// src/web.ts
|
|
9259
9633
|
init_storm();
|
|
9260
9634
|
init_resources();
|
|
9261
9635
|
init_config();
|
|
9262
9636
|
init_droneChannel();
|
|
9263
9637
|
init_oauth();
|
|
9264
|
-
var PACKAGE_VERSION = package_default.version;
|
|
9265
9638
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
9266
9639
|
var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
|
|
9267
9640
|
var TASKLESS_MARK = `<svg viewBox="0 0 60 54" width="19" height="19" fill="none" aria-hidden="true" style="display:block"><g transform="matrix(1.0512,0,0,1.0512,0,-0.021)"><path fill="currentColor" fill-rule="nonzero" d="M46.92,0 L10.24,0 C4.59,0 0,4.59 0,10.24 L0,10.4 C0,16.05 4.59,20.64 10.24,20.64 L16.84,20.64 C19.56,20.64 22.12,19.59 24.05,17.67 L36.06,5.77 C37.25,4.6 38.82,3.95 40.49,3.95 L46.93,3.95 C50.4,3.95 53.22,6.77 53.22,10.24 L53.22,10.4 C53.22,13.87 50.4,16.69 46.93,16.69 L39.82,16.69 C37.05,16.69 34.36,17.83 32.44,19.83 L21.13,31.58 C19.28,33.5 18.27,36.02 18.27,38.68 L18.27,40.94 C18.27,46.59 22.86,51.18 28.51,51.18 C34.16,51.18 38.75,46.59 38.75,40.94 L38.75,20.74 C39.1,20.68 39.46,20.64 39.82,20.64 L46.93,20.64 C52.58,20.64 57.17,16.05 57.17,10.4 L57.17,10.24 C57.17,4.59 52.58,0 46.93,0 L46.92,0 Z M21.26,14.87 C20.07,16.04 18.5,16.69 16.83,16.69 L10.23,16.69 C6.76,16.69 3.94,13.87 3.94,10.4 L3.94,10.24 C3.94,6.77 6.76,3.95 10.23,3.95 L32.27,3.95 L21.25,14.87 L21.26,14.87 Z M34.79,40.94 C34.79,44.41 31.97,47.23 28.5,47.23 C25.03,47.23 22.21,44.41 22.21,40.94 L22.21,38.68 C22.21,37.05 22.83,35.5 23.97,34.32 L34.79,23.07 L34.79,40.94 L34.79,40.94 Z"/></g></svg>`;
|
|
@@ -10569,7 +10942,8 @@ function daemonStatus(deps) {
|
|
|
10569
10942
|
restart: deps.restart ? { phase: deps.restart.phase, intent: deps.restart.intent, sessions: live } : null,
|
|
10570
10943
|
counts,
|
|
10571
10944
|
live,
|
|
10572
|
-
version: PACKAGE_VERSION
|
|
10945
|
+
version: PACKAGE_VERSION,
|
|
10946
|
+
repos: deps.repoMap?.() ?? []
|
|
10573
10947
|
};
|
|
10574
10948
|
}
|
|
10575
10949
|
function eventsStream(db, narratorLog, control, daemonVersion, restart, liveSessionIds2, selfHost) {
|
|
@@ -10817,8 +11191,8 @@ function startWeb(deps, port = WEB_PORT) {
|
|
|
10817
11191
|
return { port, server };
|
|
10818
11192
|
}
|
|
10819
11193
|
function closeWebServer(server) {
|
|
10820
|
-
return new Promise((
|
|
10821
|
-
server.close(() =>
|
|
11194
|
+
return new Promise((resolve4) => {
|
|
11195
|
+
server.close(() => resolve4());
|
|
10822
11196
|
server.closeAllConnections?.();
|
|
10823
11197
|
});
|
|
10824
11198
|
}
|
|
@@ -10862,6 +11236,253 @@ async function checkDaemonVersion(start, now = () => /* @__PURE__ */ new Date())
|
|
|
10862
11236
|
}
|
|
10863
11237
|
}
|
|
10864
11238
|
|
|
11239
|
+
// src/release.ts
|
|
11240
|
+
var SWARM_PACKAGE = "@taskless-app/swarm";
|
|
11241
|
+
var LATEST_TIMEOUT_MS = 4e3;
|
|
11242
|
+
async function fetchLatestVersion(opts = {}) {
|
|
11243
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
11244
|
+
const timeoutMs = opts.timeoutMs ?? LATEST_TIMEOUT_MS;
|
|
11245
|
+
const pkg = opts.pkg ?? SWARM_PACKAGE;
|
|
11246
|
+
const ctrl = new AbortController();
|
|
11247
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
11248
|
+
try {
|
|
11249
|
+
const res = await fetchFn(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
11250
|
+
signal: ctrl.signal,
|
|
11251
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" }
|
|
11252
|
+
});
|
|
11253
|
+
if (!res.ok) return null;
|
|
11254
|
+
const body = await res.json();
|
|
11255
|
+
return typeof body.version === "string" && body.version ? body.version : null;
|
|
11256
|
+
} catch {
|
|
11257
|
+
return null;
|
|
11258
|
+
} finally {
|
|
11259
|
+
clearTimeout(timer);
|
|
11260
|
+
}
|
|
11261
|
+
}
|
|
11262
|
+
function isOutdated(current, latest) {
|
|
11263
|
+
if (!latest || !current) return false;
|
|
11264
|
+
return compareVersionDirs(latest, current) > 0;
|
|
11265
|
+
}
|
|
11266
|
+
function outdatedLine(current, latest, updateCommand = "swarm update") {
|
|
11267
|
+
if (!isOutdated(current, latest)) return null;
|
|
11268
|
+
return `\u26A0 \u0432\u0435\u0440\u0441\u0438\u044F \u0440\u043E\u044F \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430: \u0443 \u0442\u0435\u0431\u044F v${current}, \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u0430 v${latest} \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: ${updateCommand}`;
|
|
11269
|
+
}
|
|
11270
|
+
|
|
11271
|
+
// src/hostCommands.ts
|
|
11272
|
+
init_src();
|
|
11273
|
+
import { readFileSync as readFileSync18, renameSync as renameSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
11274
|
+
import { join as join22 } from "path";
|
|
11275
|
+
function hostCommandStatePath(home) {
|
|
11276
|
+
return join22(home, "host-command.json");
|
|
11277
|
+
}
|
|
11278
|
+
function readState(path) {
|
|
11279
|
+
try {
|
|
11280
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
11281
|
+
} catch {
|
|
11282
|
+
return {};
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
11285
|
+
function writeState(path, state) {
|
|
11286
|
+
try {
|
|
11287
|
+
writeFileSync13(`${path}.tmp`, JSON.stringify(state));
|
|
11288
|
+
renameSync8(`${path}.tmp`, path);
|
|
11289
|
+
} catch {
|
|
11290
|
+
}
|
|
11291
|
+
}
|
|
11292
|
+
function savePendingAck(path, ack) {
|
|
11293
|
+
writeState(path, { ...readState(path), pendingAck: ack });
|
|
11294
|
+
}
|
|
11295
|
+
function readPendingAck(path) {
|
|
11296
|
+
return readState(path).pendingAck ?? null;
|
|
11297
|
+
}
|
|
11298
|
+
function clearPendingAck(path) {
|
|
11299
|
+
const state = readState(path);
|
|
11300
|
+
const pending2 = state.pendingAck ?? null;
|
|
11301
|
+
if (pending2) writeState(path, { ...state, pendingAck: null });
|
|
11302
|
+
return pending2;
|
|
11303
|
+
}
|
|
11304
|
+
async function flushPendingAck(path, ack) {
|
|
11305
|
+
const state = readState(path);
|
|
11306
|
+
const pending2 = state.pendingAck;
|
|
11307
|
+
if (!pending2) return null;
|
|
11308
|
+
writeState(path, { ...state, pendingAck: null });
|
|
11309
|
+
try {
|
|
11310
|
+
await ack(pending2.commandId, "done", { step: "restart", result: pending2.note });
|
|
11311
|
+
} catch {
|
|
11312
|
+
}
|
|
11313
|
+
return pending2;
|
|
11314
|
+
}
|
|
11315
|
+
function loadMaxDronesOverride(path) {
|
|
11316
|
+
const n = readState(path).maxDrones;
|
|
11317
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.trunc(n) : null;
|
|
11318
|
+
}
|
|
11319
|
+
function saveMaxDronesOverride(path, n) {
|
|
11320
|
+
writeState(path, { ...readState(path), maxDrones: n });
|
|
11321
|
+
}
|
|
11322
|
+
async function executeHostCommands(opts) {
|
|
11323
|
+
const now = opts.nowMs ?? Date.now();
|
|
11324
|
+
let restarting = false;
|
|
11325
|
+
for (const cmd of opts.commands) {
|
|
11326
|
+
if (restarting) {
|
|
11327
|
+
await opts.ack(cmd.id, "rejected", { result: "\u0434\u0435\u043C\u043E\u043D \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u2014 \u043E\u0442\u0434\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0437\u0430\u043D\u043E\u0432\u043E, \u043A\u043E\u0433\u0434\u0430 \u043E\u043D \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F" });
|
|
11328
|
+
continue;
|
|
11329
|
+
}
|
|
11330
|
+
if (isHostCommandExpired(cmd.expiresAt, now)) {
|
|
11331
|
+
await opts.ack(cmd.id, "rejected", { result: "\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u0430 \u2014 \u044F \u0431\u044B\u043B \u043D\u0435 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043E\u043B\u0433\u043E" });
|
|
11332
|
+
continue;
|
|
11333
|
+
}
|
|
11334
|
+
if (!isHostCommandKind(cmd.kind)) {
|
|
11335
|
+
await opts.ack(cmd.id, "rejected", { result: `\u044D\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044F \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435 \u0437\u043D\u0430\u0435\u0442 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \xAB${cmd.kind}\xBB \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u0434\u0435\u043C\u043E\u043D` });
|
|
11336
|
+
continue;
|
|
11337
|
+
}
|
|
11338
|
+
restarting = await executeOne(cmd, opts);
|
|
11339
|
+
}
|
|
11340
|
+
}
|
|
11341
|
+
async function executeOne(cmd, opts) {
|
|
11342
|
+
const { effects, ack, statePath } = opts;
|
|
11343
|
+
const label = hostCommandLabel(cmd);
|
|
11344
|
+
effects.narrate(`\u{1F4DF} \u041A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u0437 Taskless: ${label}`);
|
|
11345
|
+
try {
|
|
11346
|
+
switch (cmd.kind) {
|
|
11347
|
+
case "pause":
|
|
11348
|
+
effects.pause();
|
|
11349
|
+
await ack(cmd.id, "done", { result: "\u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0443, \u0436\u0438\u0432\u044B\u0435 \u0434\u0440\u043E\u043D\u044B \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442" });
|
|
11350
|
+
return false;
|
|
11351
|
+
case "resume":
|
|
11352
|
+
if (!effects.resume()) {
|
|
11353
|
+
await ack(cmd.id, "rejected", {
|
|
11354
|
+
result: "\u0436\u0434\u0443 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0433\u043E \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u2014 \u043F\u043E\u043A\u0430 \u043D\u0435 \u0441\u043D\u0438\u043C\u0443 \u043F\u0430\u0443\u0437\u0443, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u043E\u0442\u044B \u043D\u0435 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u044F\u0442\u0441\u044F \u0438 \u0440\u0435\u0441\u0442\u0430\u0440\u0442 \u043D\u0435 \u0441\u043B\u0443\u0447\u0438\u0442\u0441\u044F"
|
|
11355
|
+
});
|
|
11356
|
+
return false;
|
|
11357
|
+
}
|
|
11358
|
+
await ack(cmd.id, "done", { result: "\u0441\u043D\u044F\u043B \u0441 \u043F\u0430\u0443\u0437\u044B \u2014 \u0441\u043D\u043E\u0432\u0430 \u0431\u0435\u0440\u0443 \u0437\u0430\u0434\u0430\u0447\u0438" });
|
|
11359
|
+
return false;
|
|
11360
|
+
case "set_max_drones": {
|
|
11361
|
+
const n = cmd.maxDrones;
|
|
11362
|
+
if (n == null || !Number.isFinite(n) || n < 1 || n > MAX_DRONES_LIMIT) {
|
|
11363
|
+
await ack(cmd.id, "rejected", { result: `\u0447\u0438\u0441\u043B\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043E\u0442 1 \u0434\u043E ${MAX_DRONES_LIMIT}` });
|
|
11364
|
+
return false;
|
|
11365
|
+
}
|
|
11366
|
+
const value = Math.trunc(n);
|
|
11367
|
+
saveMaxDronesOverride(statePath, value);
|
|
11368
|
+
effects.setMaxDrones(value);
|
|
11369
|
+
await ack(cmd.id, "done", { result: `\u043F\u043E\u0442\u043E\u043B\u043E\u043A \u0434\u0440\u043E\u043D\u043E\u0432 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: ${value}` });
|
|
11370
|
+
return false;
|
|
11371
|
+
}
|
|
11372
|
+
case "stop_run": {
|
|
11373
|
+
const slug = cmd.runSlug?.trim();
|
|
11374
|
+
if (!slug) {
|
|
11375
|
+
await ack(cmd.id, "rejected", { result: "\u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043D\u043E, \u043A\u0430\u043A\u043E\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0442\u044C" });
|
|
11376
|
+
return false;
|
|
11377
|
+
}
|
|
11378
|
+
const stopped = await effects.stopRun(slug, "\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0438\u0437 Taskless");
|
|
11379
|
+
if (!stopped) {
|
|
11380
|
+
await ack(cmd.id, "rejected", { result: `\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043F\u043E ${slug} \u0443 \u043C\u0435\u043D\u044F \u043D\u0435\u0442` });
|
|
11381
|
+
return false;
|
|
11382
|
+
}
|
|
11383
|
+
await ack(cmd.id, "done", { result: `\u043F\u0440\u043E\u0433\u043E\u043D ${slug} \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D` });
|
|
11384
|
+
return false;
|
|
11385
|
+
}
|
|
11386
|
+
case "restart":
|
|
11387
|
+
return await doRestart(cmd, opts, "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F \u043F\u043E \u043A\u043E\u043C\u0430\u043D\u0434\u0435 \u0438\u0437 Taskless");
|
|
11388
|
+
case "update": {
|
|
11389
|
+
const res = await effects.update(async (step, note) => {
|
|
11390
|
+
await ack(cmd.id, "running", { step, result: note });
|
|
11391
|
+
});
|
|
11392
|
+
if (!res.ok) {
|
|
11393
|
+
await ack(cmd.id, "failed", { step: res.step, result: `${UPDATE_STEP_LABELS[res.step]}: ${res.message}` });
|
|
11394
|
+
effects.narrate(`\u26A0\uFE0F \u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0448\u0430\u0433\u0435 \xAB${UPDATE_STEP_LABELS[res.step]}\xBB: ${res.message}`);
|
|
11395
|
+
return false;
|
|
11396
|
+
}
|
|
11397
|
+
return await doRestart(cmd, opts, res.message);
|
|
11398
|
+
}
|
|
11399
|
+
}
|
|
11400
|
+
} catch (e) {
|
|
11401
|
+
await ack(cmd.id, "failed", { result: e instanceof Error ? e.message : String(e) });
|
|
11402
|
+
return false;
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
async function doRestart(cmd, opts, note) {
|
|
11406
|
+
const { effects, ack, statePath } = opts;
|
|
11407
|
+
savePendingAck(statePath, { commandId: cmd.id, kind: cmd.kind, note });
|
|
11408
|
+
const { waiting, sessions } = effects.requestRestart();
|
|
11409
|
+
if (waiting) {
|
|
11410
|
+
await ack(cmd.id, "running", {
|
|
11411
|
+
step: "restart",
|
|
11412
|
+
result: `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0449\u0443, \u043A\u043E\u0433\u0434\u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u044F\u0442\u0441\u044F \u0441\u043B\u043E\u0442\u044B: \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442 ${sessions.length} \u2014 \u043E\u043D\u0438 \u0431\u044B \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u043B\u0438`
|
|
11413
|
+
});
|
|
11414
|
+
effects.narrate(`\u23F3 \u0420\u0435\u0441\u0442\u0430\u0440\u0442 \u043F\u043E \u043A\u043E\u043C\u0430\u043D\u0434\u0435 \u0438\u0437 Taskless \u043E\u0442\u043B\u043E\u0436\u0435\u043D: \u0436\u0434\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (${sessions.length})`);
|
|
11415
|
+
return true;
|
|
11416
|
+
}
|
|
11417
|
+
await ack(cmd.id, "running", { step: "restart", result: "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u044E\u0441\u044C" });
|
|
11418
|
+
return true;
|
|
11419
|
+
}
|
|
11420
|
+
|
|
11421
|
+
// src/selfUpdate.ts
|
|
11422
|
+
init_git();
|
|
11423
|
+
import { existsSync as existsSync13 } from "fs";
|
|
11424
|
+
import { join as join23 } from "path";
|
|
11425
|
+
import { $ as $6 } from "execa";
|
|
11426
|
+
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
11427
|
+
var defaultRun = async (cmd, args, cwd) => {
|
|
11428
|
+
const res = await $6({ reject: false, cwd })(cmd, args);
|
|
11429
|
+
return { exitCode: res.exitCode ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
11430
|
+
};
|
|
11431
|
+
function tail(text, max = 400) {
|
|
11432
|
+
const t = text.trim();
|
|
11433
|
+
return t.length <= max ? t : `\u2026${t.slice(-max)}`;
|
|
11434
|
+
}
|
|
11435
|
+
async function runSelfUpdate(io) {
|
|
11436
|
+
const run = io.run ?? defaultRun;
|
|
11437
|
+
const exists = io.exists ?? existsSync13;
|
|
11438
|
+
const repo = io.repo;
|
|
11439
|
+
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
11440
|
+
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
11441
|
+
if (fetched.exitCode !== 0) {
|
|
11442
|
+
return { ok: false, step: "fetch", message: `git fetch \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B: ${tail(fetched.stderr || fetched.stdout)}` };
|
|
11443
|
+
}
|
|
11444
|
+
await io.onStep("checkout", "\u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u044E\u0441\u044C \u043D\u0430 \u0441\u0432\u0435\u0436\u0438\u0439 main");
|
|
11445
|
+
const dirty = await run("git", ["-C", repo, "status", "--porcelain"], repo);
|
|
11446
|
+
if (dirty.exitCode !== 0) {
|
|
11447
|
+
return { ok: false, step: "checkout", message: `\u043D\u0435 \u0441\u043C\u043E\u0433 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F: ${tail(dirty.stderr)}` };
|
|
11448
|
+
}
|
|
11449
|
+
if (dirty.stdout.trim()) {
|
|
11450
|
+
return {
|
|
11451
|
+
ok: false,
|
|
11452
|
+
step: "checkout",
|
|
11453
|
+
message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u0435\u0441\u0442\u044C \u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u044B\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u2014 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E \u0438\u0445. \u041F\u0440\u0438\u0431\u0435\u0440\u0438 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435."
|
|
11454
|
+
};
|
|
11455
|
+
}
|
|
11456
|
+
const ref = await io.mainRef(repo);
|
|
11457
|
+
const merged = await run("git", ["-C", repo, "merge", "--ff-only", ref], repo);
|
|
11458
|
+
if (merged.exitCode !== 0) {
|
|
11459
|
+
return {
|
|
11460
|
+
ok: false,
|
|
11461
|
+
step: "checkout",
|
|
11462
|
+
message: `\u043D\u0435 \u043F\u0435\u0440\u0435\u043C\u0430\u0442\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0430 ${ref} (\u0441\u0432\u043E\u044F \u0432\u0435\u0442\u043A\u0430/\u0440\u0430\u0441\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435 \u0441 main): ${tail(merged.stderr || merged.stdout)}`
|
|
11463
|
+
};
|
|
11464
|
+
}
|
|
11465
|
+
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
11466
|
+
if (!exists(join23(repo, "pnpm-lock.yaml"))) {
|
|
11467
|
+
return { ok: false, step: "install", message: "\u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442 pnpm-lock.yaml \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u0435\u0433\u043E \u044D\u0442\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435 \u0431\u0435\u0440\u0443\u0441\u044C" };
|
|
11468
|
+
}
|
|
11469
|
+
const installed = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
11470
|
+
if (installed.exitCode !== 0) {
|
|
11471
|
+
return { ok: false, step: "install", message: `pnpm install \u043D\u0435 \u043F\u0440\u043E\u0448\u0451\u043B: ${tail(installed.stderr || installed.stdout)}` };
|
|
11472
|
+
}
|
|
11473
|
+
await io.onStep("build", "\u0441\u043E\u0431\u0438\u0440\u0430\u044E \u043F\u0430\u043A\u0435\u0442\u044B \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430");
|
|
11474
|
+
const built = await run("pnpm", ["--filter", `${DAEMON_PACKAGE}...`, "build"], repo);
|
|
11475
|
+
if (built.exitCode !== 0) {
|
|
11476
|
+
return { ok: false, step: "build", message: `\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430: ${tail(built.stderr || built.stdout)}` };
|
|
11477
|
+
}
|
|
11478
|
+
const head = await run("git", ["-C", repo, "rev-parse", "--short", "HEAD"], repo);
|
|
11479
|
+
const sha = head.exitCode === 0 ? head.stdout.trim() : "";
|
|
11480
|
+
return { ok: true, step: "build", message: sha ? `\u043A\u043E\u0434 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \u0434\u043E ${sha}, \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u0438 \u0441\u0431\u043E\u0440\u043A\u0430 \u0433\u043E\u0442\u043E\u0432\u044B` : "\u043A\u043E\u0434 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D, \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u0438 \u0441\u0431\u043E\u0440\u043A\u0430 \u0433\u043E\u0442\u043E\u0432\u044B" };
|
|
11481
|
+
}
|
|
11482
|
+
function defaultSelfUpdateIO(repo, onStep) {
|
|
11483
|
+
return { repo, run: defaultRun, mainRef: remoteMainRef, onStep };
|
|
11484
|
+
}
|
|
11485
|
+
|
|
10865
11486
|
// src/narration.ts
|
|
10866
11487
|
var RING_LIMIT = 20;
|
|
10867
11488
|
function makeOvermindNarrator() {
|
|
@@ -10882,7 +11503,7 @@ var RESET_RE = /resets[^\n]*/i;
|
|
|
10882
11503
|
function anthropicHealthy(opts = {}) {
|
|
10883
11504
|
const bin = opts.claudeBin ?? resolveClaudeBin();
|
|
10884
11505
|
const timeoutMs = opts.timeoutMs ?? 2e4;
|
|
10885
|
-
return new Promise((
|
|
11506
|
+
return new Promise((resolve4) => {
|
|
10886
11507
|
let done = false;
|
|
10887
11508
|
let out = "";
|
|
10888
11509
|
const finish = (r) => {
|
|
@@ -10893,7 +11514,7 @@ function anthropicHealthy(opts = {}) {
|
|
|
10893
11514
|
child.kill("SIGKILL");
|
|
10894
11515
|
} catch {
|
|
10895
11516
|
}
|
|
10896
|
-
|
|
11517
|
+
resolve4(r);
|
|
10897
11518
|
};
|
|
10898
11519
|
const child = spawn2(bin, [...ISOLATION_FLAGS, "-p", "ok", "--max-turns", "1"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
10899
11520
|
child.stdout?.on("data", (d) => {
|
|
@@ -10932,16 +11553,16 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
10932
11553
|
}
|
|
10933
11554
|
|
|
10934
11555
|
// src/anomalyState.ts
|
|
10935
|
-
import { existsSync as
|
|
10936
|
-
import { join as
|
|
11556
|
+
import { existsSync as existsSync14, readFileSync as readFileSync19, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
11557
|
+
import { join as join24 } from "path";
|
|
10937
11558
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
10938
11559
|
var EMPTY = { since: {}, lastFiredAt: {}, fires: {}, stopQuiet: null };
|
|
10939
11560
|
function anomalyStatePath(home) {
|
|
10940
|
-
return
|
|
11561
|
+
return join24(home, "anomaly-state.json");
|
|
10941
11562
|
}
|
|
10942
11563
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
10943
|
-
const exists = io.exists ??
|
|
10944
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
11564
|
+
const exists = io.exists ?? existsSync14;
|
|
11565
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync19(p, "utf8"));
|
|
10945
11566
|
const path = anomalyStatePath(home);
|
|
10946
11567
|
if (!exists(path)) return { ...EMPTY };
|
|
10947
11568
|
try {
|
|
@@ -10967,8 +11588,8 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
10967
11588
|
function saveAnomalyState(home, state, io = {}) {
|
|
10968
11589
|
const path = anomalyStatePath(home);
|
|
10969
11590
|
const writeFile2 = io.writeFile ?? ((p, data) => {
|
|
10970
|
-
|
|
10971
|
-
|
|
11591
|
+
writeFileSync14(`${p}.tmp`, data);
|
|
11592
|
+
renameSync9(`${p}.tmp`, p);
|
|
10972
11593
|
});
|
|
10973
11594
|
writeFile2(path, JSON.stringify(state, null, 2));
|
|
10974
11595
|
}
|
|
@@ -11610,10 +12231,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
11610
12231
|
}
|
|
11611
12232
|
return parts.join("\n");
|
|
11612
12233
|
}
|
|
11613
|
-
var HERO_CHARTER_PATH =
|
|
12234
|
+
var HERO_CHARTER_PATH = join25(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
11614
12235
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
11615
12236
|
const name = run.name || `#${run.id}`;
|
|
11616
|
-
const charter =
|
|
12237
|
+
const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
|
|
11617
12238
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
11618
12239
|
|
|
11619
12240
|
${task.description}` : `\u042D\u041F\u0418\u041A ${run.task_slug} (\u0442\u0435\u043B\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E slug \u0438 \u0437\u0434\u0440\u0430\u0432\u043E\u043C\u0443 \u0441\u043C\u044B\u0441\u043B\u0443).`;
|
|
@@ -11864,7 +12485,7 @@ async function wakeRun(deps, runId) {
|
|
|
11864
12485
|
if (!run) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u0437\u0435\u0440\u043A\u0430\u043B\u0435 \u0440\u043E\u044F` };
|
|
11865
12486
|
if (run.pid != null && deps.isAlive(run.pid)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0443\u0436\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 (pid ${run.pid}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
|
|
11866
12487
|
if (isTerminalStatus(run.status)) return { ok: false, why: `\u043F\u0440\u043E\u0433\u043E\u043D #${runId} \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435\u043D (${run.status}) \u2014 \u0431\u0443\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E` };
|
|
11867
|
-
const worktreeExists = deps.worktreeExists ??
|
|
12488
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
11868
12489
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
11869
12490
|
const wt = run.worktree_path;
|
|
11870
12491
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -11949,7 +12570,7 @@ function repoHasCi(deps, run) {
|
|
|
11949
12570
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
11950
12571
|
for (const base3 of [worktreePath, repo]) {
|
|
11951
12572
|
if (!base3) continue;
|
|
11952
|
-
const dir =
|
|
12573
|
+
const dir = join25(base3, ".github", "workflows");
|
|
11953
12574
|
try {
|
|
11954
12575
|
if (readdirSync10(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
11955
12576
|
} catch {
|
|
@@ -12098,7 +12719,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
12098
12719
|
return true;
|
|
12099
12720
|
}
|
|
12100
12721
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
12101
|
-
const exists = deps.worktreeExists ??
|
|
12722
|
+
const exists = deps.worktreeExists ?? existsSync15;
|
|
12102
12723
|
const transcript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12103
12724
|
return exists(worktree) && transcript(worktree);
|
|
12104
12725
|
}
|
|
@@ -12241,6 +12862,15 @@ async function exclusiveGateAllows(deps, run) {
|
|
|
12241
12862
|
}
|
|
12242
12863
|
return true;
|
|
12243
12864
|
}
|
|
12865
|
+
function pruneGateLogs(deps) {
|
|
12866
|
+
if (!deps.envGateLogged?.size && !deps.exclusiveLogged?.size) return;
|
|
12867
|
+
const open = /* @__PURE__ */ new Set();
|
|
12868
|
+
for (const run of deps.db.listRuns()) if (!isTerminalStatus(run.status)) open.add(run.id);
|
|
12869
|
+
for (const map of [deps.envGateLogged, deps.exclusiveLogged]) {
|
|
12870
|
+
if (!map) continue;
|
|
12871
|
+
for (const id of [...map.keys()]) if (!open.has(id)) map.delete(id);
|
|
12872
|
+
}
|
|
12873
|
+
}
|
|
12244
12874
|
async function exclusivityBlockedRunIds(deps) {
|
|
12245
12875
|
const blocked = /* @__PURE__ */ new Set();
|
|
12246
12876
|
if (!deps.exclusiveProject) return blocked;
|
|
@@ -12303,7 +12933,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
12303
12933
|
}
|
|
12304
12934
|
if (!candidate) return;
|
|
12305
12935
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
12306
|
-
const worktreeExists = deps.worktreeExists ??
|
|
12936
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12307
12937
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12308
12938
|
const wt = candidate.worktree_path;
|
|
12309
12939
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -12472,7 +13102,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
12472
13102
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
12473
13103
|
if (!deps.respawn) return;
|
|
12474
13104
|
const wt = resumeCandidate.worktree_path;
|
|
12475
|
-
const worktreeExists = deps.worktreeExists ??
|
|
13105
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12476
13106
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12477
13107
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
12478
13108
|
const mode = canContinue ? "continue" : "fresh";
|
|
@@ -12634,7 +13264,7 @@ function buildEscalationCandidates(db) {
|
|
|
12634
13264
|
}
|
|
12635
13265
|
async function tickOnce(deps) {
|
|
12636
13266
|
const now = deps.now ? deps.now() : Date.now();
|
|
12637
|
-
const worktreeExists = deps.worktreeExists ??
|
|
13267
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12638
13268
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12639
13269
|
if (deps.rehydrate) {
|
|
12640
13270
|
const rh = deps.rehydrate;
|
|
@@ -12686,6 +13316,18 @@ async function tickOnce(deps) {
|
|
|
12686
13316
|
await dv.run();
|
|
12687
13317
|
}
|
|
12688
13318
|
}
|
|
13319
|
+
if (deps.repoMap) {
|
|
13320
|
+
const rm3 = deps.repoMap;
|
|
13321
|
+
const sinceLast = rm3.lastAt == null ? Infinity : now - rm3.lastAt;
|
|
13322
|
+
if (sinceLast >= rm3.intervalMs) {
|
|
13323
|
+
rm3.lastAt = now;
|
|
13324
|
+
try {
|
|
13325
|
+
await rm3.run();
|
|
13326
|
+
} catch (e) {
|
|
13327
|
+
console.error("[overmind] \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043A\u0430\u0440\u0442\u044B \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:", e);
|
|
13328
|
+
}
|
|
13329
|
+
}
|
|
13330
|
+
}
|
|
12689
13331
|
if (deps.rehydrate && deps.kill) {
|
|
12690
13332
|
const kill = deps.kill;
|
|
12691
13333
|
for (const run of deps.db.listRuns()) {
|
|
@@ -12711,6 +13353,19 @@ async function tickOnce(deps) {
|
|
|
12711
13353
|
}
|
|
12712
13354
|
}
|
|
12713
13355
|
}
|
|
13356
|
+
if (deps.logRotate) {
|
|
13357
|
+
const lr = deps.logRotate;
|
|
13358
|
+
const sinceLast = lr.lastAt == null ? Infinity : now - lr.lastAt;
|
|
13359
|
+
if (sinceLast >= lr.intervalMs) {
|
|
13360
|
+
lr.lastAt = now;
|
|
13361
|
+
try {
|
|
13362
|
+
lr.run();
|
|
13363
|
+
} catch (e) {
|
|
13364
|
+
console.error("[overmind] \u0440\u043E\u0442\u0430\u0446\u0438\u044F \u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C:", e);
|
|
13365
|
+
}
|
|
13366
|
+
}
|
|
13367
|
+
}
|
|
13368
|
+
pruneGateLogs(deps);
|
|
12714
13369
|
let stormActive = false;
|
|
12715
13370
|
if (deps.storm) {
|
|
12716
13371
|
for (const run of deps.db.listRuns("running")) {
|
|
@@ -13240,7 +13895,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
13240
13895
|
workspace_id: reviewRun.workspace_id
|
|
13241
13896
|
// та же задача, тот же воркспейс (TASWM-288)
|
|
13242
13897
|
});
|
|
13243
|
-
const wt =
|
|
13898
|
+
const wt = join25(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
13244
13899
|
try {
|
|
13245
13900
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
13246
13901
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -13292,7 +13947,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
13292
13947
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
13293
13948
|
workspace_id: reviewRun.workspace_id
|
|
13294
13949
|
});
|
|
13295
|
-
const wt =
|
|
13950
|
+
const wt = join25(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
13296
13951
|
try {
|
|
13297
13952
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
13298
13953
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -13327,7 +13982,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
13327
13982
|
]);
|
|
13328
13983
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
13329
13984
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
13330
|
-
if (!
|
|
13985
|
+
if (!existsSync15(wt)) {
|
|
13331
13986
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
13332
13987
|
const source = await addWorktree(repo, wt, branch, {
|
|
13333
13988
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -13352,11 +14007,11 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
13352
14007
|
const prompt = notice ? `${freshPrompt}
|
|
13353
14008
|
|
|
13354
14009
|
${notice}` : freshPrompt;
|
|
13355
|
-
const pid = deps.launch({ worktree: wt, run, prompt });
|
|
14010
|
+
const pid = deps.launch({ worktree: wt, run: runToSpawn, prompt });
|
|
13356
14011
|
deps.db.updateRun(run.id, { status: "running", pid });
|
|
13357
14012
|
}
|
|
13358
14013
|
async function syncCrashedOnStartup(deps) {
|
|
13359
|
-
const worktreeExists = deps.worktreeExists ??
|
|
14014
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
13360
14015
|
let orphaned = 0;
|
|
13361
14016
|
let merged = 0;
|
|
13362
14017
|
let requeued = 0;
|
|
@@ -13419,39 +14074,37 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
13419
14074
|
console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u044C \u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E (${e instanceof Error ? e.message : e}) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u0441\u043D\u043E\u0432\u0430 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0442 --repo`);
|
|
13420
14075
|
}
|
|
13421
14076
|
}
|
|
14077
|
+
const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir4(), repoPaths);
|
|
14078
|
+
const repoMap = emptyRepoMapState(scanRootsList);
|
|
13422
14079
|
if (SWARM_BACKEND !== "client") {
|
|
13423
14080
|
const { SwarmDb: SwarmDb2 } = await loadSqlite();
|
|
13424
|
-
return { store: new SwarmDb2(DB_PATH, repo), reconciled: true, host };
|
|
14081
|
+
return { store: new SwarmDb2(DB_PATH, repo), reconciled: true, host, repoMap };
|
|
13425
14082
|
}
|
|
13426
14083
|
const call = resilientSwarmMcp({
|
|
13427
14084
|
onDown: (ms) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u0440\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0441\u0434\u0430\u0447\u0438 \u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u044B \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u044E\u0442.`),
|
|
13428
14085
|
onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
|
|
13429
14086
|
});
|
|
13430
14087
|
const explicitRegistry = await buildRepoRegistry(repoPaths);
|
|
13431
|
-
|
|
13432
|
-
const scanRootsList = defaultScanRoots(homedir3(), repoPaths);
|
|
13433
|
-
const scanStartedAt = Date.now();
|
|
13434
|
-
const scanned = scanRoots(scanRootsList, { maxDepth: SWARM_SCAN_DEPTH });
|
|
13435
|
-
let addedByScan = 0;
|
|
13436
|
-
for (const [full, path] of scanned) {
|
|
13437
|
-
if (byName.has(full)) continue;
|
|
13438
|
-
byName.set(full, path);
|
|
13439
|
-
addedByScan++;
|
|
13440
|
-
}
|
|
13441
|
-
if (addedByScan > 0) {
|
|
13442
|
-
console.log(
|
|
13443
|
-
`[overmind] \u043D\u0430\u0448\u0451\u043B \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 ${addedByScan} \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439(\u0435\u0432) \u0441\u043A\u0430\u043D\u043E\u043C \u0437\u0430 ${Date.now() - scanStartedAt}\u043C\u0441: ${[...scanned.keys()].slice(0, 8).join(", ")}${scanned.size > 8 ? ` \u0438 \u0435\u0449\u0451 ${scanned.size - 8}` : ""}`
|
|
13444
|
-
);
|
|
13445
|
-
}
|
|
13446
|
-
const repoRegistry = [...byName].map(([repoFullName, path]) => ({ repoFullName, path }));
|
|
13447
|
-
if (repoPaths.length > 0 && repoRegistry.length < repoPaths.length) {
|
|
14088
|
+
if (repoPaths.length > 0 && explicitRegistry.length < repoPaths.length) {
|
|
13448
14089
|
console.warn(
|
|
13449
|
-
`[overmind] \u0440\u0435\u043F\u043E-\u0440\u0435\u0435\u0441\u0442\u0440: ${
|
|
14090
|
+
`[overmind] \u0440\u0435\u043F\u043E-\u0440\u0435\u0435\u0441\u0442\u0440: ${explicitRegistry.length}/${repoPaths.length} \u043F\u0443\u0442\u0435\u0439 \u0440\u0435\u0437\u043E\u043B\u0432\u043D\u0443\u043B\u0438\u0441\u044C \u0432 repoFullName (\u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u2014 \u043D\u0435 git-\u0440\u0435\u043F\u043E \u0438\u043B\u0438 \u0431\u0435\u0437 remote origin)`
|
|
13450
14091
|
);
|
|
13451
14092
|
}
|
|
14093
|
+
const rebuildRegistry = () => refreshRepoMap({
|
|
14094
|
+
call,
|
|
14095
|
+
configured: explicitRegistry,
|
|
14096
|
+
roots: scanRootsList,
|
|
14097
|
+
maxDepth: SWARM_SCAN_DEPTH,
|
|
14098
|
+
cachePath: REPO_MAP_PATH,
|
|
14099
|
+
state: repoMap,
|
|
14100
|
+
log: (m) => console.log(m)
|
|
14101
|
+
});
|
|
14102
|
+
const repoRegistry = await rebuildRegistry();
|
|
13452
14103
|
if (repoRegistry.length === 0) {
|
|
13453
14104
|
console.warn(
|
|
13454
|
-
`[overmind] \u041D\u0415 \u041D\u0410\u0428\u0401\u041B \u041D\u0418 \u041E\u0414\u041D\u041E\u0413\u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u2014 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0431\u0440\u0430\u0442\u044C\u0441\u044F \u043D\u0435 \u0431\u0443\u0434\u0443\u0442: \u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0438\u043C\u0435\u043D\u0435\u043C owner/repo, \u0430 \u0441\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u0441 \u0447\u0435\u043C. \u0418\u0441\u043A\u0430\u043B \u0432: ${scanRootsList.slice(0, 6).join(", ")}.
|
|
14105
|
+
`[overmind] \u041D\u0415 \u041D\u0410\u0428\u0401\u041B \u041D\u0418 \u041E\u0414\u041D\u041E\u0413\u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u2014 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0431\u0440\u0430\u0442\u044C\u0441\u044F \u043D\u0435 \u0431\u0443\u0434\u0443\u0442: \u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0438\u043C\u0435\u043D\u0435\u043C owner/repo, \u0430 \u0441\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u0441 \u0447\u0435\u043C. \u0418\u0441\u043A\u0430\u043B \u0432: ${scanRootsList.slice(0, 6).join(", ")}. ` + // TASWM-336: почему пусто — три РАЗНЫЕ причины, и лечатся они по-разному. Одна
|
|
14106
|
+
// общая фраза заставляла бы человека гадать, что из этого его случай.
|
|
14107
|
+
(repoMap.unknown ? "\u041F\u0440\u043E\u0434 \u043D\u0435 \u0441\u043A\u0430\u0437\u0430\u043B, \u043A\u0430\u043A\u0438\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0443 \u0440\u043E\u044F, \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C `swarm login` \u0438 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C \u0441\u0435\u0440\u0432\u0435\u0440\u0430. " : repoMap.missing.length ? `\u041D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0438\u0437 ${repoMap.missing.length} \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u0440\u043E\u044F \u0442\u0443\u0442 \u043D\u0435\u0442. ` : "\u0423 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u043D\u0435\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u043C \u0440\u043E\u0435\u043C. ") + "\u0415\u0441\u043B\u0438 \u043A\u043B\u043E\u043D\u044B \u043B\u0435\u0436\u0430\u0442 \u0432 \u0434\u0440\u0443\u0433\u043E\u043C \u043C\u0435\u0441\u0442\u0435 \u2014 \u0443\u043A\u0430\u0436\u0438: swarm start --repo <\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E> (\u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u0441\u044F)."
|
|
13455
14108
|
);
|
|
13456
14109
|
}
|
|
13457
14110
|
const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry, host });
|
|
@@ -13466,7 +14119,17 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
13466
14119
|
`overmind backend=client (\u0442\u043E\u043D\u043A\u0438\u0439 \u043A\u043B\u0438\u0435\u043D\u0442, outbox=${SWARM_OUTBOX}); \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F (ps \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D) \u2014 running/blocked \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F \u043D\u0435\u0440\u0435\u043A\u043E\u043D\u0441\u0438\u043B\u0438\u0440\u043E\u0432\u0430\u043D\u044B, startup-sync (TASWM-128) \u0431\u0443\u0434\u0435\u0442 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D`
|
|
13467
14120
|
);
|
|
13468
14121
|
}
|
|
13469
|
-
return {
|
|
14122
|
+
return {
|
|
14123
|
+
store: client,
|
|
14124
|
+
reconciled: rec.reconciled,
|
|
14125
|
+
host,
|
|
14126
|
+
repoMap,
|
|
14127
|
+
// Периодическая пересборка (шаг 0a4 тика): применяем новый реестр к ЖИВОМУ клиенту —
|
|
14128
|
+
// и список для claim/heartbeat, и обратную карту owner/repo → путь.
|
|
14129
|
+
refreshRepoMap: async () => {
|
|
14130
|
+
client.setRepoRegistry(await rebuildRegistry());
|
|
14131
|
+
}
|
|
14132
|
+
};
|
|
13470
14133
|
}
|
|
13471
14134
|
var crashGuardsInstalled = false;
|
|
13472
14135
|
function installCrashGuards() {
|
|
@@ -13485,7 +14148,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
13485
14148
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
13486
14149
|
targetDir: WORKTREES_DIR,
|
|
13487
14150
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
13488
|
-
exists: (d) =>
|
|
14151
|
+
exists: (d) => existsSync15(d),
|
|
13489
14152
|
isNonEmptyDir: (d) => {
|
|
13490
14153
|
try {
|
|
13491
14154
|
return readdirSync10(d).length > 0;
|
|
@@ -13494,13 +14157,13 @@ async function migrateWorktreesAtStartup() {
|
|
|
13494
14157
|
}
|
|
13495
14158
|
},
|
|
13496
14159
|
liveDronesUnder: (d) => pidsWithCwdUnder(d).length > 0,
|
|
13497
|
-
rename: (from, to) =>
|
|
14160
|
+
rename: (from, to) => renameSync10(from, to),
|
|
13498
14161
|
repairMoved: (t) => repairMovedWorktrees(t),
|
|
13499
14162
|
log: (m) => console.log(m)
|
|
13500
14163
|
});
|
|
13501
14164
|
} catch (e) {
|
|
13502
14165
|
console.error("[migrate] \u043C\u0438\u0433\u0440\u0430\u0446\u0438\u044F worktrees \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C \u2014 \u0444\u043E\u043B\u0431\u044D\u043A:", e);
|
|
13503
|
-
return
|
|
14166
|
+
return existsSync15(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
13504
14167
|
}
|
|
13505
14168
|
}
|
|
13506
14169
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -13518,13 +14181,22 @@ async function reportWorkspaceAccess(tl, db) {
|
|
|
13518
14181
|
async function runDaemon(opts) {
|
|
13519
14182
|
installCrashGuards();
|
|
13520
14183
|
const daemonStart = await captureDaemonStart(THIS_FILE);
|
|
14184
|
+
const latestRelease = fetchLatestVersion();
|
|
13521
14185
|
const worktreesDir = await migrateWorktreesAtStartup();
|
|
13522
14186
|
if (!opts.repo) {
|
|
13523
14187
|
const remembered = effectiveRepoPaths("", loadRememberedRepos(SWARM_HOME))[0];
|
|
13524
14188
|
if (remembered) opts.repo = remembered;
|
|
13525
14189
|
}
|
|
13526
|
-
const {
|
|
14190
|
+
const {
|
|
14191
|
+
store: db,
|
|
14192
|
+
reconciled,
|
|
14193
|
+
host,
|
|
14194
|
+
repoMap: repoMapState,
|
|
14195
|
+
refreshRepoMap: rebuildRepoMap
|
|
14196
|
+
} = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
|
|
13527
14197
|
await preflightAuth();
|
|
14198
|
+
const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
|
|
14199
|
+
if (outdated) console.warn(`[overmind] ${outdated}`);
|
|
13528
14200
|
const tl = await connectTaskless();
|
|
13529
14201
|
const tlFor = (workspaceId) => tl ? tl.forWorkspace(workspaceId) : null;
|
|
13530
14202
|
if (tl) await reportWorkspaceAccess(tl, db);
|
|
@@ -13555,6 +14227,8 @@ async function runDaemon(opts) {
|
|
|
13555
14227
|
}
|
|
13556
14228
|
} : void 0;
|
|
13557
14229
|
let lastDroneCap = null;
|
|
14230
|
+
const hostCommandState = hostCommandStatePath(SWARM_HOME);
|
|
14231
|
+
let hostMaxDrones = loadMaxDronesOverride(hostCommandState);
|
|
13558
14232
|
const launch = makeLauncher({
|
|
13559
14233
|
overmindEntry: THIS_FILE,
|
|
13560
14234
|
dbPath: DB_PATH,
|
|
@@ -13582,6 +14256,10 @@ async function runDaemon(opts) {
|
|
|
13582
14256
|
const client = db;
|
|
13583
14257
|
rehydrate = { run: () => client.hydrate(), ready: () => client.pendingCount() === 0, intervalMs: SWARM_REHYDRATE_MS };
|
|
13584
14258
|
}
|
|
14259
|
+
let repoMap;
|
|
14260
|
+
if (rebuildRepoMap && SWARM_REPO_MAP_MS > 0) {
|
|
14261
|
+
repoMap = { run: rebuildRepoMap, intervalMs: SWARM_REPO_MAP_MS, lastAt: Date.now() };
|
|
14262
|
+
}
|
|
13585
14263
|
let daemonVersion;
|
|
13586
14264
|
if (daemonStart && SWARM_VERSION_CHECK_MS > 0) {
|
|
13587
14265
|
let cached = null;
|
|
@@ -13593,22 +14271,26 @@ async function runDaemon(opts) {
|
|
|
13593
14271
|
intervalMs: SWARM_VERSION_CHECK_MS
|
|
13594
14272
|
};
|
|
13595
14273
|
}
|
|
14274
|
+
let onHostCommands = () => {
|
|
14275
|
+
};
|
|
13596
14276
|
let heartbeat;
|
|
13597
14277
|
if (SWARM_BACKEND === "client" && db instanceof SwarmClient && SWARM_PROD_HEARTBEAT_MS > 0) {
|
|
13598
14278
|
const client = db;
|
|
13599
14279
|
heartbeat = {
|
|
13600
14280
|
push: async () => {
|
|
13601
|
-
const
|
|
13602
|
-
if (commands.length
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
14281
|
+
const reply = await client.heartbeat(daemonVersion?.get() ?? null, control.state ?? null);
|
|
14282
|
+
if (reply.commands.length) onHostCommands(reply.commands);
|
|
14283
|
+
if (reply.logRequests.length) {
|
|
14284
|
+
await serveLogRequests(
|
|
14285
|
+
{
|
|
14286
|
+
runLogsDir: RUN_LOGS_DIR,
|
|
14287
|
+
daemonLogPath: DAEMON_LOG_PATH,
|
|
14288
|
+
localRunId: (prodRunId) => client.localRunIdForProd(prodRunId),
|
|
14289
|
+
deliver: (requestId, payload) => client.deliverLog(requestId, payload)
|
|
14290
|
+
},
|
|
14291
|
+
reply.logRequests
|
|
14292
|
+
);
|
|
14293
|
+
}
|
|
13612
14294
|
},
|
|
13613
14295
|
intervalMs: SWARM_PROD_HEARTBEAT_MS
|
|
13614
14296
|
};
|
|
@@ -13682,7 +14364,7 @@ async function runDaemon(opts) {
|
|
|
13682
14364
|
fallback: SWARM_MAX_IN_FLIGHT > 0 ? SWARM_MAX_IN_FLIGHT : 3,
|
|
13683
14365
|
ttlMs: 1e4
|
|
13684
14366
|
});
|
|
13685
|
-
maxDrones = async () => lastDroneCap = await readMaxDrones();
|
|
14367
|
+
maxDrones = async () => lastDroneCap = hostMaxDrones ?? await readMaxDrones();
|
|
13686
14368
|
}
|
|
13687
14369
|
let verifyModeReader;
|
|
13688
14370
|
let baseBranchReader;
|
|
@@ -13809,8 +14491,9 @@ async function runDaemon(opts) {
|
|
|
13809
14491
|
baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve("")
|
|
13810
14492
|
]);
|
|
13811
14493
|
const task = taskClient ? requireTaskBody(rawTask, run.task_slug, run.workspace_id) : null;
|
|
13812
|
-
const
|
|
13813
|
-
const
|
|
14494
|
+
const healed = healTaskSlug(db, run, task);
|
|
14495
|
+
const base3 = effectiveBaseBranch(healed.base_branch, projectBase);
|
|
14496
|
+
const runToSpawn = base3 && healed.base_branch !== base3 ? db.updateRun(healed.id, { base_branch: base3 }) ?? healed : healed;
|
|
13814
14497
|
const taskContext = task && taskClient ? await fetchTaskContext(taskClient, task) : void 0;
|
|
13815
14498
|
await spawnDrone(
|
|
13816
14499
|
{
|
|
@@ -13828,7 +14511,7 @@ async function runDaemon(opts) {
|
|
|
13828
14511
|
runToSpawn
|
|
13829
14512
|
);
|
|
13830
14513
|
if (task) db.updateRun(run.id, { task_title: task.title, task_desc: task.description, task_url: task.url });
|
|
13831
|
-
if (taskClient) await taskClient.startTask(
|
|
14514
|
+
if (taskClient) await taskClient.startTask(runToSpawn.task_slug);
|
|
13832
14515
|
},
|
|
13833
14516
|
prStatus: (url) => prStatus(url).then((s) => ({ merged: s.merged, conflicting: s.conflicting, isDraft: s.isDraft, files: s.files, state: s.state })),
|
|
13834
14517
|
// Открытые PR ветки прогона (готча №14): gh pr list --head <slug> в репо прогона
|
|
@@ -13941,6 +14624,22 @@ async function runDaemon(opts) {
|
|
|
13941
14624
|
...exclusiveProject ? { exclusiveProject } : {},
|
|
13942
14625
|
repoFallback: opts.repo,
|
|
13943
14626
|
exclusiveLogged: /* @__PURE__ */ new Map(),
|
|
14627
|
+
// Ротация своего лога (TASWM-330): демон пишет в него неделями, а до этого ротация была
|
|
14628
|
+
// только при старте. Итог ротации печатаем в сам лог — первой строкой нового поколения
|
|
14629
|
+
// видно, что история не потерялась, а уехала в `.1`.
|
|
14630
|
+
logRotate: {
|
|
14631
|
+
run: () => {
|
|
14632
|
+
if (!stdoutGoesTo(DAEMON_LOG_PATH)) return;
|
|
14633
|
+
const outcome = rotateDaemonLog(DAEMON_LOG_PATH, { maxBytes: DAEMON_LOG_MAX_BYTES, keep: DAEMON_LOG_KEEP });
|
|
14634
|
+
if (outcome === "rotated") {
|
|
14635
|
+
console.log(
|
|
14636
|
+
`[overmind] \u043B\u043E\u0433 \u0434\u043E\u0440\u043E\u0441 \u0434\u043E ${Math.round(DAEMON_LOG_MAX_BYTES / 1024 / 1024)} \u041C\u0411 \u2014 \u043F\u0440\u043E\u0448\u043B\u043E\u0435 \u043F\u043E\u043A\u043E\u043B\u0435\u043D\u0438\u0435 \u0432 ${DAEMON_LOG_PATH}.1 (\u0434\u0435\u0440\u0436\u0438\u043C ${DAEMON_LOG_KEEP}; \u043F\u043E\u0442\u043E\u043B\u043E\u043A \u2014 SWARM_DAEMON_LOG_MAX_BYTES)`
|
|
14637
|
+
);
|
|
14638
|
+
}
|
|
14639
|
+
},
|
|
14640
|
+
intervalMs: DAEMON_LOG_CHECK_MS,
|
|
14641
|
+
lastAt: null
|
|
14642
|
+
},
|
|
13944
14643
|
// merge-main-in: review-PR конфликтует с main → влить main в ветку PR, не переделывая
|
|
13945
14644
|
// фичу. Реальные git-примитивы + launch; логика — в resolveConflict (тестируется).
|
|
13946
14645
|
resolveConflict: (reviewRun) => resolveConflict(
|
|
@@ -13980,7 +14679,7 @@ async function runDaemon(opts) {
|
|
|
13980
14679
|
mode,
|
|
13981
14680
|
nudge
|
|
13982
14681
|
),
|
|
13983
|
-
worktreeExists: (p) =>
|
|
14682
|
+
worktreeExists: (p) => existsSync15(p),
|
|
13984
14683
|
hasTranscript: (p) => sessionTranscriptExists(p),
|
|
13985
14684
|
// CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
|
|
13986
14685
|
// (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
|
|
@@ -14008,6 +14707,8 @@ async function runDaemon(opts) {
|
|
|
14008
14707
|
...heartbeat ? { heartbeat } : {},
|
|
14009
14708
|
// Сверка кода демона с main (TASWM-251): ключ есть только когда entryFile в git-репо.
|
|
14010
14709
|
...daemonVersion ? { daemonVersion } : {},
|
|
14710
|
+
// Карта репозиториев машины (TASWM-336): ключ есть только в client-режиме с непустым TTL.
|
|
14711
|
+
...repoMap ? { repoMap } : {},
|
|
14011
14712
|
// Встроенный надзор (client-режим, TASWM-194): ключ есть только когда сконфигурен.
|
|
14012
14713
|
...anomalies ? { anomalies } : {},
|
|
14013
14714
|
// Сторож прогресса роя (TASWM-270): в отличие от anomalies выше — заведён БЕЗУСЛОВНО
|
|
@@ -14071,6 +14772,8 @@ async function runDaemon(opts) {
|
|
|
14071
14772
|
narrator.narrate(`\u23F3 \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u043B \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0439 \u0440\u0435\u0441\u0442\u0430\u0440\u0442 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0436\u0434\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439: ${sessions.length}`);
|
|
14072
14773
|
return { waiting: true, sessions };
|
|
14073
14774
|
};
|
|
14775
|
+
let onRestartWaitCancelled = null;
|
|
14776
|
+
let onRestartStillWaiting = null;
|
|
14074
14777
|
const requestStop = (o) => {
|
|
14075
14778
|
const sessions = liveSessionIds(db, pidAlive);
|
|
14076
14779
|
if (o.force || sessions.length === 0) {
|
|
@@ -14102,8 +14805,78 @@ async function runDaemon(opts) {
|
|
|
14102
14805
|
restartAutoPaused = false;
|
|
14103
14806
|
anomalyState.markResumed(Date.now());
|
|
14104
14807
|
}
|
|
14808
|
+
onRestartWaitCancelled?.();
|
|
14105
14809
|
narrator.narrate(wasStop ? "\u25B6\uFE0F \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043E\u0442\u043C\u0435\u043D\u0438\u043B \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u0434\u0435\u043C\u043E\u043D\u0430" : "\u25B6\uFE0F \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043E\u0442\u043C\u0435\u043D\u0438\u043B \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0433\u043E \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u0434\u0435\u043C\u043E\u043D\u0430");
|
|
14106
14810
|
};
|
|
14811
|
+
if (SWARM_BACKEND === "client" && db instanceof SwarmClient) {
|
|
14812
|
+
const client = db;
|
|
14813
|
+
const ack = (id, status, patch) => client.ackCommand(id, status, patch ?? {});
|
|
14814
|
+
const effects = {
|
|
14815
|
+
pause: () => {
|
|
14816
|
+
control.paused = true;
|
|
14817
|
+
},
|
|
14818
|
+
resume: () => {
|
|
14819
|
+
if (restart.phase === "waiting") return false;
|
|
14820
|
+
control.paused = false;
|
|
14821
|
+
return true;
|
|
14822
|
+
},
|
|
14823
|
+
requestRestart: () => requestRestart(false),
|
|
14824
|
+
setMaxDrones: (n) => {
|
|
14825
|
+
hostMaxDrones = n;
|
|
14826
|
+
lastDroneCap = n;
|
|
14827
|
+
},
|
|
14828
|
+
stopRun: async (slug, reason) => {
|
|
14829
|
+
const run = await cancelRun(
|
|
14830
|
+
{
|
|
14831
|
+
db,
|
|
14832
|
+
kill: (pid) => killProcessGroup(pid, "SIGTERM"),
|
|
14833
|
+
removeWorktree: (repo, p) => removeWorktree(repo || opts.repo, p)
|
|
14834
|
+
},
|
|
14835
|
+
slug,
|
|
14836
|
+
reason
|
|
14837
|
+
);
|
|
14838
|
+
return run ? { id: run.id } : null;
|
|
14839
|
+
},
|
|
14840
|
+
update: async (onStep) => {
|
|
14841
|
+
if (!daemonStart) {
|
|
14842
|
+
return { ok: false, step: "fetch", message: "\u043A\u043E\u0434 \u0434\u0435\u043C\u043E\u043D\u0430 \u043B\u0435\u0436\u0438\u0442 \u043D\u0435 \u0432 git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E" };
|
|
14843
|
+
}
|
|
14844
|
+
return runSelfUpdate(defaultSelfUpdateIO(daemonStart.repo, onStep));
|
|
14845
|
+
},
|
|
14846
|
+
narrate: narrator.narrate
|
|
14847
|
+
};
|
|
14848
|
+
let commandQueue = Promise.resolve();
|
|
14849
|
+
onHostCommands = (commands) => {
|
|
14850
|
+
commandQueue = commandQueue.then(() => executeHostCommands({ commands, effects, ack, statePath: hostCommandState })).catch((e) => {
|
|
14851
|
+
console.error("[overmind] \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043C\u0430\u0448\u0438\u043D\u044B \u0441\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C:", e);
|
|
14852
|
+
});
|
|
14853
|
+
};
|
|
14854
|
+
onRestartWaitCancelled = () => {
|
|
14855
|
+
const pending2 = clearPendingAck(hostCommandState);
|
|
14856
|
+
if (!pending2) return;
|
|
14857
|
+
void ack(pending2.commandId, "rejected", {
|
|
14858
|
+
step: "restart",
|
|
14859
|
+
result: "\u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0442\u0430\u0440\u0442\u0430 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E \u043D\u0430 \u0441\u0430\u043C\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u2014 \u043E\u0442\u0434\u0430\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0437\u0430\u043D\u043E\u0432\u043E"
|
|
14860
|
+
}).catch(() => {
|
|
14861
|
+
});
|
|
14862
|
+
};
|
|
14863
|
+
let lastWaitAck = 0;
|
|
14864
|
+
onRestartStillWaiting = (sessions) => {
|
|
14865
|
+
const nowMs = Date.now();
|
|
14866
|
+
if (nowMs - lastWaitAck < HOST_COMMAND_KEEPALIVE_MS) return;
|
|
14867
|
+
const pending2 = readPendingAck(hostCommandState);
|
|
14868
|
+
if (!pending2) return;
|
|
14869
|
+
lastWaitAck = nowMs;
|
|
14870
|
+
void ack(pending2.commandId, "running", {
|
|
14871
|
+
step: "restart",
|
|
14872
|
+
result: `\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0449\u0443, \u043A\u043E\u0433\u0434\u0430 \u043E\u0441\u0432\u043E\u0431\u043E\u0434\u044F\u0442\u0441\u044F \u0441\u043B\u043E\u0442\u044B: \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442 ${sessions} \u2014 \u043E\u043D\u0438 \u0431\u044B \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u043B\u0438`
|
|
14873
|
+
}).catch(() => {
|
|
14874
|
+
});
|
|
14875
|
+
};
|
|
14876
|
+
void flushPendingAck(hostCommandState, ack).then((done) => {
|
|
14877
|
+
if (done) narrator.narrate(`\u2705 \u041A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u0437 Taskless \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430: ${done.note}`);
|
|
14878
|
+
});
|
|
14879
|
+
}
|
|
14107
14880
|
if (channelSecret) {
|
|
14108
14881
|
droneChannel = startDroneChannel(
|
|
14109
14882
|
{
|
|
@@ -14149,6 +14922,10 @@ async function runDaemon(opts) {
|
|
|
14149
14922
|
// Устаревший код демона (TASWM-251): тот же кэш, что читает heartbeat.push выше —
|
|
14150
14923
|
// локальный дашборд :7420 видит ту же сверку, что уходит в прод, без отдельного git-похода.
|
|
14151
14924
|
daemonVersion: () => daemonVersion?.get() ?? null,
|
|
14925
|
+
// Карта репозиториев машины (TASWM-336): то же состояние, что мутирует шаг тика, — его
|
|
14926
|
+
// печатает `swarm status`. Без него человек снова гадал бы, какие репозитории машина
|
|
14927
|
+
// считает своими и в каком дереве работает при двух копиях одного клона.
|
|
14928
|
+
repoMap: () => repoMapRows(repoMapState),
|
|
14152
14929
|
// Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
|
|
14153
14930
|
// narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
|
|
14154
14931
|
control,
|
|
@@ -14210,8 +14987,10 @@ async function runDaemon(opts) {
|
|
|
14210
14987
|
try {
|
|
14211
14988
|
writeHeartbeat(HEARTBEAT_PATH, Date.now());
|
|
14212
14989
|
await tickOnce(deps);
|
|
14213
|
-
if (restart.phase === "waiting"
|
|
14214
|
-
|
|
14990
|
+
if (restart.phase === "waiting") {
|
|
14991
|
+
const live = liveSessionIds(db, pidAlive);
|
|
14992
|
+
if (live.length === 0) performRestart([], restart.intent);
|
|
14993
|
+
else onRestartStillWaiting?.(live.length);
|
|
14215
14994
|
}
|
|
14216
14995
|
} catch (e) {
|
|
14217
14996
|
console.error("[overmind] tick error:", e);
|