@taskless-app/swarm 0.4.10 → 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 +1700 -610
- package/dist/overmind.js +603 -206
- 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
|
|
|
@@ -432,7 +437,7 @@ var init_text = __esm({
|
|
|
432
437
|
"use strict";
|
|
433
438
|
ANSI_CSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
434
439
|
CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/g;
|
|
435
|
-
EVENT_TEXT_MAX =
|
|
440
|
+
EVENT_TEXT_MAX = 16e3;
|
|
436
441
|
}
|
|
437
442
|
});
|
|
438
443
|
|
|
@@ -793,6 +798,9 @@ var init_src = __esm({
|
|
|
793
798
|
// src/config.ts
|
|
794
799
|
var config_exports = {};
|
|
795
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,
|
|
796
804
|
DAEMON_LOG_PATH: () => DAEMON_LOG_PATH,
|
|
797
805
|
DB_PATH: () => DB_PATH,
|
|
798
806
|
DRONE_CHANNEL_ENABLED: () => DRONE_CHANNEL_ENABLED,
|
|
@@ -805,6 +813,7 @@ __export(config_exports, {
|
|
|
805
813
|
DRONE_ISOLATION: () => DRONE_ISOLATION,
|
|
806
814
|
HEARTBEAT_PATH: () => HEARTBEAT_PATH,
|
|
807
815
|
HEARTBEAT_STALE_MS: () => HEARTBEAT_STALE_MS,
|
|
816
|
+
REPO_MAP_PATH: () => REPO_MAP_PATH,
|
|
808
817
|
REVIEW_CUTOFF_PATH: () => REVIEW_CUTOFF_PATH,
|
|
809
818
|
RUN_LOGS_DIR: () => RUN_LOGS_DIR,
|
|
810
819
|
SWARM_ANOMALY_FAILED_SPIKE_COUNT: () => SWARM_ANOMALY_FAILED_SPIKE_COUNT,
|
|
@@ -842,9 +851,11 @@ __export(config_exports, {
|
|
|
842
851
|
SWARM_RELIEF_MAX_FD_LOAD_FACTOR: () => SWARM_RELIEF_MAX_FD_LOAD_FACTOR,
|
|
843
852
|
SWARM_RELIEF_MIN_FREE_GB: () => SWARM_RELIEF_MIN_FREE_GB,
|
|
844
853
|
SWARM_REPO: () => SWARM_REPO,
|
|
854
|
+
SWARM_REPO_MAP_MS: () => SWARM_REPO_MAP_MS,
|
|
845
855
|
SWARM_REPO_PATHS: () => SWARM_REPO_PATHS,
|
|
846
856
|
SWARM_REVIEW_CAP: () => SWARM_REVIEW_CAP,
|
|
847
857
|
SWARM_SCAN_DEPTH: () => SWARM_SCAN_DEPTH,
|
|
858
|
+
SWARM_SCAN_ROOTS_RAW: () => SWARM_SCAN_ROOTS_RAW,
|
|
848
859
|
SWARM_SHOT_TIMEOUT_MS: () => SWARM_SHOT_TIMEOUT_MS,
|
|
849
860
|
SWARM_STORM_URL: () => SWARM_STORM_URL,
|
|
850
861
|
SWARM_SWEEP_INTERVAL_MS: () => SWARM_SWEEP_INTERVAL_MS,
|
|
@@ -944,7 +955,7 @@ function reviewCutoffAt() {
|
|
|
944
955
|
}
|
|
945
956
|
return at;
|
|
946
957
|
}
|
|
947
|
-
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;
|
|
948
959
|
var init_config = __esm({
|
|
949
960
|
"src/config.ts"() {
|
|
950
961
|
"use strict";
|
|
@@ -960,6 +971,9 @@ var init_config = __esm({
|
|
|
960
971
|
WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
|
|
961
972
|
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join2(SWARM_HOME, "run-logs");
|
|
962
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);
|
|
963
977
|
DIST_BUILD = true;
|
|
964
978
|
SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
|
|
965
979
|
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join2(SWARM_HOME, "outbox.jsonl");
|
|
@@ -994,6 +1008,9 @@ var init_config = __esm({
|
|
|
994
1008
|
SWARM_REPO = process.env.SWARM_REPO || "";
|
|
995
1009
|
SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
|
|
996
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);
|
|
997
1014
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
998
1015
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
999
1016
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
@@ -1486,7 +1503,7 @@ var init_procList = __esm({
|
|
|
1486
1503
|
|
|
1487
1504
|
// src/procCleanup.ts
|
|
1488
1505
|
function sleep(ms) {
|
|
1489
|
-
return new Promise((
|
|
1506
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1490
1507
|
}
|
|
1491
1508
|
function parseLsofCwd(output) {
|
|
1492
1509
|
const rows = [];
|
|
@@ -1704,16 +1721,16 @@ var init_procCleanup = __esm({
|
|
|
1704
1721
|
});
|
|
1705
1722
|
|
|
1706
1723
|
// src/git.ts
|
|
1707
|
-
import { existsSync as
|
|
1724
|
+
import { existsSync as existsSync8, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
|
|
1708
1725
|
import { appendFile, mkdir, readFile, rm } from "fs/promises";
|
|
1709
|
-
import { isAbsolute, join as
|
|
1726
|
+
import { isAbsolute as isAbsolute2, join as join12 } from "path";
|
|
1710
1727
|
import { $ as $2 } from "execa";
|
|
1711
1728
|
async function reclaimStaleWorktree(repo, worktree) {
|
|
1712
1729
|
await detachWorktree(repo, worktree);
|
|
1713
1730
|
}
|
|
1714
1731
|
function isLinkedWorktree(dir) {
|
|
1715
1732
|
try {
|
|
1716
|
-
return
|
|
1733
|
+
return statSync6(join12(dir, ".git")).isFile();
|
|
1717
1734
|
} catch {
|
|
1718
1735
|
return false;
|
|
1719
1736
|
}
|
|
@@ -1725,7 +1742,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
1725
1742
|
}
|
|
1726
1743
|
}
|
|
1727
1744
|
async function ensureWorktreeDirGone(worktree) {
|
|
1728
|
-
if (!
|
|
1745
|
+
if (!existsSync8(worktree)) return false;
|
|
1729
1746
|
if (!isLinkedWorktree(worktree)) {
|
|
1730
1747
|
console.error(
|
|
1731
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.`
|
|
@@ -1740,7 +1757,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
1740
1757
|
);
|
|
1741
1758
|
return true;
|
|
1742
1759
|
}
|
|
1743
|
-
if (
|
|
1760
|
+
if (existsSync8(worktree)) {
|
|
1744
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.`);
|
|
1745
1762
|
}
|
|
1746
1763
|
return true;
|
|
@@ -1783,7 +1800,7 @@ async function addWorktree(repo, path, branch, opts = {}) {
|
|
|
1783
1800
|
}
|
|
1784
1801
|
const busyAt = await worktreeForBranch(repo, branch);
|
|
1785
1802
|
if (busyAt) {
|
|
1786
|
-
const exists =
|
|
1803
|
+
const exists = existsSync8(busyAt);
|
|
1787
1804
|
const live = exists && (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(busyAt) : true);
|
|
1788
1805
|
if (live) throw new WorktreeBusyError(branch, busyAt);
|
|
1789
1806
|
if (exists && await worktreeDirty(busyAt)) throw new WorktreeBusyError(branch, busyAt);
|
|
@@ -1819,9 +1836,9 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
1819
1836
|
const out = await $2({ reject: false })`git -C ${worktree} rev-parse --git-common-dir`;
|
|
1820
1837
|
const raw = out.stdout.trim();
|
|
1821
1838
|
if (out.exitCode !== 0 || !raw) return false;
|
|
1822
|
-
const commonDir =
|
|
1823
|
-
const infoDir =
|
|
1824
|
-
const excludeFile =
|
|
1839
|
+
const commonDir = isAbsolute2(raw) ? raw : join12(worktree, raw);
|
|
1840
|
+
const infoDir = join12(commonDir, "info");
|
|
1841
|
+
const excludeFile = join12(infoDir, "exclude");
|
|
1825
1842
|
let current = "";
|
|
1826
1843
|
try {
|
|
1827
1844
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -1854,7 +1871,7 @@ async function worktreeDirty(path) {
|
|
|
1854
1871
|
async function repairMovedWorktrees(targetDir) {
|
|
1855
1872
|
const subdirs = (dir) => {
|
|
1856
1873
|
try {
|
|
1857
|
-
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));
|
|
1858
1875
|
} catch {
|
|
1859
1876
|
return [];
|
|
1860
1877
|
}
|
|
@@ -1966,18 +1983,18 @@ __export(droneChannel_exports, {
|
|
|
1966
1983
|
verifyLocalCliToken: () => verifyLocalCliToken
|
|
1967
1984
|
});
|
|
1968
1985
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
1969
|
-
import { chmodSync, mkdirSync as mkdirSync5, readFileSync as
|
|
1986
|
+
import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
1970
1987
|
import { dirname as dirname3 } from "path";
|
|
1971
1988
|
import { serve } from "@hono/node-server";
|
|
1972
1989
|
function loadChannelSecret(path) {
|
|
1973
1990
|
try {
|
|
1974
|
-
const s =
|
|
1991
|
+
const s = readFileSync13(path, "utf8").trim();
|
|
1975
1992
|
if (s.length >= 32) return s;
|
|
1976
1993
|
} catch {
|
|
1977
1994
|
}
|
|
1978
1995
|
const secret = randomBytes(32).toString("hex");
|
|
1979
1996
|
mkdirSync5(dirname3(path), { recursive: true });
|
|
1980
|
-
|
|
1997
|
+
writeFileSync9(path, secret, { mode: 384 });
|
|
1981
1998
|
try {
|
|
1982
1999
|
chmodSync(path, 384);
|
|
1983
2000
|
} catch {
|
|
@@ -2184,8 +2201,8 @@ function startDroneChannel(deps, port) {
|
|
|
2184
2201
|
return { port, server };
|
|
2185
2202
|
}
|
|
2186
2203
|
function closeDroneChannel(server) {
|
|
2187
|
-
return new Promise((
|
|
2188
|
-
server.close(() =>
|
|
2204
|
+
return new Promise((resolve4) => {
|
|
2205
|
+
server.close(() => resolve4());
|
|
2189
2206
|
server.closeAllConnections?.();
|
|
2190
2207
|
});
|
|
2191
2208
|
}
|
|
@@ -2307,7 +2324,7 @@ var init_droneChannel = __esm({
|
|
|
2307
2324
|
|
|
2308
2325
|
// src/attachments.ts
|
|
2309
2326
|
import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
|
|
2310
|
-
import { join as
|
|
2327
|
+
import { join as join14 } from "path";
|
|
2311
2328
|
function attachmentsFromDoc(doc, origin) {
|
|
2312
2329
|
const out = [];
|
|
2313
2330
|
const walk2 = (node) => {
|
|
@@ -2398,7 +2415,7 @@ async function materializeAttachments(opts) {
|
|
|
2398
2415
|
};
|
|
2399
2416
|
const result = { files: [], skipped: [] };
|
|
2400
2417
|
if (opts.refs.length === 0) return result;
|
|
2401
|
-
const dir =
|
|
2418
|
+
const dir = join14(opts.worktree, ATTACHMENTS_DIR);
|
|
2402
2419
|
await io.removeDir(dir);
|
|
2403
2420
|
let dirReady = false;
|
|
2404
2421
|
let total = 0;
|
|
@@ -2444,7 +2461,7 @@ async function materializeAttachments(opts) {
|
|
|
2444
2461
|
await io.mkdir(dir);
|
|
2445
2462
|
dirReady = true;
|
|
2446
2463
|
}
|
|
2447
|
-
await io.writeFile(
|
|
2464
|
+
await io.writeFile(join14(dir, name), file.bytes);
|
|
2448
2465
|
} catch (e) {
|
|
2449
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)})`);
|
|
2450
2467
|
continue;
|
|
@@ -2597,7 +2614,7 @@ var init_attachments = __esm({
|
|
|
2597
2614
|
|
|
2598
2615
|
// src/shotUpload.ts
|
|
2599
2616
|
import { readFile as fsReadFile } from "fs/promises";
|
|
2600
|
-
import { basename as basename2, isAbsolute as
|
|
2617
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join15 } from "path";
|
|
2601
2618
|
function shotMimetype(path) {
|
|
2602
2619
|
const dot = path.lastIndexOf(".");
|
|
2603
2620
|
if (dot < 0) return null;
|
|
@@ -2623,7 +2640,7 @@ async function readShots(paths, opts) {
|
|
|
2623
2640
|
}
|
|
2624
2641
|
let bytes;
|
|
2625
2642
|
try {
|
|
2626
|
-
bytes = await read(
|
|
2643
|
+
bytes = await read(isAbsolute3(path) ? path : join15(opts.worktree, path));
|
|
2627
2644
|
} catch (e) {
|
|
2628
2645
|
skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
|
|
2629
2646
|
continue;
|
|
@@ -2701,8 +2718,8 @@ var init_shotUpload = __esm({
|
|
|
2701
2718
|
|
|
2702
2719
|
// src/oauth.ts
|
|
2703
2720
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
2704
|
-
import { readFileSync as
|
|
2705
|
-
import { join as
|
|
2721
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
2722
|
+
import { join as join16 } from "path";
|
|
2706
2723
|
function makePkce() {
|
|
2707
2724
|
const verifier = randomBytes2(48).toString("base64url");
|
|
2708
2725
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -2786,23 +2803,23 @@ function toStore(clientId, t, now) {
|
|
|
2786
2803
|
}
|
|
2787
2804
|
function loadStore() {
|
|
2788
2805
|
try {
|
|
2789
|
-
return JSON.parse(
|
|
2806
|
+
return JSON.parse(readFileSync14(STORE_PATH, "utf8"));
|
|
2790
2807
|
} catch {
|
|
2791
2808
|
return null;
|
|
2792
2809
|
}
|
|
2793
2810
|
}
|
|
2794
2811
|
function saveStore(s) {
|
|
2795
|
-
|
|
2812
|
+
writeFileSync10(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
|
|
2796
2813
|
}
|
|
2797
2814
|
function clearStore() {
|
|
2798
2815
|
try {
|
|
2799
|
-
|
|
2816
|
+
unlinkSync3(STORE_PATH);
|
|
2800
2817
|
} catch {
|
|
2801
2818
|
}
|
|
2802
2819
|
}
|
|
2803
2820
|
function loadClientId() {
|
|
2804
2821
|
try {
|
|
2805
|
-
return JSON.parse(
|
|
2822
|
+
return JSON.parse(readFileSync14(CLIENT_PATH, "utf8")).clientId;
|
|
2806
2823
|
} catch {
|
|
2807
2824
|
return null;
|
|
2808
2825
|
}
|
|
@@ -2811,7 +2828,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
|
|
|
2811
2828
|
const existing = loadClientId();
|
|
2812
2829
|
if (existing) return existing;
|
|
2813
2830
|
const clientId = await registerClient({ api, redirectUri }, fetchFn);
|
|
2814
|
-
|
|
2831
|
+
writeFileSync10(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
|
|
2815
2832
|
return clientId;
|
|
2816
2833
|
}
|
|
2817
2834
|
async function ensureAccess(store, opts = {}) {
|
|
@@ -2837,18 +2854,18 @@ var init_oauth = __esm({
|
|
|
2837
2854
|
AUTHORIZE = "/api/oauth/authorize";
|
|
2838
2855
|
TOKEN = "/api/oauth/token";
|
|
2839
2856
|
SCOPE = "openid profile email taskless:full";
|
|
2840
|
-
STORE_PATH =
|
|
2841
|
-
CLIENT_PATH =
|
|
2857
|
+
STORE_PATH = join16(SWARM_HOME, "oauth.json");
|
|
2858
|
+
CLIENT_PATH = join16(SWARM_HOME, "oauth-client.json");
|
|
2842
2859
|
base = (api) => api.replace(/\/$/, "");
|
|
2843
2860
|
}
|
|
2844
2861
|
});
|
|
2845
2862
|
|
|
2846
2863
|
// src/agentToken.ts
|
|
2847
|
-
import { readFileSync as
|
|
2848
|
-
import { join as
|
|
2864
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
|
|
2865
|
+
import { join as join17 } from "path";
|
|
2849
2866
|
function loadAgentToken() {
|
|
2850
2867
|
try {
|
|
2851
|
-
const s = JSON.parse(
|
|
2868
|
+
const s = JSON.parse(readFileSync15(STORE_PATH2, "utf8"));
|
|
2852
2869
|
return s.token ? s : null;
|
|
2853
2870
|
} catch {
|
|
2854
2871
|
return null;
|
|
@@ -2885,17 +2902,17 @@ var init_agentToken = __esm({
|
|
|
2885
2902
|
"src/agentToken.ts"() {
|
|
2886
2903
|
"use strict";
|
|
2887
2904
|
init_config();
|
|
2888
|
-
STORE_PATH2 =
|
|
2905
|
+
STORE_PATH2 = join17(SWARM_HOME, "agent.json");
|
|
2889
2906
|
base2 = (api) => api.replace(/\/$/, "");
|
|
2890
2907
|
}
|
|
2891
2908
|
});
|
|
2892
2909
|
|
|
2893
2910
|
// src/workspace.ts
|
|
2894
|
-
import { readFileSync as
|
|
2895
|
-
import { join as
|
|
2911
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, unlinkSync as unlinkSync5 } from "fs";
|
|
2912
|
+
import { join as join18 } from "path";
|
|
2896
2913
|
function loadWorkspace() {
|
|
2897
2914
|
try {
|
|
2898
|
-
const w = JSON.parse(
|
|
2915
|
+
const w = JSON.parse(readFileSync16(WORKSPACE_PATH, "utf8"));
|
|
2899
2916
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
2900
2917
|
} catch {
|
|
2901
2918
|
return null;
|
|
@@ -2909,7 +2926,7 @@ var init_workspace = __esm({
|
|
|
2909
2926
|
"src/workspace.ts"() {
|
|
2910
2927
|
"use strict";
|
|
2911
2928
|
init_config();
|
|
2912
|
-
WORKSPACE_PATH =
|
|
2929
|
+
WORKSPACE_PATH = join18(SWARM_HOME, "workspace.json");
|
|
2913
2930
|
}
|
|
2914
2931
|
});
|
|
2915
2932
|
|
|
@@ -3336,8 +3353,8 @@ __export(db_exports, {
|
|
|
3336
3353
|
SwarmDb: () => SwarmDb
|
|
3337
3354
|
});
|
|
3338
3355
|
import { createRequire as createRequire2 } from "module";
|
|
3339
|
-
import { existsSync as
|
|
3340
|
-
import { resolve as
|
|
3356
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3357
|
+
import { resolve as resolve3 } from "path";
|
|
3341
3358
|
function rowToQuestion(row) {
|
|
3342
3359
|
return {
|
|
3343
3360
|
id: row.id,
|
|
@@ -3817,11 +3834,11 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3817
3834
|
* транзакции: прервётся — маркер не встанет, повтор безопасен.
|
|
3818
3835
|
*/
|
|
3819
3836
|
importDb(sourcePath, repo) {
|
|
3820
|
-
const abs =
|
|
3837
|
+
const abs = resolve3(sourcePath);
|
|
3821
3838
|
const repoN = normalizeRepo(repo);
|
|
3822
3839
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
3823
3840
|
if (already) return { imported: false, runs: already.run_count };
|
|
3824
|
-
if (!
|
|
3841
|
+
if (!existsSync10(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
3825
3842
|
const src = new this.databaseCtor(abs);
|
|
3826
3843
|
try {
|
|
3827
3844
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -4189,9 +4206,9 @@ var init_droneStore = __esm({
|
|
|
4189
4206
|
});
|
|
4190
4207
|
|
|
4191
4208
|
// src/overmind.ts
|
|
4192
|
-
import { homedir as
|
|
4193
|
-
import { existsSync as
|
|
4194
|
-
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";
|
|
4195
4212
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
4196
4213
|
import { spawn as spawn3 } from "child_process";
|
|
4197
4214
|
|
|
@@ -4601,6 +4618,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
4601
4618
|
runKeyFor(localId) {
|
|
4602
4619
|
return this.keyByLocal.get(localId);
|
|
4603
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
|
+
}
|
|
4604
4633
|
// cuid прода → локальный id (по runKey гидрированного прогона `p:<cuid>`).
|
|
4605
4634
|
// Для маппинга прод heroId → локальный hero_run_id при гидрации. Резолвится, когда
|
|
4606
4635
|
// прогон-герой присутствует в зеркале; иначе null (уточнится следующим рефрешем).
|
|
@@ -5820,8 +5849,9 @@ var SwarmClient = class _SwarmClient {
|
|
|
5820
5849
|
this.runs.set(id, local);
|
|
5821
5850
|
this.keyByLocal.set(id, runKey);
|
|
5822
5851
|
}
|
|
5823
|
-
const
|
|
5824
|
-
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;
|
|
5825
5855
|
local.task_title = pr.taskTitle ?? local.task_title;
|
|
5826
5856
|
local.task_desc = pr.taskDesc ?? local.task_desc;
|
|
5827
5857
|
local.name = pr.droneName ?? local.name;
|
|
@@ -6140,6 +6170,66 @@ function parseDecision(v) {
|
|
|
6140
6170
|
init_config();
|
|
6141
6171
|
init_heartbeat();
|
|
6142
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
|
+
|
|
6143
6233
|
// src/repoRegistry.ts
|
|
6144
6234
|
init_config();
|
|
6145
6235
|
import { $ } from "execa";
|
|
@@ -6209,8 +6299,15 @@ function pickRepoPaths(args) {
|
|
|
6209
6299
|
return { paths: [], source: "none" };
|
|
6210
6300
|
}
|
|
6211
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
|
+
|
|
6212
6309
|
// src/repoScan.ts
|
|
6213
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as
|
|
6310
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
6214
6311
|
import { join as join5 } from "path";
|
|
6215
6312
|
var SKIP = /* @__PURE__ */ new Set([
|
|
6216
6313
|
"node_modules",
|
|
@@ -6246,7 +6343,11 @@ function originOf(dir) {
|
|
|
6246
6343
|
return null;
|
|
6247
6344
|
}
|
|
6248
6345
|
}
|
|
6249
|
-
function
|
|
6346
|
+
function repoFullNameAt(dir) {
|
|
6347
|
+
const origin = originOf(dir);
|
|
6348
|
+
return origin ? parseRepoFullName(origin) : null;
|
|
6349
|
+
}
|
|
6350
|
+
function scanRootsAll(roots, opts = {}) {
|
|
6250
6351
|
const maxDepth = opts.maxDepth ?? 3;
|
|
6251
6352
|
const found = /* @__PURE__ */ new Map();
|
|
6252
6353
|
for (const root of roots) walk(root, 0, maxDepth, found);
|
|
@@ -6257,7 +6358,11 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
6257
6358
|
const origin = originOf(dir);
|
|
6258
6359
|
if (origin) {
|
|
6259
6360
|
const full = parseRepoFullName(origin);
|
|
6260
|
-
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
|
+
}
|
|
6261
6366
|
return;
|
|
6262
6367
|
}
|
|
6263
6368
|
let entries;
|
|
@@ -6270,7 +6375,7 @@ function walk(dir, depth, maxDepth, found) {
|
|
|
6270
6375
|
if (name.startsWith(".") || SKIP.has(name)) continue;
|
|
6271
6376
|
const child = join5(dir, name);
|
|
6272
6377
|
try {
|
|
6273
|
-
if (!
|
|
6378
|
+
if (!statSync3(child).isDirectory()) continue;
|
|
6274
6379
|
} catch {
|
|
6275
6380
|
continue;
|
|
6276
6381
|
}
|
|
@@ -6295,20 +6400,173 @@ function defaultScanRoots(home, knownPaths = []) {
|
|
|
6295
6400
|
return out;
|
|
6296
6401
|
}
|
|
6297
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
|
+
|
|
6298
6556
|
// src/hostIdentity.ts
|
|
6299
6557
|
import { hostname } from "os";
|
|
6300
|
-
import { existsSync as
|
|
6301
|
-
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";
|
|
6302
6560
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6303
6561
|
function hostIdentityPath(home) {
|
|
6304
|
-
return
|
|
6562
|
+
return join7(home, "host.json");
|
|
6305
6563
|
}
|
|
6306
6564
|
function loadHostIdentity(deps) {
|
|
6307
|
-
const exists = deps.exists ??
|
|
6308
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
6565
|
+
const exists = deps.exists ?? existsSync5;
|
|
6566
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
|
|
6309
6567
|
const writeFile2 = deps.writeFile ?? ((p, data) => {
|
|
6310
|
-
|
|
6311
|
-
|
|
6568
|
+
writeFileSync7(`${p}.tmp`, data);
|
|
6569
|
+
renameSync6(`${p}.tmp`, p);
|
|
6312
6570
|
});
|
|
6313
6571
|
const host = deps.hostname ?? hostname;
|
|
6314
6572
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -6490,14 +6748,14 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
|
|
|
6490
6748
|
|
|
6491
6749
|
// src/launcher.ts
|
|
6492
6750
|
import { spawn } from "child_process";
|
|
6493
|
-
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";
|
|
6494
6752
|
import { createRequire } from "module";
|
|
6495
|
-
import { homedir as
|
|
6496
|
-
import { dirname as dirname2, join as
|
|
6753
|
+
import { homedir as homedir3 } from "os";
|
|
6754
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
6497
6755
|
import { fileURLToPath } from "url";
|
|
6498
6756
|
|
|
6499
6757
|
// src/claudeBin.ts
|
|
6500
|
-
import { existsSync as
|
|
6758
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
6501
6759
|
import { win32 as winPath } from "path";
|
|
6502
6760
|
var CLAUDE_FALLBACK = "claude";
|
|
6503
6761
|
function compareVersionDirs(a, b) {
|
|
@@ -6534,7 +6792,7 @@ function winVersionRoots(env, listDir) {
|
|
|
6534
6792
|
function findWindowsClaude(opts = {}) {
|
|
6535
6793
|
const env = opts.env ?? process.env;
|
|
6536
6794
|
const listDir = opts.listDir ?? realListDir;
|
|
6537
|
-
const fileExists = opts.fileExists ??
|
|
6795
|
+
const fileExists = opts.fileExists ?? existsSync6;
|
|
6538
6796
|
let best = null;
|
|
6539
6797
|
for (const root of winVersionRoots(env, listDir)) {
|
|
6540
6798
|
for (const version of listDir(root)) {
|
|
@@ -6569,8 +6827,8 @@ function resolveClaudeBin(opts = {}) {
|
|
|
6569
6827
|
}
|
|
6570
6828
|
|
|
6571
6829
|
// src/droneHome.ts
|
|
6572
|
-
import { lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, rmSync, statSync as
|
|
6573
|
-
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";
|
|
6574
6832
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
6575
6833
|
function droneHomeDeny(env = process.env) {
|
|
6576
6834
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -6594,23 +6852,23 @@ function buildDroneHome(opts) {
|
|
|
6594
6852
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
6595
6853
|
for (const existing of readdirSync4(droneHome)) {
|
|
6596
6854
|
if (wanted.has(existing)) continue;
|
|
6597
|
-
const p =
|
|
6855
|
+
const p = join8(droneHome, existing);
|
|
6598
6856
|
try {
|
|
6599
6857
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
6600
6858
|
} catch {
|
|
6601
6859
|
}
|
|
6602
6860
|
}
|
|
6603
6861
|
for (const name of wanted) {
|
|
6604
|
-
const link =
|
|
6862
|
+
const link = join8(droneHome, name);
|
|
6605
6863
|
try {
|
|
6606
6864
|
lstatSync(link);
|
|
6607
6865
|
continue;
|
|
6608
6866
|
} catch {
|
|
6609
6867
|
}
|
|
6610
|
-
const target =
|
|
6868
|
+
const target = join8(realHome, name);
|
|
6611
6869
|
let isDir;
|
|
6612
6870
|
try {
|
|
6613
|
-
isDir =
|
|
6871
|
+
isDir = statSync4(target).isDirectory();
|
|
6614
6872
|
} catch {
|
|
6615
6873
|
continue;
|
|
6616
6874
|
}
|
|
@@ -6628,7 +6886,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
6628
6886
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
6629
6887
|
];
|
|
6630
6888
|
function realPathEnvPins(realHome) {
|
|
6631
|
-
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)]));
|
|
6632
6890
|
}
|
|
6633
6891
|
function droneDenyRules(realHome, swarmHome) {
|
|
6634
6892
|
const paths = [
|
|
@@ -6720,7 +6978,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
6720
6978
|
const env = { ...base3 };
|
|
6721
6979
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
6722
6980
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
6723
|
-
env.TURBO_CACHE_DIR =
|
|
6981
|
+
env.TURBO_CACHE_DIR = join9(worktree, ".turbo", "cache");
|
|
6724
6982
|
}
|
|
6725
6983
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
6726
6984
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -6748,25 +7006,25 @@ var DAEMON_STATE_ENV = [
|
|
|
6748
7006
|
"TASKLESS_TOKEN"
|
|
6749
7007
|
];
|
|
6750
7008
|
function bundledRoleDir(kind) {
|
|
6751
|
-
return
|
|
7009
|
+
return join9(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
6752
7010
|
}
|
|
6753
7011
|
function roleDir(worktree, kind) {
|
|
6754
|
-
return
|
|
7012
|
+
return join9(worktree, ".claude", "roles", kind);
|
|
6755
7013
|
}
|
|
6756
7014
|
function roleDirs(worktree, kind) {
|
|
6757
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
7015
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
|
|
6758
7016
|
}
|
|
6759
7017
|
function transcriptDir(worktree, projectsDir) {
|
|
6760
|
-
return
|
|
7018
|
+
return join9(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
6761
7019
|
}
|
|
6762
|
-
function sessionTranscriptExists(worktree, projectsDir =
|
|
7020
|
+
function sessionTranscriptExists(worktree, projectsDir = join9(homedir3(), ".claude", "projects")) {
|
|
6763
7021
|
try {
|
|
6764
7022
|
return readdirSync5(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
|
|
6765
7023
|
} catch {
|
|
6766
7024
|
return false;
|
|
6767
7025
|
}
|
|
6768
7026
|
}
|
|
6769
|
-
function transcriptMtimeMs(worktree, projectsDir =
|
|
7027
|
+
function transcriptMtimeMs(worktree, projectsDir = join9(homedir3(), ".claude", "projects")) {
|
|
6770
7028
|
const dir = transcriptDir(worktree, projectsDir);
|
|
6771
7029
|
let files;
|
|
6772
7030
|
try {
|
|
@@ -6778,7 +7036,7 @@ function transcriptMtimeMs(worktree, projectsDir = join8(homedir2(), ".claude",
|
|
|
6778
7036
|
for (const f of files) {
|
|
6779
7037
|
if (!f.endsWith(".jsonl")) continue;
|
|
6780
7038
|
try {
|
|
6781
|
-
const mtime2 =
|
|
7039
|
+
const mtime2 = statSync5(join9(dir, f)).mtimeMs;
|
|
6782
7040
|
if (latest === null || mtime2 > latest) latest = mtime2;
|
|
6783
7041
|
} catch {
|
|
6784
7042
|
}
|
|
@@ -6786,7 +7044,7 @@ function transcriptMtimeMs(worktree, projectsDir = join8(homedir2(), ".claude",
|
|
|
6786
7044
|
return latest;
|
|
6787
7045
|
}
|
|
6788
7046
|
function nextRunLogPath(runLogsDir, runId) {
|
|
6789
|
-
const dir =
|
|
7047
|
+
const dir = join9(runLogsDir, String(runId));
|
|
6790
7048
|
mkdirSync4(dir, { recursive: true });
|
|
6791
7049
|
let files = [];
|
|
6792
7050
|
try {
|
|
@@ -6798,7 +7056,7 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
6798
7056
|
const m = /^(\d+)\.json/.exec(f);
|
|
6799
7057
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
6800
7058
|
}
|
|
6801
|
-
return
|
|
7059
|
+
return join9(dir, `${next}.json`);
|
|
6802
7060
|
}
|
|
6803
7061
|
function openNextRunLog(runLogsDir, runId) {
|
|
6804
7062
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -6872,16 +7130,16 @@ function makeLauncher(cfg) {
|
|
|
6872
7130
|
}
|
|
6873
7131
|
}
|
|
6874
7132
|
};
|
|
6875
|
-
const cfgPath =
|
|
6876
|
-
|
|
7133
|
+
const cfgPath = join9(worktree, ".swarm-mcp.json");
|
|
7134
|
+
writeFileSync8(cfgPath, JSON.stringify(mcpConfig, null, 2));
|
|
6877
7135
|
const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
|
|
6878
7136
|
const pluginDirs = roleDirs(worktree, run.kind);
|
|
6879
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
7137
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir3();
|
|
6880
7138
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
6881
7139
|
const settings = cfg.droneIsolation ? droneSettingsJson(realHome, cfg.droneIsolation.swarmHome) : void 0;
|
|
6882
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
7140
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join9(cfg.droneIsolation.swarmHome, "token"))) {
|
|
6883
7141
|
console.error(
|
|
6884
|
-
`[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.`
|
|
6885
7143
|
);
|
|
6886
7144
|
}
|
|
6887
7145
|
let stdio = "ignore";
|
|
@@ -6915,8 +7173,8 @@ function makeLauncher(cfg) {
|
|
|
6915
7173
|
init_procCleanup();
|
|
6916
7174
|
|
|
6917
7175
|
// src/cost.ts
|
|
6918
|
-
import { readFileSync as
|
|
6919
|
-
import { join as
|
|
7176
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync6, renameSync as renameSync7 } from "fs";
|
|
7177
|
+
import { join as join10 } from "path";
|
|
6920
7178
|
|
|
6921
7179
|
// src/pricing.ts
|
|
6922
7180
|
var MODEL_PRICING = {
|
|
@@ -6973,17 +7231,17 @@ function unprocessedLogFiles(dir) {
|
|
|
6973
7231
|
}
|
|
6974
7232
|
}
|
|
6975
7233
|
function accountRunCost(deps, run) {
|
|
6976
|
-
const dir =
|
|
7234
|
+
const dir = join10(deps.runLogsDir, String(run.id));
|
|
6977
7235
|
const files = unprocessedLogFiles(dir);
|
|
6978
7236
|
if (files.length === 0) return;
|
|
6979
7237
|
const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
|
|
6980
7238
|
let added = 0;
|
|
6981
7239
|
const processed = [];
|
|
6982
7240
|
for (const f of files) {
|
|
6983
|
-
const full =
|
|
7241
|
+
const full = join10(dir, f);
|
|
6984
7242
|
let raw = null;
|
|
6985
7243
|
try {
|
|
6986
|
-
raw =
|
|
7244
|
+
raw = readFileSync10(full, "utf8");
|
|
6987
7245
|
} catch {
|
|
6988
7246
|
continue;
|
|
6989
7247
|
}
|
|
@@ -7000,7 +7258,7 @@ function accountRunCost(deps, run) {
|
|
|
7000
7258
|
if (added > 0) deps.db.updateRun(run.id, { cost_usd: run.cost_usd + added });
|
|
7001
7259
|
for (const full of processed) {
|
|
7002
7260
|
try {
|
|
7003
|
-
|
|
7261
|
+
renameSync7(full, `${full}.done`);
|
|
7004
7262
|
} catch {
|
|
7005
7263
|
}
|
|
7006
7264
|
}
|
|
@@ -7010,11 +7268,11 @@ function makeCostAccountant(deps) {
|
|
|
7010
7268
|
}
|
|
7011
7269
|
|
|
7012
7270
|
// src/runOutcome.ts
|
|
7013
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
7014
|
-
import { join as
|
|
7271
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync11 } from "fs";
|
|
7272
|
+
import { join as join11 } from "path";
|
|
7015
7273
|
var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
|
|
7016
7274
|
function classifyExit(runLogsDir, runId) {
|
|
7017
|
-
const dir =
|
|
7275
|
+
const dir = join11(runLogsDir, String(runId));
|
|
7018
7276
|
let files;
|
|
7019
7277
|
try {
|
|
7020
7278
|
files = readdirSync7(dir);
|
|
@@ -7031,7 +7289,7 @@ function classifyExit(runLogsDir, runId) {
|
|
|
7031
7289
|
if (!latest) return "unknown";
|
|
7032
7290
|
let raw;
|
|
7033
7291
|
try {
|
|
7034
|
-
raw =
|
|
7292
|
+
raw = readFileSync11(join11(dir, latest.name), "utf8");
|
|
7035
7293
|
} catch {
|
|
7036
7294
|
return "unknown";
|
|
7037
7295
|
}
|
|
@@ -7042,11 +7300,12 @@ function makeExitClassifier(runLogsDir) {
|
|
|
7042
7300
|
}
|
|
7043
7301
|
|
|
7044
7302
|
// src/orchestrator.ts
|
|
7303
|
+
init_src();
|
|
7045
7304
|
init_git();
|
|
7046
7305
|
|
|
7047
7306
|
// src/deps.ts
|
|
7048
|
-
import { existsSync as
|
|
7049
|
-
import { join as
|
|
7307
|
+
import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync12 } from "fs";
|
|
7308
|
+
import { join as join13 } from "path";
|
|
7050
7309
|
import { execa } from "execa";
|
|
7051
7310
|
var PREPARE_TIMEOUT_MS = 15 * 6e4;
|
|
7052
7311
|
var MAX_PLAN_COMMANDS = 12;
|
|
@@ -7062,7 +7321,7 @@ async function runCommand(cmd, worktree) {
|
|
|
7062
7321
|
}
|
|
7063
7322
|
function safeReadText(p) {
|
|
7064
7323
|
try {
|
|
7065
|
-
return
|
|
7324
|
+
return readFileSync12(p, "utf8");
|
|
7066
7325
|
} catch {
|
|
7067
7326
|
return null;
|
|
7068
7327
|
}
|
|
@@ -7102,21 +7361,21 @@ function overridePlan(override) {
|
|
|
7102
7361
|
};
|
|
7103
7362
|
}
|
|
7104
7363
|
function detectPackageManager(worktree, io = {}) {
|
|
7105
|
-
const exists = io.exists ??
|
|
7364
|
+
const exists = io.exists ?? existsSync9;
|
|
7106
7365
|
const readText = io.readText ?? safeReadText;
|
|
7107
|
-
const hasPkgJson = exists(
|
|
7366
|
+
const hasPkgJson = exists(join13(worktree, "package.json"));
|
|
7108
7367
|
if (!hasPkgJson) {
|
|
7109
|
-
if (exists(
|
|
7368
|
+
if (exists(join13(worktree, "deno.json")) || exists(join13(worktree, "deno.jsonc")) || exists(join13(worktree, "deno.lock"))) return "deno";
|
|
7110
7369
|
return null;
|
|
7111
7370
|
}
|
|
7112
|
-
const pkg = readJson(readText,
|
|
7371
|
+
const pkg = readJson(readText, join13(worktree, "package.json"));
|
|
7113
7372
|
const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
|
|
7114
7373
|
if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
|
|
7115
|
-
if (exists(
|
|
7116
|
-
if (exists(
|
|
7117
|
-
if (exists(
|
|
7118
|
-
if (exists(
|
|
7119
|
-
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";
|
|
7120
7379
|
return "npm";
|
|
7121
7380
|
}
|
|
7122
7381
|
function jsInstallCommand(pm) {
|
|
@@ -7169,7 +7428,7 @@ var ECOSYSTEM_MARKERS = [
|
|
|
7169
7428
|
// конфигурация — дело сборки, не подготовки
|
|
7170
7429
|
];
|
|
7171
7430
|
function detectPlan(worktree, io) {
|
|
7172
|
-
const exists = io.exists ??
|
|
7431
|
+
const exists = io.exists ?? existsSync9;
|
|
7173
7432
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
7174
7433
|
const pm = detectPackageManager(worktree, io);
|
|
7175
7434
|
if (pm) {
|
|
@@ -7177,7 +7436,7 @@ function detectPlan(worktree, io) {
|
|
|
7177
7436
|
// node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
|
|
7178
7437
|
// У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
|
|
7179
7438
|
// заново, но он дешёвый: тянет из того же общего кэша.
|
|
7180
|
-
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists:
|
|
7439
|
+
{ label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join13(worktree, "node_modules"), source: "detect" }
|
|
7181
7440
|
];
|
|
7182
7441
|
for (const target of workspaceBuildTargets(worktree, io)) {
|
|
7183
7442
|
commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
|
|
@@ -7185,12 +7444,12 @@ function detectPlan(worktree, io) {
|
|
|
7185
7444
|
return { source: "detect", ecosystem: `node/${pm}`, commands };
|
|
7186
7445
|
}
|
|
7187
7446
|
for (const marker of ECOSYSTEM_MARKERS) {
|
|
7188
|
-
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));
|
|
7189
7448
|
if (!hit) continue;
|
|
7190
7449
|
let ecosystem = marker.ecosystem;
|
|
7191
7450
|
let command = marker.command;
|
|
7192
7451
|
for (const refine of marker.refine ?? []) {
|
|
7193
|
-
if (!exists(
|
|
7452
|
+
if (!exists(join13(worktree, refine.file))) continue;
|
|
7194
7453
|
ecosystem = refine.ecosystem;
|
|
7195
7454
|
command = refine.command;
|
|
7196
7455
|
break;
|
|
@@ -7247,12 +7506,12 @@ var CI_INSTALL_PATTERNS = [
|
|
|
7247
7506
|
function ciWorkflowPlan(worktree, io) {
|
|
7248
7507
|
const readText = io.readText ?? safeReadText;
|
|
7249
7508
|
const listFiles = io.listFiles ?? safeListFiles;
|
|
7250
|
-
const dir =
|
|
7509
|
+
const dir = join13(worktree, ".github", "workflows");
|
|
7251
7510
|
const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
|
|
7252
7511
|
const seen = /* @__PURE__ */ new Set();
|
|
7253
7512
|
const commands = [];
|
|
7254
7513
|
for (const name of files.sort()) {
|
|
7255
|
-
const yaml = readText(
|
|
7514
|
+
const yaml = readText(join13(dir, name));
|
|
7256
7515
|
if (yaml === null) continue;
|
|
7257
7516
|
for (const raw of workflowRunLines(yaml)) {
|
|
7258
7517
|
const line = raw.replace(/\s+/g, " ").trim();
|
|
@@ -7267,7 +7526,7 @@ function ciWorkflowPlan(worktree, io) {
|
|
|
7267
7526
|
return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
|
|
7268
7527
|
}
|
|
7269
7528
|
function workspaceGlobs(worktree, readText) {
|
|
7270
|
-
const yaml = readText(
|
|
7529
|
+
const yaml = readText(join13(worktree, "pnpm-workspace.yaml"));
|
|
7271
7530
|
if (yaml !== null) {
|
|
7272
7531
|
const globs = [];
|
|
7273
7532
|
let inPackages = false;
|
|
@@ -7287,7 +7546,7 @@ function workspaceGlobs(worktree, readText) {
|
|
|
7287
7546
|
}
|
|
7288
7547
|
if (globs.length) return globs;
|
|
7289
7548
|
}
|
|
7290
|
-
const pkg = readJson(readText,
|
|
7549
|
+
const pkg = readJson(readText, join13(worktree, "package.json"));
|
|
7291
7550
|
const ws = pkg?.workspaces;
|
|
7292
7551
|
const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
7293
7552
|
return list.filter((g) => typeof g === "string");
|
|
@@ -7298,14 +7557,14 @@ function workspaceDirs(worktree, globs, io) {
|
|
|
7298
7557
|
if (glob.startsWith("!")) continue;
|
|
7299
7558
|
const star = glob.indexOf("*");
|
|
7300
7559
|
if (star === -1) {
|
|
7301
|
-
const dir =
|
|
7302
|
-
if (io.exists(
|
|
7560
|
+
const dir = join13(worktree, glob);
|
|
7561
|
+
if (io.exists(join13(dir, "package.json"))) dirs.add(dir);
|
|
7303
7562
|
continue;
|
|
7304
7563
|
}
|
|
7305
|
-
const base3 =
|
|
7564
|
+
const base3 = join13(worktree, glob.slice(0, star).replace(/\/$/, ""));
|
|
7306
7565
|
for (const name of io.listDirs(base3)) {
|
|
7307
|
-
const dir =
|
|
7308
|
-
if (io.exists(
|
|
7566
|
+
const dir = join13(base3, name);
|
|
7567
|
+
if (io.exists(join13(dir, "package.json"))) dirs.add(dir);
|
|
7309
7568
|
}
|
|
7310
7569
|
}
|
|
7311
7570
|
return [...dirs];
|
|
@@ -7326,18 +7585,18 @@ function entryPaths(pkg) {
|
|
|
7326
7585
|
return out;
|
|
7327
7586
|
}
|
|
7328
7587
|
function workspaceBuildTargets(worktree, io = {}) {
|
|
7329
|
-
const exists = io.exists ??
|
|
7588
|
+
const exists = io.exists ?? existsSync9;
|
|
7330
7589
|
const readText = io.readText ?? safeReadText;
|
|
7331
7590
|
const listDirs = io.listDirs ?? safeListDirs;
|
|
7332
7591
|
const globs = workspaceGlobs(worktree, readText);
|
|
7333
7592
|
if (!globs.length) return [];
|
|
7334
7593
|
const pkgs = [];
|
|
7335
7594
|
for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
|
|
7336
|
-
const pkg = readJson(readText,
|
|
7595
|
+
const pkg = readJson(readText, join13(dir, "package.json"));
|
|
7337
7596
|
if (!pkg || typeof pkg.name !== "string") continue;
|
|
7338
7597
|
const scripts = pkg.scripts ?? {};
|
|
7339
7598
|
const entries = entryPaths(pkg);
|
|
7340
|
-
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)));
|
|
7341
7600
|
const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
|
|
7342
7601
|
pkgs.push({ name: pkg.name, dir, deps, needsBuild });
|
|
7343
7602
|
}
|
|
@@ -7365,7 +7624,7 @@ function planPrepare(worktree, io = {}) {
|
|
|
7365
7624
|
return { ...plan, commands: plan.commands.slice(0, MAX_PLAN_COMMANDS) };
|
|
7366
7625
|
}
|
|
7367
7626
|
async function ensureWorktreeDeps(worktree, io = {}) {
|
|
7368
|
-
const exists = io.exists ??
|
|
7627
|
+
const exists = io.exists ?? existsSync9;
|
|
7369
7628
|
const exec = io.exec ?? ((cmd) => runCommand(cmd, worktree));
|
|
7370
7629
|
const approve = io.approve ?? (() => true);
|
|
7371
7630
|
const plan = planPrepare(worktree, io);
|
|
@@ -7464,6 +7723,12 @@ var AreaBusyError = class extends Error {
|
|
|
7464
7723
|
}
|
|
7465
7724
|
area;
|
|
7466
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
|
+
}
|
|
7467
7732
|
async function spawnDrone(deps, run) {
|
|
7468
7733
|
const repo = normalizeRepo(run.repo || deps.repo || "");
|
|
7469
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`);
|
|
@@ -8910,10 +9175,31 @@ function makeExclusiveReader(opts) {
|
|
|
8910
9175
|
}
|
|
8911
9176
|
|
|
8912
9177
|
// src/envGate.ts
|
|
8913
|
-
import { existsSync as
|
|
8914
|
-
import { join as
|
|
9178
|
+
import { existsSync as existsSync12 } from "fs";
|
|
9179
|
+
import { join as join20 } from "path";
|
|
8915
9180
|
import { createConnection } from "net";
|
|
8916
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
|
|
8917
9203
|
var PROBE_TIMEOUT_MS = 2500;
|
|
8918
9204
|
var PORT_TIMEOUT_MS = 1e3;
|
|
8919
9205
|
function parseRequirements(lines) {
|
|
@@ -8962,8 +9248,8 @@ var COMPOSE_MARKERS = [
|
|
|
8962
9248
|
"compose.yaml"
|
|
8963
9249
|
];
|
|
8964
9250
|
function detectRequirements(repo, io = {}) {
|
|
8965
|
-
const exists = io.exists ??
|
|
8966
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
9251
|
+
const exists = io.exists ?? existsSync12;
|
|
9252
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join20(repo, f)));
|
|
8967
9253
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
8968
9254
|
}
|
|
8969
9255
|
function resolveRequirements(repo, declared, io = {}) {
|
|
@@ -8982,17 +9268,14 @@ async function dockerAlive() {
|
|
|
8982
9268
|
}
|
|
8983
9269
|
}
|
|
8984
9270
|
async function hasCommand(name, env) {
|
|
8985
|
-
|
|
8986
|
-
const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
8987
|
-
const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
8988
|
-
return dirs.some((dir) => exts.some((ext) => existsSync10(join18(dir, name + ext))));
|
|
9271
|
+
return hasCommandInPath(name, env);
|
|
8989
9272
|
}
|
|
8990
9273
|
async function portOpen(host, port) {
|
|
8991
|
-
return new Promise((
|
|
9274
|
+
return new Promise((resolve4) => {
|
|
8992
9275
|
const socket = createConnection({ host, port });
|
|
8993
9276
|
const finish = (ok) => {
|
|
8994
9277
|
socket.destroy();
|
|
8995
|
-
|
|
9278
|
+
resolve4(ok);
|
|
8996
9279
|
};
|
|
8997
9280
|
socket.setTimeout(PORT_TIMEOUT_MS);
|
|
8998
9281
|
socket.once("connect", () => finish(true));
|
|
@@ -9102,8 +9385,8 @@ import { basename as basename3 } from "path";
|
|
|
9102
9385
|
import { serve as serve2 } from "@hono/node-server";
|
|
9103
9386
|
|
|
9104
9387
|
// src/diff.ts
|
|
9105
|
-
import { statSync as
|
|
9106
|
-
import { join as
|
|
9388
|
+
import { statSync as statSync7 } from "fs";
|
|
9389
|
+
import { join as join21 } from "path";
|
|
9107
9390
|
import { $ as $4 } from "execa";
|
|
9108
9391
|
var MAX_FILES = 200;
|
|
9109
9392
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -9246,7 +9529,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
9246
9529
|
}
|
|
9247
9530
|
function mtime(path) {
|
|
9248
9531
|
try {
|
|
9249
|
-
return
|
|
9532
|
+
return statSync7(path).mtimeMs;
|
|
9250
9533
|
} catch {
|
|
9251
9534
|
return 0;
|
|
9252
9535
|
}
|
|
@@ -9271,7 +9554,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9271
9554
|
files.push(pf);
|
|
9272
9555
|
}
|
|
9273
9556
|
}
|
|
9274
|
-
files.sort((a, b) => mtime(
|
|
9557
|
+
files.sort((a, b) => mtime(join21(worktree, b.path)) - mtime(join21(worktree, a.path)));
|
|
9275
9558
|
const truncatedFiles = files.length > MAX_FILES;
|
|
9276
9559
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
9277
9560
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -9290,7 +9573,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9290
9573
|
// package.json
|
|
9291
9574
|
var package_default = {
|
|
9292
9575
|
name: "@taskless-app/swarm",
|
|
9293
|
-
version: "0.
|
|
9576
|
+
version: "0.5.0",
|
|
9294
9577
|
type: "module",
|
|
9295
9578
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
9296
9579
|
keywords: [
|
|
@@ -9342,13 +9625,16 @@ var package_default = {
|
|
|
9342
9625
|
}
|
|
9343
9626
|
};
|
|
9344
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
|
+
|
|
9345
9632
|
// src/web.ts
|
|
9346
9633
|
init_storm();
|
|
9347
9634
|
init_resources();
|
|
9348
9635
|
init_config();
|
|
9349
9636
|
init_droneChannel();
|
|
9350
9637
|
init_oauth();
|
|
9351
|
-
var PACKAGE_VERSION = package_default.version;
|
|
9352
9638
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
9353
9639
|
var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
|
|
9354
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>`;
|
|
@@ -10656,7 +10942,8 @@ function daemonStatus(deps) {
|
|
|
10656
10942
|
restart: deps.restart ? { phase: deps.restart.phase, intent: deps.restart.intent, sessions: live } : null,
|
|
10657
10943
|
counts,
|
|
10658
10944
|
live,
|
|
10659
|
-
version: PACKAGE_VERSION
|
|
10945
|
+
version: PACKAGE_VERSION,
|
|
10946
|
+
repos: deps.repoMap?.() ?? []
|
|
10660
10947
|
};
|
|
10661
10948
|
}
|
|
10662
10949
|
function eventsStream(db, narratorLog, control, daemonVersion, restart, liveSessionIds2, selfHost) {
|
|
@@ -10904,8 +11191,8 @@ function startWeb(deps, port = WEB_PORT) {
|
|
|
10904
11191
|
return { port, server };
|
|
10905
11192
|
}
|
|
10906
11193
|
function closeWebServer(server) {
|
|
10907
|
-
return new Promise((
|
|
10908
|
-
server.close(() =>
|
|
11194
|
+
return new Promise((resolve4) => {
|
|
11195
|
+
server.close(() => resolve4());
|
|
10909
11196
|
server.closeAllConnections?.();
|
|
10910
11197
|
});
|
|
10911
11198
|
}
|
|
@@ -10949,24 +11236,56 @@ async function checkDaemonVersion(start, now = () => /* @__PURE__ */ new Date())
|
|
|
10949
11236
|
}
|
|
10950
11237
|
}
|
|
10951
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
|
+
|
|
10952
11271
|
// src/hostCommands.ts
|
|
10953
11272
|
init_src();
|
|
10954
|
-
import { readFileSync as
|
|
10955
|
-
import { join as
|
|
11273
|
+
import { readFileSync as readFileSync18, renameSync as renameSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
11274
|
+
import { join as join22 } from "path";
|
|
10956
11275
|
function hostCommandStatePath(home) {
|
|
10957
|
-
return
|
|
11276
|
+
return join22(home, "host-command.json");
|
|
10958
11277
|
}
|
|
10959
11278
|
function readState(path) {
|
|
10960
11279
|
try {
|
|
10961
|
-
return JSON.parse(
|
|
11280
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
10962
11281
|
} catch {
|
|
10963
11282
|
return {};
|
|
10964
11283
|
}
|
|
10965
11284
|
}
|
|
10966
11285
|
function writeState(path, state) {
|
|
10967
11286
|
try {
|
|
10968
|
-
|
|
10969
|
-
|
|
11287
|
+
writeFileSync13(`${path}.tmp`, JSON.stringify(state));
|
|
11288
|
+
renameSync8(`${path}.tmp`, path);
|
|
10970
11289
|
} catch {
|
|
10971
11290
|
}
|
|
10972
11291
|
}
|
|
@@ -11101,8 +11420,8 @@ async function doRestart(cmd, opts, note) {
|
|
|
11101
11420
|
|
|
11102
11421
|
// src/selfUpdate.ts
|
|
11103
11422
|
init_git();
|
|
11104
|
-
import { existsSync as
|
|
11105
|
-
import { join as
|
|
11423
|
+
import { existsSync as existsSync13 } from "fs";
|
|
11424
|
+
import { join as join23 } from "path";
|
|
11106
11425
|
import { $ as $6 } from "execa";
|
|
11107
11426
|
var DAEMON_PACKAGE = "@taskless-app/swarm";
|
|
11108
11427
|
var defaultRun = async (cmd, args, cwd) => {
|
|
@@ -11115,7 +11434,7 @@ function tail(text, max = 400) {
|
|
|
11115
11434
|
}
|
|
11116
11435
|
async function runSelfUpdate(io) {
|
|
11117
11436
|
const run = io.run ?? defaultRun;
|
|
11118
|
-
const exists = io.exists ??
|
|
11437
|
+
const exists = io.exists ?? existsSync13;
|
|
11119
11438
|
const repo = io.repo;
|
|
11120
11439
|
await io.onStep("fetch", "\u0442\u044F\u043D\u0443 \u0441\u0432\u0435\u0436\u0438\u0439 \u043A\u043E\u0434");
|
|
11121
11440
|
const fetched = await run("git", ["-C", repo, "fetch", "--quiet", "origin"], repo);
|
|
@@ -11144,7 +11463,7 @@ async function runSelfUpdate(io) {
|
|
|
11144
11463
|
};
|
|
11145
11464
|
}
|
|
11146
11465
|
await io.onStep("install", "\u0441\u0442\u0430\u0432\u043B\u044E \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438");
|
|
11147
|
-
if (!exists(
|
|
11466
|
+
if (!exists(join23(repo, "pnpm-lock.yaml"))) {
|
|
11148
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" };
|
|
11149
11468
|
}
|
|
11150
11469
|
const installed = await run("pnpm", ["install", "--frozen-lockfile"], repo);
|
|
@@ -11184,7 +11503,7 @@ var RESET_RE = /resets[^\n]*/i;
|
|
|
11184
11503
|
function anthropicHealthy(opts = {}) {
|
|
11185
11504
|
const bin = opts.claudeBin ?? resolveClaudeBin();
|
|
11186
11505
|
const timeoutMs = opts.timeoutMs ?? 2e4;
|
|
11187
|
-
return new Promise((
|
|
11506
|
+
return new Promise((resolve4) => {
|
|
11188
11507
|
let done = false;
|
|
11189
11508
|
let out = "";
|
|
11190
11509
|
const finish = (r) => {
|
|
@@ -11195,7 +11514,7 @@ function anthropicHealthy(opts = {}) {
|
|
|
11195
11514
|
child.kill("SIGKILL");
|
|
11196
11515
|
} catch {
|
|
11197
11516
|
}
|
|
11198
|
-
|
|
11517
|
+
resolve4(r);
|
|
11199
11518
|
};
|
|
11200
11519
|
const child = spawn2(bin, [...ISOLATION_FLAGS, "-p", "ok", "--max-turns", "1"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
11201
11520
|
child.stdout?.on("data", (d) => {
|
|
@@ -11234,16 +11553,16 @@ async function fetchExternalStorm(url, opts = {}) {
|
|
|
11234
11553
|
}
|
|
11235
11554
|
|
|
11236
11555
|
// src/anomalyState.ts
|
|
11237
|
-
import { existsSync as
|
|
11238
|
-
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";
|
|
11239
11558
|
var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
11240
11559
|
var EMPTY = { since: {}, lastFiredAt: {}, fires: {}, stopQuiet: null };
|
|
11241
11560
|
function anomalyStatePath(home) {
|
|
11242
|
-
return
|
|
11561
|
+
return join24(home, "anomaly-state.json");
|
|
11243
11562
|
}
|
|
11244
11563
|
function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
11245
|
-
const exists = io.exists ??
|
|
11246
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
11564
|
+
const exists = io.exists ?? existsSync14;
|
|
11565
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync19(p, "utf8"));
|
|
11247
11566
|
const path = anomalyStatePath(home);
|
|
11248
11567
|
if (!exists(path)) return { ...EMPTY };
|
|
11249
11568
|
try {
|
|
@@ -11269,8 +11588,8 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
|
|
|
11269
11588
|
function saveAnomalyState(home, state, io = {}) {
|
|
11270
11589
|
const path = anomalyStatePath(home);
|
|
11271
11590
|
const writeFile2 = io.writeFile ?? ((p, data) => {
|
|
11272
|
-
|
|
11273
|
-
|
|
11591
|
+
writeFileSync14(`${p}.tmp`, data);
|
|
11592
|
+
renameSync9(`${p}.tmp`, p);
|
|
11274
11593
|
});
|
|
11275
11594
|
writeFile2(path, JSON.stringify(state, null, 2));
|
|
11276
11595
|
}
|
|
@@ -11912,10 +12231,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
|
|
|
11912
12231
|
}
|
|
11913
12232
|
return parts.join("\n");
|
|
11914
12233
|
}
|
|
11915
|
-
var HERO_CHARTER_PATH =
|
|
12234
|
+
var HERO_CHARTER_PATH = join25(dirname5(THIS_FILE), "..", "docs", "HERO.md");
|
|
11916
12235
|
function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
|
|
11917
12236
|
const name = run.name || `#${run.id}`;
|
|
11918
|
-
const charter =
|
|
12237
|
+
const charter = readFileSync20(HERO_CHARTER_PATH, "utf8");
|
|
11919
12238
|
const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
|
|
11920
12239
|
|
|
11921
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).`;
|
|
@@ -12166,7 +12485,7 @@ async function wakeRun(deps, runId) {
|
|
|
12166
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` };
|
|
12167
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` };
|
|
12168
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` };
|
|
12169
|
-
const worktreeExists = deps.worktreeExists ??
|
|
12488
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12170
12489
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12171
12490
|
const wt = run.worktree_path;
|
|
12172
12491
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -12251,7 +12570,7 @@ function repoHasCi(deps, run) {
|
|
|
12251
12570
|
function repoHasCiWorkflows(worktreePath, repo) {
|
|
12252
12571
|
for (const base3 of [worktreePath, repo]) {
|
|
12253
12572
|
if (!base3) continue;
|
|
12254
|
-
const dir =
|
|
12573
|
+
const dir = join25(base3, ".github", "workflows");
|
|
12255
12574
|
try {
|
|
12256
12575
|
if (readdirSync10(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
|
|
12257
12576
|
} catch {
|
|
@@ -12400,7 +12719,7 @@ async function wakeReviewerOnCiReady(deps, run, over) {
|
|
|
12400
12719
|
return true;
|
|
12401
12720
|
}
|
|
12402
12721
|
function worktreeAndTranscriptAlive(deps, worktree) {
|
|
12403
|
-
const exists = deps.worktreeExists ??
|
|
12722
|
+
const exists = deps.worktreeExists ?? existsSync15;
|
|
12404
12723
|
const transcript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12405
12724
|
return exists(worktree) && transcript(worktree);
|
|
12406
12725
|
}
|
|
@@ -12543,6 +12862,15 @@ async function exclusiveGateAllows(deps, run) {
|
|
|
12543
12862
|
}
|
|
12544
12863
|
return true;
|
|
12545
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
|
+
}
|
|
12546
12874
|
async function exclusivityBlockedRunIds(deps) {
|
|
12547
12875
|
const blocked = /* @__PURE__ */ new Set();
|
|
12548
12876
|
if (!deps.exclusiveProject) return blocked;
|
|
@@ -12605,7 +12933,7 @@ async function resumeWatchdogParked(deps, droneCap) {
|
|
|
12605
12933
|
}
|
|
12606
12934
|
if (!candidate) return;
|
|
12607
12935
|
if (candidate.pid != null && deps.isAlive(candidate.pid)) return;
|
|
12608
|
-
const worktreeExists = deps.worktreeExists ??
|
|
12936
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12609
12937
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12610
12938
|
const wt = candidate.worktree_path;
|
|
12611
12939
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
@@ -12774,7 +13102,7 @@ async function manageRelief(deps, now, droneCap, stormActive) {
|
|
|
12774
13102
|
if (resumeCandidate.pid != null && deps.isAlive(resumeCandidate.pid)) return;
|
|
12775
13103
|
if (!deps.respawn) return;
|
|
12776
13104
|
const wt = resumeCandidate.worktree_path;
|
|
12777
|
-
const worktreeExists = deps.worktreeExists ??
|
|
13105
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12778
13106
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12779
13107
|
const canContinue = !!wt && worktreeExists(wt) && hasTranscript(wt);
|
|
12780
13108
|
const mode = canContinue ? "continue" : "fresh";
|
|
@@ -12936,7 +13264,7 @@ function buildEscalationCandidates(db) {
|
|
|
12936
13264
|
}
|
|
12937
13265
|
async function tickOnce(deps) {
|
|
12938
13266
|
const now = deps.now ? deps.now() : Date.now();
|
|
12939
|
-
const worktreeExists = deps.worktreeExists ??
|
|
13267
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
12940
13268
|
const hasTranscript = deps.hasTranscript ?? sessionTranscriptExists;
|
|
12941
13269
|
if (deps.rehydrate) {
|
|
12942
13270
|
const rh = deps.rehydrate;
|
|
@@ -12988,6 +13316,18 @@ async function tickOnce(deps) {
|
|
|
12988
13316
|
await dv.run();
|
|
12989
13317
|
}
|
|
12990
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
|
+
}
|
|
12991
13331
|
if (deps.rehydrate && deps.kill) {
|
|
12992
13332
|
const kill = deps.kill;
|
|
12993
13333
|
for (const run of deps.db.listRuns()) {
|
|
@@ -13013,6 +13353,19 @@ async function tickOnce(deps) {
|
|
|
13013
13353
|
}
|
|
13014
13354
|
}
|
|
13015
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);
|
|
13016
13369
|
let stormActive = false;
|
|
13017
13370
|
if (deps.storm) {
|
|
13018
13371
|
for (const run of deps.db.listRuns("running")) {
|
|
@@ -13542,7 +13895,7 @@ async function resolveConflict(deps, reviewRun) {
|
|
|
13542
13895
|
workspace_id: reviewRun.workspace_id
|
|
13543
13896
|
// та же задача, тот же воркспейс (TASWM-288)
|
|
13544
13897
|
});
|
|
13545
|
-
const wt =
|
|
13898
|
+
const wt = join25(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
|
|
13546
13899
|
try {
|
|
13547
13900
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
13548
13901
|
await deps.git.setWorktreeIdentity(wt, resolver.name);
|
|
@@ -13594,7 +13947,7 @@ async function spawnReviewer(deps, reviewRun, files) {
|
|
|
13594
13947
|
// (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
|
|
13595
13948
|
workspace_id: reviewRun.workspace_id
|
|
13596
13949
|
});
|
|
13597
|
-
const wt =
|
|
13950
|
+
const wt = join25(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
|
|
13598
13951
|
try {
|
|
13599
13952
|
await deps.git.prepareDetachedWorktree(repo, branch, wt);
|
|
13600
13953
|
await deps.git.setWorktreeIdentity(wt, reviewer.name);
|
|
@@ -13629,7 +13982,7 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
13629
13982
|
]);
|
|
13630
13983
|
const base3 = effectiveBaseBranch(run.base_branch, projectBase);
|
|
13631
13984
|
const runToSpawn = base3 && run.base_branch !== base3 ? deps.db.updateRun(run.id, { base_branch: base3 }) ?? run : run;
|
|
13632
|
-
if (!
|
|
13985
|
+
if (!existsSync15(wt)) {
|
|
13633
13986
|
const branch = runToSpawn.branch || runToSpawn.task_slug;
|
|
13634
13987
|
const source = await addWorktree(repo, wt, branch, {
|
|
13635
13988
|
...base3 ? { base: await remoteBaseRef(repo, base3) } : {},
|
|
@@ -13654,11 +14007,11 @@ async function respawnRun(deps, run, mode, nudge) {
|
|
|
13654
14007
|
const prompt = notice ? `${freshPrompt}
|
|
13655
14008
|
|
|
13656
14009
|
${notice}` : freshPrompt;
|
|
13657
|
-
const pid = deps.launch({ worktree: wt, run, prompt });
|
|
14010
|
+
const pid = deps.launch({ worktree: wt, run: runToSpawn, prompt });
|
|
13658
14011
|
deps.db.updateRun(run.id, { status: "running", pid });
|
|
13659
14012
|
}
|
|
13660
14013
|
async function syncCrashedOnStartup(deps) {
|
|
13661
|
-
const worktreeExists = deps.worktreeExists ??
|
|
14014
|
+
const worktreeExists = deps.worktreeExists ?? existsSync15;
|
|
13662
14015
|
let orphaned = 0;
|
|
13663
14016
|
let merged = 0;
|
|
13664
14017
|
let requeued = 0;
|
|
@@ -13721,39 +14074,37 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
13721
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`);
|
|
13722
14075
|
}
|
|
13723
14076
|
}
|
|
14077
|
+
const scanRootsList = resolveScanRoots(SWARM_SCAN_ROOTS_RAW, homedir4(), repoPaths);
|
|
14078
|
+
const repoMap = emptyRepoMapState(scanRootsList);
|
|
13724
14079
|
if (SWARM_BACKEND !== "client") {
|
|
13725
14080
|
const { SwarmDb: SwarmDb2 } = await loadSqlite();
|
|
13726
|
-
return { store: new SwarmDb2(DB_PATH, repo), reconciled: true, host };
|
|
14081
|
+
return { store: new SwarmDb2(DB_PATH, repo), reconciled: true, host, repoMap };
|
|
13727
14082
|
}
|
|
13728
14083
|
const call = resilientSwarmMcp({
|
|
13729
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.`),
|
|
13730
14085
|
onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
|
|
13731
14086
|
});
|
|
13732
14087
|
const explicitRegistry = await buildRepoRegistry(repoPaths);
|
|
13733
|
-
|
|
13734
|
-
const scanRootsList = defaultScanRoots(homedir3(), repoPaths);
|
|
13735
|
-
const scanStartedAt = Date.now();
|
|
13736
|
-
const scanned = scanRoots(scanRootsList, { maxDepth: SWARM_SCAN_DEPTH });
|
|
13737
|
-
let addedByScan = 0;
|
|
13738
|
-
for (const [full, path] of scanned) {
|
|
13739
|
-
if (byName.has(full)) continue;
|
|
13740
|
-
byName.set(full, path);
|
|
13741
|
-
addedByScan++;
|
|
13742
|
-
}
|
|
13743
|
-
if (addedByScan > 0) {
|
|
13744
|
-
console.log(
|
|
13745
|
-
`[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}` : ""}`
|
|
13746
|
-
);
|
|
13747
|
-
}
|
|
13748
|
-
const repoRegistry = [...byName].map(([repoFullName, path]) => ({ repoFullName, path }));
|
|
13749
|
-
if (repoPaths.length > 0 && repoRegistry.length < repoPaths.length) {
|
|
14088
|
+
if (repoPaths.length > 0 && explicitRegistry.length < repoPaths.length) {
|
|
13750
14089
|
console.warn(
|
|
13751
|
-
`[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)`
|
|
13752
14091
|
);
|
|
13753
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();
|
|
13754
14103
|
if (repoRegistry.length === 0) {
|
|
13755
14104
|
console.warn(
|
|
13756
|
-
`[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)."
|
|
13757
14108
|
);
|
|
13758
14109
|
}
|
|
13759
14110
|
const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry, host });
|
|
@@ -13768,7 +14119,17 @@ async function makeStore(repo, worktreesDir, repoFlag) {
|
|
|
13768
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`
|
|
13769
14120
|
);
|
|
13770
14121
|
}
|
|
13771
|
-
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
|
+
};
|
|
13772
14133
|
}
|
|
13773
14134
|
var crashGuardsInstalled = false;
|
|
13774
14135
|
function installCrashGuards() {
|
|
@@ -13787,7 +14148,7 @@ async function migrateWorktreesAtStartup() {
|
|
|
13787
14148
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
13788
14149
|
targetDir: WORKTREES_DIR,
|
|
13789
14150
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
13790
|
-
exists: (d) =>
|
|
14151
|
+
exists: (d) => existsSync15(d),
|
|
13791
14152
|
isNonEmptyDir: (d) => {
|
|
13792
14153
|
try {
|
|
13793
14154
|
return readdirSync10(d).length > 0;
|
|
@@ -13796,13 +14157,13 @@ async function migrateWorktreesAtStartup() {
|
|
|
13796
14157
|
}
|
|
13797
14158
|
},
|
|
13798
14159
|
liveDronesUnder: (d) => pidsWithCwdUnder(d).length > 0,
|
|
13799
|
-
rename: (from, to) =>
|
|
14160
|
+
rename: (from, to) => renameSync10(from, to),
|
|
13800
14161
|
repairMoved: (t) => repairMovedWorktrees(t),
|
|
13801
14162
|
log: (m) => console.log(m)
|
|
13802
14163
|
});
|
|
13803
14164
|
} catch (e) {
|
|
13804
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);
|
|
13805
|
-
return
|
|
14166
|
+
return existsSync15(WORKTREES_DIR) ? WORKTREES_DIR : WORKTREES_LEGACY_DIR;
|
|
13806
14167
|
}
|
|
13807
14168
|
}
|
|
13808
14169
|
async function reportWorkspaceAccess(tl, db) {
|
|
@@ -13820,13 +14181,22 @@ async function reportWorkspaceAccess(tl, db) {
|
|
|
13820
14181
|
async function runDaemon(opts) {
|
|
13821
14182
|
installCrashGuards();
|
|
13822
14183
|
const daemonStart = await captureDaemonStart(THIS_FILE);
|
|
14184
|
+
const latestRelease = fetchLatestVersion();
|
|
13823
14185
|
const worktreesDir = await migrateWorktreesAtStartup();
|
|
13824
14186
|
if (!opts.repo) {
|
|
13825
14187
|
const remembered = effectiveRepoPaths("", loadRememberedRepos(SWARM_HOME))[0];
|
|
13826
14188
|
if (remembered) opts.repo = remembered;
|
|
13827
14189
|
}
|
|
13828
|
-
const {
|
|
14190
|
+
const {
|
|
14191
|
+
store: db,
|
|
14192
|
+
reconciled,
|
|
14193
|
+
host,
|
|
14194
|
+
repoMap: repoMapState,
|
|
14195
|
+
refreshRepoMap: rebuildRepoMap
|
|
14196
|
+
} = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
|
|
13829
14197
|
await preflightAuth();
|
|
14198
|
+
const outdated = outdatedLine(PACKAGE_VERSION, await latestRelease);
|
|
14199
|
+
if (outdated) console.warn(`[overmind] ${outdated}`);
|
|
13830
14200
|
const tl = await connectTaskless();
|
|
13831
14201
|
const tlFor = (workspaceId) => tl ? tl.forWorkspace(workspaceId) : null;
|
|
13832
14202
|
if (tl) await reportWorkspaceAccess(tl, db);
|
|
@@ -13886,6 +14256,10 @@ async function runDaemon(opts) {
|
|
|
13886
14256
|
const client = db;
|
|
13887
14257
|
rehydrate = { run: () => client.hydrate(), ready: () => client.pendingCount() === 0, intervalMs: SWARM_REHYDRATE_MS };
|
|
13888
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
|
+
}
|
|
13889
14263
|
let daemonVersion;
|
|
13890
14264
|
if (daemonStart && SWARM_VERSION_CHECK_MS > 0) {
|
|
13891
14265
|
let cached = null;
|
|
@@ -14117,8 +14491,9 @@ async function runDaemon(opts) {
|
|
|
14117
14491
|
baseBranchReader ? baseBranchReader(repoOfRun) : Promise.resolve("")
|
|
14118
14492
|
]);
|
|
14119
14493
|
const task = taskClient ? requireTaskBody(rawTask, run.task_slug, run.workspace_id) : null;
|
|
14120
|
-
const
|
|
14121
|
-
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;
|
|
14122
14497
|
const taskContext = task && taskClient ? await fetchTaskContext(taskClient, task) : void 0;
|
|
14123
14498
|
await spawnDrone(
|
|
14124
14499
|
{
|
|
@@ -14136,7 +14511,7 @@ async function runDaemon(opts) {
|
|
|
14136
14511
|
runToSpawn
|
|
14137
14512
|
);
|
|
14138
14513
|
if (task) db.updateRun(run.id, { task_title: task.title, task_desc: task.description, task_url: task.url });
|
|
14139
|
-
if (taskClient) await taskClient.startTask(
|
|
14514
|
+
if (taskClient) await taskClient.startTask(runToSpawn.task_slug);
|
|
14140
14515
|
},
|
|
14141
14516
|
prStatus: (url) => prStatus(url).then((s) => ({ merged: s.merged, conflicting: s.conflicting, isDraft: s.isDraft, files: s.files, state: s.state })),
|
|
14142
14517
|
// Открытые PR ветки прогона (готча №14): gh pr list --head <slug> в репо прогона
|
|
@@ -14249,6 +14624,22 @@ async function runDaemon(opts) {
|
|
|
14249
14624
|
...exclusiveProject ? { exclusiveProject } : {},
|
|
14250
14625
|
repoFallback: opts.repo,
|
|
14251
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
|
+
},
|
|
14252
14643
|
// merge-main-in: review-PR конфликтует с main → влить main в ветку PR, не переделывая
|
|
14253
14644
|
// фичу. Реальные git-примитивы + launch; логика — в resolveConflict (тестируется).
|
|
14254
14645
|
resolveConflict: (reviewRun) => resolveConflict(
|
|
@@ -14288,7 +14679,7 @@ async function runDaemon(opts) {
|
|
|
14288
14679
|
mode,
|
|
14289
14680
|
nudge
|
|
14290
14681
|
),
|
|
14291
|
-
worktreeExists: (p) =>
|
|
14682
|
+
worktreeExists: (p) => existsSync15(p),
|
|
14292
14683
|
hasTranscript: (p) => sessionTranscriptExists(p),
|
|
14293
14684
|
// CI-осведомлённость review-PR (TASWM-102): реальный `gh pr checks`/`gh run view --log-failed`
|
|
14294
14685
|
// (gh.ts) — решение (будить ли дрона) и гейты живут в tickOnce/handleCiRed. failureSummary
|
|
@@ -14316,6 +14707,8 @@ async function runDaemon(opts) {
|
|
|
14316
14707
|
...heartbeat ? { heartbeat } : {},
|
|
14317
14708
|
// Сверка кода демона с main (TASWM-251): ключ есть только когда entryFile в git-репо.
|
|
14318
14709
|
...daemonVersion ? { daemonVersion } : {},
|
|
14710
|
+
// Карта репозиториев машины (TASWM-336): ключ есть только в client-режиме с непустым TTL.
|
|
14711
|
+
...repoMap ? { repoMap } : {},
|
|
14319
14712
|
// Встроенный надзор (client-режим, TASWM-194): ключ есть только когда сконфигурен.
|
|
14320
14713
|
...anomalies ? { anomalies } : {},
|
|
14321
14714
|
// Сторож прогресса роя (TASWM-270): в отличие от anomalies выше — заведён БЕЗУСЛОВНО
|
|
@@ -14529,6 +14922,10 @@ async function runDaemon(opts) {
|
|
|
14529
14922
|
// Устаревший код демона (TASWM-251): тот же кэш, что читает heartbeat.push выше —
|
|
14530
14923
|
// локальный дашборд :7420 видит ту же сверку, что уходит в прод, без отдельного git-похода.
|
|
14531
14924
|
daemonVersion: () => daemonVersion?.get() ?? null,
|
|
14925
|
+
// Карта репозиториев машины (TASWM-336): то же состояние, что мутирует шаг тика, — его
|
|
14926
|
+
// печатает `swarm status`. Без него человек снова гадал бы, какие репозитории машина
|
|
14927
|
+
// считает своими и в каком дереве работает при двух копиях одного клона.
|
|
14928
|
+
repoMap: () => repoMapRows(repoMapState),
|
|
14532
14929
|
// Оператор-пауза + «Оживить» (TASWM-214 Фаза 2): тот же shared-флаг, что читает тик;
|
|
14533
14930
|
// narrate — операторские действия видны в ленте роя; revive — сброс залипшего шторма.
|
|
14534
14931
|
control,
|