@taskless-app/swarm 0.6.14 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli.js +362 -172
- package/dist/overmind.js +793 -327
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -332,9 +332,22 @@ var init_timeline = __esm({
|
|
|
332
332
|
});
|
|
333
333
|
|
|
334
334
|
// ../swarm-core/src/reviewPhase.ts
|
|
335
|
+
var REVIEW_PHASE_TEXT, PHASE_BY_LENGTH;
|
|
335
336
|
var init_reviewPhase = __esm({
|
|
336
337
|
"../swarm-core/src/reviewPhase.ts"() {
|
|
337
338
|
"use strict";
|
|
339
|
+
REVIEW_PHASE_TEXT = {
|
|
340
|
+
checks: "\u0418\u0434\u0443\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438",
|
|
341
|
+
awaiting_reviewer: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0436\u0434\u0451\u0442 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430",
|
|
342
|
+
ci_fixing: "CI \u043A\u0440\u0430\u0441\u043D\u044B\u0439 \u2014 \u0434\u0440\u043E\u043D \u0447\u0438\u043D\u0438\u0442",
|
|
343
|
+
in_review: "\u041D\u0430 \u0440\u0435\u0432\u044C\u044E",
|
|
344
|
+
changes: "\u041D\u0443\u0436\u043D\u044B \u043F\u0440\u0430\u0432\u043A\u0438 \u2014 \u0434\u0440\u043E\u043D \u043F\u0440\u0430\u0432\u0438\u0442",
|
|
345
|
+
ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
|
|
346
|
+
reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
|
|
347
|
+
};
|
|
348
|
+
PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
|
|
349
|
+
(a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
|
|
350
|
+
);
|
|
338
351
|
}
|
|
339
352
|
});
|
|
340
353
|
|
|
@@ -534,7 +547,7 @@ var init_status = __esm({
|
|
|
534
547
|
// ../swarm-core/src/deps.ts
|
|
535
548
|
function judgeDep(runs) {
|
|
536
549
|
const real = runs.filter((r) => !r.service);
|
|
537
|
-
if (real.length === 0) return { ...
|
|
550
|
+
if (real.length === 0) return { ...UNKNOWN };
|
|
538
551
|
if (real.some((r) => !isTerminalStatus(r.status))) return { ...PENDING };
|
|
539
552
|
if (real.some((r) => r.status === "done")) {
|
|
540
553
|
return { outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
@@ -548,12 +561,13 @@ function judgeDep(runs) {
|
|
|
548
561
|
stopReason: last.stopReason ?? null
|
|
549
562
|
};
|
|
550
563
|
}
|
|
551
|
-
var PENDING;
|
|
564
|
+
var PENDING, UNKNOWN;
|
|
552
565
|
var init_deps = __esm({
|
|
553
566
|
"../swarm-core/src/deps.ts"() {
|
|
554
567
|
"use strict";
|
|
555
568
|
init_status();
|
|
556
569
|
PENDING = { outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
570
|
+
UNKNOWN = { outcome: "unknown", met: false, status: null, name: null, stopReason: null };
|
|
557
571
|
}
|
|
558
572
|
});
|
|
559
573
|
|
|
@@ -1939,7 +1953,10 @@ function normalizeRepo(repo) {
|
|
|
1939
1953
|
}
|
|
1940
1954
|
}
|
|
1941
1955
|
function droneWorktreePath(worktreesDir, repo, slug) {
|
|
1942
|
-
return join4(worktreesDir,
|
|
1956
|
+
return join4(droneWorktreeRoot(worktreesDir, repo), slug);
|
|
1957
|
+
}
|
|
1958
|
+
function droneWorktreeRoot(worktreesDir, repo) {
|
|
1959
|
+
return join4(worktreesDir, basename(normalizeRepo(repo)));
|
|
1943
1960
|
}
|
|
1944
1961
|
function resolveEffectiveWorktreesDir(deps) {
|
|
1945
1962
|
if (deps.envOverride) return deps.envOverride;
|
|
@@ -1965,7 +1982,7 @@ function tokenFromFile() {
|
|
|
1965
1982
|
return "";
|
|
1966
1983
|
}
|
|
1967
1984
|
}
|
|
1968
|
-
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
|
|
1985
|
+
var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
|
|
1969
1986
|
var init_config = __esm({
|
|
1970
1987
|
"src/config.ts"() {
|
|
1971
1988
|
"use strict";
|
|
@@ -2027,6 +2044,7 @@ var init_config = __esm({
|
|
|
2027
2044
|
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
2028
2045
|
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join4(SWARM_HOME, "repo-map.json");
|
|
2029
2046
|
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
2047
|
+
SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
|
|
2030
2048
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
2031
2049
|
SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
|
|
2032
2050
|
TASKLESS_MCP_PATH = process.env.TASKLESS_MCP_PATH || "/api/mcp";
|
|
@@ -2053,6 +2071,7 @@ var init_config = __esm({
|
|
|
2053
2071
|
SWARM_ANOMALY_QUESTION_UNANSWERED_MS = envNum("SWARM_ANOMALY_QUESTION_UNANSWERED_MS", 20 * 6e4);
|
|
2054
2072
|
SWARM_ANOMALY_OUTBOX_STUCK_MS = envNum("SWARM_ANOMALY_OUTBOX_STUCK_MS", 10 * 6e4);
|
|
2055
2073
|
SWARM_ANOMALY_RESOURCE_GATE_STALL_MS = envNum("SWARM_ANOMALY_RESOURCE_GATE_STALL_MS", 5 * 6e4);
|
|
2074
|
+
SWARM_ANOMALY_DEP_MISSING_MS = envNum("SWARM_ANOMALY_DEP_MISSING_MS", 2 * 6e4);
|
|
2056
2075
|
SWARM_WATCHDOG_STALL_MS = envNum("SWARM_WATCHDOG_STALL_MS", 15 * 6e4);
|
|
2057
2076
|
}
|
|
2058
2077
|
});
|
|
@@ -2167,7 +2186,7 @@ var init_package = __esm({
|
|
|
2167
2186
|
"package.json"() {
|
|
2168
2187
|
package_default = {
|
|
2169
2188
|
name: "@taskless-app/swarm",
|
|
2170
|
-
version: "0.6.
|
|
2189
|
+
version: "0.6.15",
|
|
2171
2190
|
type: "module",
|
|
2172
2191
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
2173
2192
|
keywords: [
|
|
@@ -2217,7 +2236,7 @@ var init_package = __esm({
|
|
|
2217
2236
|
execa: "^10.0.0",
|
|
2218
2237
|
zod: "3.25.76"
|
|
2219
2238
|
},
|
|
2220
|
-
gitHead: "
|
|
2239
|
+
gitHead: "5bc8e0c067de58fd72b0eb434e80ad29c7a00223"
|
|
2221
2240
|
};
|
|
2222
2241
|
}
|
|
2223
2242
|
});
|
|
@@ -2235,7 +2254,7 @@ var init_version = __esm({
|
|
|
2235
2254
|
|
|
2236
2255
|
// src/droneChannel.ts
|
|
2237
2256
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
2238
|
-
import { chmodSync, mkdirSync as
|
|
2257
|
+
import { chmodSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2239
2258
|
import { dirname as dirname2 } from "path";
|
|
2240
2259
|
import { serve } from "@hono/node-server";
|
|
2241
2260
|
function loadChannelSecret(path) {
|
|
@@ -2245,7 +2264,7 @@ function loadChannelSecret(path) {
|
|
|
2245
2264
|
} catch {
|
|
2246
2265
|
}
|
|
2247
2266
|
const secret = randomBytes(32).toString("hex");
|
|
2248
|
-
|
|
2267
|
+
mkdirSync6(dirname2(path), { recursive: true });
|
|
2249
2268
|
writeFileSync5(path, secret, { mode: 384 });
|
|
2250
2269
|
try {
|
|
2251
2270
|
chmodSync(path, 384);
|
|
@@ -2287,7 +2306,7 @@ var init_droneChannel = __esm({
|
|
|
2287
2306
|
|
|
2288
2307
|
// src/agentToken.ts
|
|
2289
2308
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
2290
|
-
import { join as
|
|
2309
|
+
import { join as join9 } from "path";
|
|
2291
2310
|
function loadAgentToken() {
|
|
2292
2311
|
try {
|
|
2293
2312
|
const s = JSON.parse(readFileSync7(STORE_PATH, "utf8"));
|
|
@@ -2368,7 +2387,7 @@ var init_agentToken = __esm({
|
|
|
2368
2387
|
"src/agentToken.ts"() {
|
|
2369
2388
|
"use strict";
|
|
2370
2389
|
init_config();
|
|
2371
|
-
STORE_PATH =
|
|
2390
|
+
STORE_PATH = join9(SWARM_HOME, "agent.json");
|
|
2372
2391
|
base = (api) => api.replace(/\/$/, "");
|
|
2373
2392
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2374
2393
|
}
|
|
@@ -2619,13 +2638,13 @@ var init_daemonLink = __esm({
|
|
|
2619
2638
|
});
|
|
2620
2639
|
|
|
2621
2640
|
// src/pauseState.ts
|
|
2622
|
-
import { existsSync as
|
|
2623
|
-
import { join as
|
|
2641
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
2642
|
+
import { join as join17 } from "path";
|
|
2624
2643
|
function pauseStatePath(home) {
|
|
2625
|
-
return
|
|
2644
|
+
return join17(home, "pause.json");
|
|
2626
2645
|
}
|
|
2627
2646
|
function loadPauseState(home, io = {}) {
|
|
2628
|
-
const exists = io.exists ??
|
|
2647
|
+
const exists = io.exists ?? existsSync10;
|
|
2629
2648
|
const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
|
|
2630
2649
|
const path = pauseStatePath(home);
|
|
2631
2650
|
if (!exists(path)) return ACTIVE;
|
|
@@ -2645,13 +2664,13 @@ function loadPauseState(home, io = {}) {
|
|
|
2645
2664
|
function savePauseState(home, state, io = {}) {
|
|
2646
2665
|
const path = pauseStatePath(home);
|
|
2647
2666
|
if (!state.paused) {
|
|
2648
|
-
const remove = io.remove ?? ((p) =>
|
|
2667
|
+
const remove = io.remove ?? ((p) => existsSync10(p) && unlinkSync5(p));
|
|
2649
2668
|
remove(path);
|
|
2650
2669
|
return;
|
|
2651
2670
|
}
|
|
2652
2671
|
const writeFile3 = io.writeFile ?? ((p, data) => {
|
|
2653
2672
|
writeFileSync9(`${p}.tmp`, data);
|
|
2654
|
-
|
|
2673
|
+
renameSync5(`${p}.tmp`, p);
|
|
2655
2674
|
});
|
|
2656
2675
|
writeFile3(path, JSON.stringify(state, null, 2));
|
|
2657
2676
|
}
|
|
@@ -2687,7 +2706,7 @@ var init_pauseState = __esm({
|
|
|
2687
2706
|
});
|
|
2688
2707
|
|
|
2689
2708
|
// src/heartbeat.ts
|
|
2690
|
-
import { readFileSync as readFileSync11, renameSync as
|
|
2709
|
+
import { readFileSync as readFileSync11, renameSync as renameSync6, writeFileSync as writeFileSync10 } from "fs";
|
|
2691
2710
|
function readHeartbeat(path) {
|
|
2692
2711
|
try {
|
|
2693
2712
|
const ms = Number(readFileSync11(path, "utf8").trim());
|
|
@@ -2708,7 +2727,8 @@ __export(db_exports, {
|
|
|
2708
2727
|
SwarmDb: () => SwarmDb
|
|
2709
2728
|
});
|
|
2710
2729
|
import { createRequire as createRequire2 } from "module";
|
|
2711
|
-
import {
|
|
2730
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2731
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2712
2732
|
import { resolve as resolve2 } from "path";
|
|
2713
2733
|
function rowToQuestion(row) {
|
|
2714
2734
|
return {
|
|
@@ -2870,6 +2890,12 @@ CREATE INDEX IF NOT EXISTS idx_runs_area ON runs(area);
|
|
|
2870
2890
|
CREATE INDEX IF NOT EXISTS idx_questions_run ON questions(run_id);
|
|
2871
2891
|
CREATE INDEX IF NOT EXISTS idx_questions_status ON questions(status);
|
|
2872
2892
|
CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id);
|
|
2893
|
+
-- \u043A\u043B\u044E\u0447-\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u043E \u0421\u0410\u041C \u0424\u0410\u0419\u041B \u0431\u0430\u0437\u044B (\u043D\u0435 \u043F\u0440\u043E \u043F\u0440\u043E\u0433\u043E\u043D\u044B). \u041F\u043E\u043A\u0430 \u0432 \u043D\u0451\u043C \u043E\u0434\u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C \u2014 instance_id
|
|
2894
|
+
-- (TASWM-419): \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u042D\u0422\u041E\u0419 \u0436\u0438\u0437\u043D\u0438 \u0431\u0430\u0437\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u043D\u0435\u0439\u043C\u0441\u043F\u0435\u0439\u0441\u044F\u0442\u0441\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438 run-log'\u043E\u0432.
|
|
2895
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
2896
|
+
key TEXT PRIMARY KEY,
|
|
2897
|
+
value TEXT NOT NULL
|
|
2898
|
+
);
|
|
2873
2899
|
-- \u043C\u0430\u0440\u043A\u0435\u0440 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u044B\u0445 \u0438\u043C\u043F\u043E\u0440\u0442\u043E\u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u0411\u0414 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C importDb)
|
|
2874
2900
|
CREATE TABLE IF NOT EXISTS imports (
|
|
2875
2901
|
source TEXT PRIMARY KEY,
|
|
@@ -2882,6 +2908,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2882
2908
|
defaultRepo;
|
|
2883
2909
|
databaseCtor;
|
|
2884
2910
|
// для importDb (второй Database, source)
|
|
2911
|
+
/** Идентификатор этой жизни файла базы (TASWM-419) — им неймспейсятся каталоги run-log'ов. */
|
|
2912
|
+
instanceId;
|
|
2885
2913
|
constructor(path = "swarm.db", defaultRepo = "") {
|
|
2886
2914
|
this.defaultRepo = normalizeRepo(defaultRepo);
|
|
2887
2915
|
this.databaseCtor = loadBetterSqlite3();
|
|
@@ -2949,6 +2977,22 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
2949
2977
|
if (this.defaultRepo) {
|
|
2950
2978
|
this.db.prepare("UPDATE runs SET repo = ? WHERE repo IS NULL").run(this.defaultRepo);
|
|
2951
2979
|
}
|
|
2980
|
+
this.instanceId = this.ensureInstanceId();
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Идентификатор ЭТОЙ жизни файла базы (TASWM-419). Заводится при первом открытии и живёт
|
|
2984
|
+
* в самой базе, поэтому переживает рестарт демона, но НЕ переживает пересоздание файла:
|
|
2985
|
+
* снесли базу — новый id, и каталоги run-log'ов нового прогона #1 не подселяются к файлам
|
|
2986
|
+
* прогона #1 из прошлой жизни. Именно это и было корнем «лог не того прогона»: номер там
|
|
2987
|
+
* был вся информация об адресе.
|
|
2988
|
+
*/
|
|
2989
|
+
ensureInstanceId() {
|
|
2990
|
+
const read = () => this.db.prepare("SELECT value FROM meta WHERE key = 'instance_id'").get()?.value;
|
|
2991
|
+
const existing = read();
|
|
2992
|
+
if (existing) return existing;
|
|
2993
|
+
const fresh = randomUUID3().replace(/-/g, "").slice(0, 12);
|
|
2994
|
+
this.db.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES ('instance_id', ?)").run(fresh);
|
|
2995
|
+
return read() ?? fresh;
|
|
2952
2996
|
}
|
|
2953
2997
|
close() {
|
|
2954
2998
|
this.db.close();
|
|
@@ -3043,6 +3087,15 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3043
3087
|
const m = /^local:(\d+)$/.exec(key);
|
|
3044
3088
|
return m ? this.getRun(Number(m[1])) : null;
|
|
3045
3089
|
}
|
|
3090
|
+
/**
|
|
3091
|
+
* TASWM-419: адрес каталога run-log'ов — номер, НЕЙМСПЕЙСНУТЫЙ идентификатором файла базы
|
|
3092
|
+
* (`local:<instance>:<id>`). Тот же номер в пересозданной базе даёт другой instance, то
|
|
3093
|
+
* есть другой каталог: подселиться к файлам чужого прогона с тем же номером больше нечем.
|
|
3094
|
+
* Псевдонимов у sqlite-прогона нет — ключ не меняется, пока жив файл базы.
|
|
3095
|
+
*/
|
|
3096
|
+
runLogKeys(id) {
|
|
3097
|
+
return this.getRun(id) ? [`local:${this.instanceId}:${id}`] : [];
|
|
3098
|
+
}
|
|
3046
3099
|
listRuns(status) {
|
|
3047
3100
|
const rows = status ? this.db.prepare("SELECT * FROM runs WHERE status = ? ORDER BY id DESC").all(status) : this.db.prepare("SELECT * FROM runs ORDER BY id DESC").all();
|
|
3048
3101
|
return rows.map(rowToRun);
|
|
@@ -3283,7 +3336,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3283
3336
|
const repoN = normalizeRepo(repo);
|
|
3284
3337
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
3285
3338
|
if (already) return { imported: false, runs: already.run_count };
|
|
3286
|
-
if (!
|
|
3339
|
+
if (!existsSync11(abs)) throw new Error(`import-db: \u0444\u0430\u0439\u043B\u0430 \u043D\u0435\u0442: ${abs}`);
|
|
3287
3340
|
const src = new this.databaseCtor(abs);
|
|
3288
3341
|
try {
|
|
3289
3342
|
const runs = src.prepare("SELECT * FROM runs ORDER BY id").all();
|
|
@@ -3610,8 +3663,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
3610
3663
|
});
|
|
3611
3664
|
|
|
3612
3665
|
// src/repoScan.ts
|
|
3613
|
-
import { readdirSync as
|
|
3614
|
-
import { join as
|
|
3666
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
3667
|
+
import { join as join18 } from "path";
|
|
3615
3668
|
var init_repoScan = __esm({
|
|
3616
3669
|
"src/repoScan.ts"() {
|
|
3617
3670
|
"use strict";
|
|
@@ -3621,8 +3674,8 @@ var init_repoScan = __esm({
|
|
|
3621
3674
|
|
|
3622
3675
|
// src/repoDiscovery.ts
|
|
3623
3676
|
import { homedir as homedir4 } from "os";
|
|
3624
|
-
import { readFileSync as readFileSync13, renameSync as
|
|
3625
|
-
import { isAbsolute as isAbsolute3, join as
|
|
3677
|
+
import { readFileSync as readFileSync13, renameSync as renameSync7, writeFileSync as writeFileSync11 } from "fs";
|
|
3678
|
+
import { isAbsolute as isAbsolute3, join as join19, resolve as resolve3 } from "path";
|
|
3626
3679
|
function repoMapLines(rows) {
|
|
3627
3680
|
if (!rows?.length) return [];
|
|
3628
3681
|
const missing = rows.filter((r) => !r.path).length;
|
|
@@ -3635,18 +3688,24 @@ function repoMapLines(rows) {
|
|
|
3635
3688
|
});
|
|
3636
3689
|
return [head, ...body];
|
|
3637
3690
|
}
|
|
3638
|
-
function
|
|
3691
|
+
function loadRepoMapCache(path) {
|
|
3639
3692
|
try {
|
|
3640
3693
|
const raw = JSON.parse(readFileSync13(path, "utf8"));
|
|
3641
|
-
if (!Array.isArray(raw?.entries)) return [];
|
|
3642
|
-
return
|
|
3643
|
-
|
|
3644
|
-
|
|
3694
|
+
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
3695
|
+
return {
|
|
3696
|
+
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
3697
|
+
entries: raw.entries.filter(
|
|
3698
|
+
(e) => !!e && typeof e.repoFullName === "string" && typeof e.path === "string" && !!e.repoFullName && !!e.path
|
|
3699
|
+
)
|
|
3700
|
+
};
|
|
3645
3701
|
} catch {
|
|
3646
|
-
return [];
|
|
3702
|
+
return { scannedAt: null, entries: [] };
|
|
3647
3703
|
}
|
|
3648
3704
|
}
|
|
3649
|
-
|
|
3705
|
+
function readRepoMapCache(path) {
|
|
3706
|
+
return loadRepoMapCache(path).entries;
|
|
3707
|
+
}
|
|
3708
|
+
var REPO_SCAN_TTL_MS, UNKNOWN_LIST_SCAN_TTL_MS, SOURCE_TEXT;
|
|
3650
3709
|
var init_repoDiscovery = __esm({
|
|
3651
3710
|
"src/repoDiscovery.ts"() {
|
|
3652
3711
|
"use strict";
|
|
@@ -3654,6 +3713,8 @@ var init_repoDiscovery = __esm({
|
|
|
3654
3713
|
init_config();
|
|
3655
3714
|
init_linkFailure();
|
|
3656
3715
|
init_repoScan();
|
|
3716
|
+
REPO_SCAN_TTL_MS = 24 * 60 * 6e4;
|
|
3717
|
+
UNKNOWN_LIST_SCAN_TTL_MS = 60 * 6e4;
|
|
3657
3718
|
SOURCE_TEXT = {
|
|
3658
3719
|
configured: "\u043F\u0443\u0442\u044C \u0437\u0430\u0434\u0430\u043D \u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440\u043E\u043C",
|
|
3659
3720
|
cache: "\u0438\u0437 \u043A\u044D\u0448\u0430 \u043C\u0430\u0448\u0438\u043D\u044B",
|
|
@@ -3998,10 +4059,10 @@ __export(statusCli_exports, {
|
|
|
3998
4059
|
probeLink: () => probeLink,
|
|
3999
4060
|
runStatusCli: () => runStatusCli
|
|
4000
4061
|
});
|
|
4001
|
-
import { existsSync as
|
|
4062
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
4002
4063
|
function outboxPending(path) {
|
|
4003
4064
|
try {
|
|
4004
|
-
if (!
|
|
4065
|
+
if (!existsSync12(path)) return 0;
|
|
4005
4066
|
return readFileSync14(path, "utf8").split("\n").filter((l) => l.trim()).length;
|
|
4006
4067
|
} catch {
|
|
4007
4068
|
return null;
|
|
@@ -4140,8 +4201,8 @@ var init_statusCli = __esm({
|
|
|
4140
4201
|
});
|
|
4141
4202
|
|
|
4142
4203
|
// src/platform.ts
|
|
4143
|
-
import { existsSync as
|
|
4144
|
-
import { delimiter, join as
|
|
4204
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
4205
|
+
import { delimiter, join as join20 } from "path";
|
|
4145
4206
|
function safeReadText(path) {
|
|
4146
4207
|
try {
|
|
4147
4208
|
return readFileSync15(path, "utf8");
|
|
@@ -4150,13 +4211,13 @@ function safeReadText(path) {
|
|
|
4150
4211
|
}
|
|
4151
4212
|
}
|
|
4152
4213
|
function resolveCommandPath(name, env = process.env, platform = process.platform) {
|
|
4153
|
-
if (name.includes("/") || name.includes("\\")) return
|
|
4214
|
+
if (name.includes("/") || name.includes("\\")) return existsSync13(name) ? name : null;
|
|
4154
4215
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
4155
4216
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
4156
4217
|
for (const dir of dirs) {
|
|
4157
4218
|
for (const ext of exts) {
|
|
4158
|
-
const candidate =
|
|
4159
|
-
if (
|
|
4219
|
+
const candidate = join20(dir, name + ext);
|
|
4220
|
+
if (existsSync13(candidate)) return candidate;
|
|
4160
4221
|
}
|
|
4161
4222
|
}
|
|
4162
4223
|
return null;
|
|
@@ -4259,13 +4320,13 @@ var init_platform = __esm({
|
|
|
4259
4320
|
});
|
|
4260
4321
|
|
|
4261
4322
|
// src/envGate.ts
|
|
4262
|
-
import { existsSync as
|
|
4263
|
-
import { join as
|
|
4323
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4324
|
+
import { join as join21 } from "path";
|
|
4264
4325
|
import { createConnection } from "net";
|
|
4265
4326
|
import { execa as execa3 } from "execa";
|
|
4266
4327
|
function detectRequirements(repo, io = {}) {
|
|
4267
|
-
const exists = io.exists ??
|
|
4268
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
4328
|
+
const exists = io.exists ?? existsSync14;
|
|
4329
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join21(repo, f)));
|
|
4269
4330
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
4270
4331
|
}
|
|
4271
4332
|
async function dockerAlive() {
|
|
@@ -4297,7 +4358,7 @@ var init_envGate = __esm({
|
|
|
4297
4358
|
});
|
|
4298
4359
|
|
|
4299
4360
|
// src/doctor.ts
|
|
4300
|
-
import { isAbsolute as isAbsolute4, join as
|
|
4361
|
+
import { isAbsolute as isAbsolute4, join as join22 } from "path";
|
|
4301
4362
|
function ok(id, title, detail) {
|
|
4302
4363
|
return { id, title, status: "ok", detail, remedies: [], critical: false };
|
|
4303
4364
|
}
|
|
@@ -4504,7 +4565,7 @@ function checkRepos(io, snap) {
|
|
|
4504
4565
|
critical: true
|
|
4505
4566
|
};
|
|
4506
4567
|
}
|
|
4507
|
-
if (env && !io.exists(
|
|
4568
|
+
if (env && !io.exists(join22(env, ".git"))) {
|
|
4508
4569
|
return {
|
|
4509
4570
|
id: "repos",
|
|
4510
4571
|
title,
|
|
@@ -4514,7 +4575,7 @@ function checkRepos(io, snap) {
|
|
|
4514
4575
|
critical: true
|
|
4515
4576
|
};
|
|
4516
4577
|
}
|
|
4517
|
-
const broken = snap.paths.filter((p) => !io.exists(
|
|
4578
|
+
const broken = snap.paths.filter((p) => !io.exists(join22(p, ".git")));
|
|
4518
4579
|
if (broken.length) {
|
|
4519
4580
|
return {
|
|
4520
4581
|
id: "repos",
|
|
@@ -4679,11 +4740,11 @@ var init_doctor = __esm({
|
|
|
4679
4740
|
|
|
4680
4741
|
// src/repoMemory.ts
|
|
4681
4742
|
import { readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
|
|
4682
|
-
import { join as
|
|
4743
|
+
import { join as join23 } from "path";
|
|
4683
4744
|
function loadRememberedRepos(home) {
|
|
4684
4745
|
let raw;
|
|
4685
4746
|
try {
|
|
4686
|
-
raw = readFileSync16(
|
|
4747
|
+
raw = readFileSync16(join23(home, FILE), "utf8");
|
|
4687
4748
|
} catch {
|
|
4688
4749
|
return [];
|
|
4689
4750
|
}
|
|
@@ -4718,7 +4779,7 @@ __export(installCli_exports, {
|
|
|
4718
4779
|
runInstallCli: () => runInstallCli,
|
|
4719
4780
|
shortenError: () => shortenError
|
|
4720
4781
|
});
|
|
4721
|
-
import { existsSync as
|
|
4782
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4722
4783
|
function classifyChromiumError(message) {
|
|
4723
4784
|
if (/playwright-core не установлен|Cannot find (module|package)/i.test(message)) return "no-module";
|
|
4724
4785
|
if (/error while loading shared libraries|cannot open shared object file|missing dependencies|\blib[\w.+-]*\.so[.\d]*\b/i.test(message)) return "no-libs";
|
|
@@ -4773,7 +4834,7 @@ function defaultDoctorIO(packageVersion) {
|
|
|
4773
4834
|
packageVersion,
|
|
4774
4835
|
entry,
|
|
4775
4836
|
hasCommand: (name) => hasCommandInPath(name),
|
|
4776
|
-
exists:
|
|
4837
|
+
exists: existsSync15,
|
|
4777
4838
|
run: (command) => runQuiet(command),
|
|
4778
4839
|
latestVersion: () => latestMemo ??= fetchLatestVersion(),
|
|
4779
4840
|
probeChromium: () => probeChromium(),
|
|
@@ -4982,13 +5043,13 @@ var init_updateCli = __esm({
|
|
|
4982
5043
|
|
|
4983
5044
|
// src/cli.ts
|
|
4984
5045
|
init_cliIo();
|
|
4985
|
-
import { basename as basename4, join as
|
|
4986
|
-
import { existsSync as
|
|
5046
|
+
import { basename as basename4, join as join24 } from "path";
|
|
5047
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17, readdirSync as readdirSync10, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4, rmSync as rmSync2 } from "fs";
|
|
4987
5048
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
4988
5049
|
|
|
4989
5050
|
// src/swarmClient.ts
|
|
4990
5051
|
init_config();
|
|
4991
|
-
import { appendFileSync, existsSync as
|
|
5052
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4992
5053
|
import { dirname } from "path";
|
|
4993
5054
|
import { randomUUID } from "crypto";
|
|
4994
5055
|
|
|
@@ -5195,8 +5256,34 @@ init_names2();
|
|
|
5195
5256
|
init_storm();
|
|
5196
5257
|
|
|
5197
5258
|
// src/logTail.ts
|
|
5198
|
-
import { closeSync, existsSync as
|
|
5259
|
+
import { closeSync, existsSync as existsSync4, openSync, readSync, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
5260
|
+
import { join as join6 } from "path";
|
|
5261
|
+
|
|
5262
|
+
// src/runLogDir.ts
|
|
5263
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync4, renameSync as renameSync2, statSync as statSync3 } from "fs";
|
|
5199
5264
|
import { join as join5 } from "path";
|
|
5265
|
+
function runLogDirName(runLogKey) {
|
|
5266
|
+
const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
5267
|
+
if (!safe) throw new Error(`\u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435 \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0432 \u0438\u043C\u044F \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430 run-log'\u043E\u0432: ${JSON.stringify(runLogKey)}`);
|
|
5268
|
+
return /^\d+$/.test(safe) ? `k-${safe}` : safe;
|
|
5269
|
+
}
|
|
5270
|
+
function runLogDirPath(runLogsDir, runLogKey) {
|
|
5271
|
+
return join5(runLogsDir, runLogDirName(runLogKey));
|
|
5272
|
+
}
|
|
5273
|
+
function findRunLogDir(runLogsDir, keys) {
|
|
5274
|
+
for (const key of keys) {
|
|
5275
|
+
const dir = runLogDirPath(runLogsDir, key);
|
|
5276
|
+
if (existsSync3(dir)) return dir;
|
|
5277
|
+
}
|
|
5278
|
+
return null;
|
|
5279
|
+
}
|
|
5280
|
+
function runLogDirForWrite(runLogsDir, keys) {
|
|
5281
|
+
const canonical = keys[0];
|
|
5282
|
+
if (!canonical) return null;
|
|
5283
|
+
return findRunLogDir(runLogsDir, keys) ?? runLogDirPath(runLogsDir, canonical);
|
|
5284
|
+
}
|
|
5285
|
+
|
|
5286
|
+
// src/logTail.ts
|
|
5200
5287
|
var TAIL_LINES_FALLBACK = 200;
|
|
5201
5288
|
var TAIL_MAX_BYTES_FALLBACK = 64 * 1024;
|
|
5202
5289
|
|
|
@@ -5283,6 +5370,11 @@ var SwarmClient = class _SwarmClient {
|
|
|
5283
5370
|
// Живёт в памяти: рестарт демона просто переспросит прод.
|
|
5284
5371
|
depsProd = /* @__PURE__ */ new Map();
|
|
5285
5372
|
depsAskedAt = /* @__PURE__ */ new Map();
|
|
5373
|
+
// TASWM-399: ссылка dependsOn → человеко-слаг блокера, как его назвал прод. В колонке
|
|
5374
|
+
// лежит cuid прогона или задачи, а в лог демона, в событие прогона и в аномалию должен
|
|
5375
|
+
// уходить «TASWM-392», а не «cmsqqpphd011y2xtbckkut6bj» — иначе оператору всё равно
|
|
5376
|
+
// нечего искать. Резолв делает прод (см. resolveDepRefs), здесь только запоминаем.
|
|
5377
|
+
depSlug = /* @__PURE__ */ new Map();
|
|
5286
5378
|
static DEPS_TTL_MS = 6e4;
|
|
5287
5379
|
// TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
|
|
5288
5380
|
// ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
|
|
@@ -5342,25 +5434,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
5342
5434
|
this.repoFullNames = registry.map((e) => e.repoFullName);
|
|
5343
5435
|
this.repoPathByFullName = new Map(registry.map((e) => [e.repoFullName, normalizeRepo(e.path)]));
|
|
5344
5436
|
this.host = opts.host ?? null;
|
|
5345
|
-
this.repoExists = opts.repoExists ??
|
|
5437
|
+
this.repoExists = opts.repoExists ?? existsSync5;
|
|
5346
5438
|
this.questionSyncMs = opts.questionSyncMs ?? 3e3;
|
|
5347
5439
|
this.executorId = opts.executorId ?? EXECUTOR.id;
|
|
5348
5440
|
this.outboxPath = opts.outboxPath;
|
|
5349
5441
|
this.mapPath = `${opts.outboxPath}.prodmap.json`;
|
|
5350
5442
|
this.stormPath = `${opts.outboxPath}.storm.jsonl`;
|
|
5351
|
-
|
|
5443
|
+
mkdirSync5(dirname(this.outboxPath), { recursive: true });
|
|
5352
5444
|
this.recover();
|
|
5353
5445
|
}
|
|
5354
5446
|
// ── рестарт: поднять outbox (незаписанные операции) и карту runKey→prodId ─────
|
|
5355
5447
|
recover() {
|
|
5356
|
-
if (
|
|
5448
|
+
if (existsSync5(this.mapPath)) {
|
|
5357
5449
|
try {
|
|
5358
5450
|
const obj = JSON.parse(readFileSync3(this.mapPath, "utf8"));
|
|
5359
5451
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
5360
5452
|
} catch {
|
|
5361
5453
|
}
|
|
5362
5454
|
}
|
|
5363
|
-
if (
|
|
5455
|
+
if (existsSync5(this.outboxPath)) {
|
|
5364
5456
|
const txt = readFileSync3(this.outboxPath, "utf8");
|
|
5365
5457
|
for (const line of txt.split("\n")) {
|
|
5366
5458
|
const s = line.trim();
|
|
@@ -5381,7 +5473,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
5381
5473
|
// eventsLog ПЕРВЫМИ (до hydrate), в порядке файла (= хронологический); id
|
|
5382
5474
|
// переприсваиваем локально (id событий — только порядок, не корреляция с продом).
|
|
5383
5475
|
replayGlobalEvents() {
|
|
5384
|
-
if (!
|
|
5476
|
+
if (!existsSync5(this.stormPath)) return;
|
|
5385
5477
|
let txt;
|
|
5386
5478
|
try {
|
|
5387
5479
|
txt = readFileSync3(this.stormPath, "utf8");
|
|
@@ -5646,6 +5738,24 @@ var SwarmClient = class _SwarmClient {
|
|
|
5646
5738
|
const viaProd = [...this.runs.values()].find((r) => r.runKey === `p:${prodId}` || this.prodIdByKey.get(r.runKey) === prodId);
|
|
5647
5739
|
return viaProd ? this.mat(viaProd) : null;
|
|
5648
5740
|
}
|
|
5741
|
+
/**
|
|
5742
|
+
* TASWM-419: адреса каталога run-log'ов прогона. КАНОНИЧЕСКИЙ — прод-вида `p:<cuid>`, как
|
|
5743
|
+
* только cuid известен: он один и тот же во всех жизнях демона, а локальный uuid прогона
|
|
5744
|
+
* после рестарта уже не воскресает (гидрированный прогон приезжает под прод-ключом).
|
|
5745
|
+
* Дальше — псевдонимы: uuid, под которым каталог мог завестись в окно между спавном и
|
|
5746
|
+
* первым флашем в прод. По ним каталог ищется, чтобы запуски одного прогона не расползлись
|
|
5747
|
+
* по двум каталогам (иначе cost недосчитал бы часть, а хвост показал бы не тот запуск).
|
|
5748
|
+
* Номер прогона в адресе не участвует вовсе — он и есть источник «лога чужого прогона».
|
|
5749
|
+
*/
|
|
5750
|
+
runLogKeys(id) {
|
|
5751
|
+
const key = this.runs.get(id)?.runKey;
|
|
5752
|
+
if (!key) return [];
|
|
5753
|
+
const prodId = key.startsWith("p:") ? key.slice(2) : this.prodIdByKey.get(key);
|
|
5754
|
+
if (!prodId) return [key];
|
|
5755
|
+
const canonical = `p:${prodId}`;
|
|
5756
|
+
const aliases = [key, ...[...this.prodIdByKey.entries()].filter(([, v]) => v === prodId).map(([k]) => k)];
|
|
5757
|
+
return [canonical, ...new Set(aliases.filter((k) => k !== canonical))];
|
|
5758
|
+
}
|
|
5649
5759
|
listRuns(status) {
|
|
5650
5760
|
const all = [...this.runs.values()].filter((r) => status ? r.status === status : true);
|
|
5651
5761
|
all.sort((a, b) => b.id - a.id);
|
|
@@ -5762,16 +5872,25 @@ var SwarmClient = class _SwarmClient {
|
|
|
5762
5872
|
/**
|
|
5763
5873
|
* TASWM-361: вердикт по каждой зависимости — см. докблок SwarmStore.depVerdicts.
|
|
5764
5874
|
*
|
|
5765
|
-
*
|
|
5766
|
-
* (
|
|
5767
|
-
* стоят разного:
|
|
5768
|
-
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
|
|
5769
|
-
* портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
5770
|
-
* •
|
|
5771
|
-
* на соседней машине как раз и может быть
|
|
5772
|
-
*
|
|
5773
|
-
*
|
|
5774
|
-
*
|
|
5875
|
+
* ИСТОЧНИК ПРАВДЫ — ПРОД (TASWM-399), зеркало здесь только быстрый путь. Зеркало —
|
|
5876
|
+
* ЧАСТИЧНАЯ выборка (прогоны этой машины, окно гидрации), и по частичной выборке два
|
|
5877
|
+
* вывода стоят разного:
|
|
5878
|
+
* • «есть смерженный прогон слага» — вывод от НАЛИЧИЯ: чего зеркало не видит, того он
|
|
5879
|
+
* не портит, поэтому успех подтверждаем и локально (так было до TASWM-361);
|
|
5880
|
+
* • всё остальное — вывод от ОТСУТСТВИЯ, и его принимаем ТОЛЬКО из ответа прода
|
|
5881
|
+
* (refreshDeps): невидимый зеркалу прогон на соседней машине как раз и может быть
|
|
5882
|
+
* живым, а закрытый с прода — давно смерженным.
|
|
5883
|
+
*
|
|
5884
|
+
* ССЫЛКА — НЕ ОБЯЗАТЕЛЬНО СЛАГ. В `dependsOn` лежит чаще всего cuid ПРОГОНА (так пишет
|
|
5885
|
+
* overmind), реже cuid задачи, и только дев-спавн `--after` пишет человеко-слаг. Быстрый
|
|
5886
|
+
* путь по зеркалу сравнивает `task_slug`, то есть срабатывает лишь на третьей форме —
|
|
5887
|
+
* это нормально: остальные две резолвит прод. А вот считать «раз в зеркале не нашёл —
|
|
5888
|
+
* значит не готово» было корнем TASWM-399.
|
|
5889
|
+
*
|
|
5890
|
+
* Прода ещё не спрашивали (первый тик) или он моргнул → `pending`: прогон честно ждёт
|
|
5891
|
+
* следующего окна. Лишняя минута очереди дешевле дрона, поднятого поверх работающего
|
|
5892
|
+
* блокера. `unknown` (блокера НЕ ЗНАЕТ И ПРОД) ставит только ответ прода — местным
|
|
5893
|
+
* незнанием такой вывод не делается.
|
|
5775
5894
|
*/
|
|
5776
5895
|
depVerdicts(depends_on) {
|
|
5777
5896
|
if (!depends_on || depends_on.length === 0) return [];
|
|
@@ -5781,57 +5900,89 @@ var SwarmClient = class _SwarmClient {
|
|
|
5781
5900
|
doneInMirror.add(r.task_slug);
|
|
5782
5901
|
}
|
|
5783
5902
|
}
|
|
5784
|
-
return depends_on.map((
|
|
5785
|
-
if (doneInMirror.has(
|
|
5786
|
-
return { slug, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
5903
|
+
return depends_on.map((ref) => {
|
|
5904
|
+
if (doneInMirror.has(ref)) {
|
|
5905
|
+
return { slug: ref, outcome: "done", met: true, status: "done", name: null, stopReason: null };
|
|
5906
|
+
}
|
|
5907
|
+
const fromProd = this.depsProd.get(ref);
|
|
5908
|
+
if (fromProd) {
|
|
5909
|
+
if (fromProd.outcome === "unknown" && this.knowsDepRef(ref)) {
|
|
5910
|
+
return { slug: this.depSlug.get(ref) ?? ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
5911
|
+
}
|
|
5912
|
+
return { slug: this.depSlug.get(ref) ?? ref, ...fromProd };
|
|
5787
5913
|
}
|
|
5788
|
-
|
|
5789
|
-
if (fromProd) return { slug, ...fromProd };
|
|
5790
|
-
return { slug, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
5914
|
+
return { slug: ref, outcome: "pending", met: false, status: null, name: null, stopReason: null };
|
|
5791
5915
|
});
|
|
5792
5916
|
}
|
|
5793
5917
|
/**
|
|
5794
|
-
*
|
|
5918
|
+
* Знает ли ЗЕРКАЛО хоть один прогон по ссылке `dependsOn` (TASWM-399). Ссылка бывает
|
|
5919
|
+
* слагом, cuid'ом задачи и cuid'ом прогона — последний сверяем с прод-id, как это
|
|
5920
|
+
* делают runByProdId/refreshRun. Служебные сабраны (ревьюер/резолвер/верификатор) не
|
|
5921
|
+
* в счёт: их существование не означает, что жив сам блокер.
|
|
5922
|
+
*/
|
|
5923
|
+
knowsDepRef(ref) {
|
|
5924
|
+
for (const r of this.runs.values()) {
|
|
5925
|
+
if (r.resolves_run_id != null || r.reviews_run_id != null || r.verifies_run_id != null) continue;
|
|
5926
|
+
if (r.task_slug === ref) return true;
|
|
5927
|
+
if (r.runKey === `p:${ref}` || this.prodIdByKey.get(r.runKey) === ref) return true;
|
|
5928
|
+
}
|
|
5929
|
+
return false;
|
|
5930
|
+
}
|
|
5931
|
+
/**
|
|
5932
|
+
* TASWM-300 → TASWM-399: спросить ПРОД про зависимости — см. докблок SwarmStore.refreshDeps.
|
|
5933
|
+
*
|
|
5934
|
+
* Почему зеркала мало. В нём лежат только прогоны ЭТОЙ машины (гейт isMine, hydrateRun) и
|
|
5935
|
+
* только те, что попали в окно гидрации (`swarm_list_runs limit`, сортировка по updatedAt
|
|
5936
|
+
* desc — сутки работы роя). Зависимость, закрытая на соседней машине, обнулившаяся при
|
|
5937
|
+
* рестарте демона или просто позавчерашняя, в зеркало не попадала — и `dependsOn` держал
|
|
5938
|
+
* прогон в очереди вечно.
|
|
5795
5939
|
*
|
|
5796
|
-
*
|
|
5797
|
-
*
|
|
5798
|
-
*
|
|
5799
|
-
*
|
|
5800
|
-
* (
|
|
5940
|
+
* ЧЕМ СПРАШИВАЕМ И ПОЧЕМУ НЕ `swarm_list_runs` (корень TASWM-399). До этой задачи здесь
|
|
5941
|
+
* шёл `swarm_list_runs { taskId: <ref> }` — то есть ссылка трактовалась как ЗАДАЧА. Но в
|
|
5942
|
+
* `dependsOn` чаще всего лежит cuid ПРОГОНА: `resolveTaskRef` его задачей не признаёт,
|
|
5943
|
+
* ответ приходит `isError`, вердикт не появляется НИКОГДА, и прогон стоит молча при
|
|
5944
|
+
* свободной машине (12–13.08 — пять остановок роя за сутки). `swarm_deps_status` понимает
|
|
5945
|
+
* все три формы ссылки и отвечает по единому правилу роя (`judgeDep` на прод-стороне).
|
|
5801
5946
|
*
|
|
5802
5947
|
* Кеш. «Смержено» МОНОТОННО: прогон, ставший done, назад не откатывается, — поэтому
|
|
5803
5948
|
* положительный ответ помним навсегда и больше не спрашиваем. Остальные вердикты (включая
|
|
5804
|
-
* «исчерпана
|
|
5805
|
-
*
|
|
5806
|
-
*
|
|
5949
|
+
* «исчерпана неуспешно» и «блокера не знает никто») переспрашиваем не чаще DEPS_TTL_MS:
|
|
5950
|
+
* оператор вправе переиграть блокер новым прогоном, а тик идёт раз в ~4 с — без TTL это
|
|
5951
|
+
* был бы поход в прод на каждую ссылку каждый тик.
|
|
5952
|
+
*
|
|
5953
|
+
* Одним вызовом на все просроченные ссылки, а не по вызову на ссылку: очередь ждущих
|
|
5954
|
+
* бывает в десятки прогонов, и N запросов на тик — это тот же самый поход в прод, только
|
|
5955
|
+
* дороже.
|
|
5807
5956
|
*/
|
|
5808
|
-
async refreshDeps(
|
|
5809
|
-
|
|
5810
|
-
if (!
|
|
5811
|
-
const asked = this.depsAskedAt.get(
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5957
|
+
async refreshDeps(refs, nowMs = Date.now()) {
|
|
5958
|
+
const ask = [...new Set(refs)].filter((ref) => {
|
|
5959
|
+
if (!ref || this.depsProd.get(ref)?.outcome === "done") return false;
|
|
5960
|
+
const asked = this.depsAskedAt.get(ref);
|
|
5961
|
+
return asked === void 0 || nowMs - asked >= _SwarmClient.DEPS_TTL_MS;
|
|
5962
|
+
});
|
|
5963
|
+
if (!ask.length) return;
|
|
5964
|
+
for (const ref of ask) this.depsAskedAt.set(ref, nowMs);
|
|
5965
|
+
let res;
|
|
5966
|
+
try {
|
|
5967
|
+
res = await this.call("swarm_deps_status", { refs: ask });
|
|
5968
|
+
} catch {
|
|
5969
|
+
for (const ref of ask) this.depsAskedAt.delete(ref);
|
|
5970
|
+
return;
|
|
5971
|
+
}
|
|
5972
|
+
if (res?.isError) return;
|
|
5973
|
+
const deps = res.structuredContent?.deps ?? [];
|
|
5974
|
+
for (const d of deps) {
|
|
5975
|
+
const ref = typeof d.ref === "string" ? d.ref : null;
|
|
5976
|
+
if (!ref) continue;
|
|
5977
|
+
const outcome = String(d.outcome ?? "pending");
|
|
5978
|
+
this.depsProd.set(ref, {
|
|
5979
|
+
outcome,
|
|
5980
|
+
met: Boolean(d.met),
|
|
5981
|
+
status: d.status ?? null,
|
|
5982
|
+
name: d.name ?? null,
|
|
5983
|
+
stopReason: d.stopReason ?? null
|
|
5984
|
+
});
|
|
5985
|
+
if (typeof d.slug === "string" && d.slug) this.depSlug.set(ref, d.slug);
|
|
5835
5986
|
}
|
|
5836
5987
|
}
|
|
5837
5988
|
/**
|
|
@@ -6150,7 +6301,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
6150
6301
|
* транзиентным сбоем ОС, который этот PR должен переживать, а не превращать в мор.
|
|
6151
6302
|
*/
|
|
6152
6303
|
reconcileRuntime(opts) {
|
|
6153
|
-
const wtExists = opts.worktreeExists ??
|
|
6304
|
+
const wtExists = opts.worktreeExists ?? existsSync5;
|
|
6154
6305
|
const pidByWorktree = /* @__PURE__ */ new Map();
|
|
6155
6306
|
try {
|
|
6156
6307
|
for (const p of opts.listProcesses()) if (p.worktree) pidByWorktree.set(p.worktree, p.pid);
|
|
@@ -6639,6 +6790,12 @@ var SwarmClient = class _SwarmClient {
|
|
|
6639
6790
|
...typeof h.paused === "boolean" ? { paused: h.paused } : {},
|
|
6640
6791
|
...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
|
|
6641
6792
|
...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {},
|
|
6793
|
+
// TASWM-424: «доступ отозван» — состояние ровно того же рода, что пауза, и
|
|
6794
|
+
// формулировка у него ОДНА на все поверхности (HOST_ACCESS_REVOKED_TEXT):
|
|
6795
|
+
// `hostLines` зовёт тот же `hostStateText`, что и парк машин в /swarm. Без
|
|
6796
|
+
// этого поля `swarm hosts` показывал отвергнутую машину просто молчащей —
|
|
6797
|
+
// то самое различие, ради которого флаг и заводили (TASWM-324).
|
|
6798
|
+
...typeof h.accessRevoked === "boolean" ? { accessRevoked: h.accessRevoked } : {},
|
|
6642
6799
|
// Показаний нет (старый прод, машина со старым демоном) — ключа не заводим вовсе,
|
|
6643
6800
|
// ровно как с паузой выше: «не знаем» и так значит «не знаем».
|
|
6644
6801
|
...resources ? { resources } : {}
|
|
@@ -6724,9 +6881,18 @@ var SwarmClient = class _SwarmClient {
|
|
|
6724
6881
|
* ВСЕГДА (в т.ч. `storm:false`), как паузу: это состояние, а не команда, и молчание тут
|
|
6725
6882
|
* означало бы «прод помнит вчерашний шторм», когда рой давно работает.
|
|
6726
6883
|
*
|
|
6884
|
+
* TASWM-402: тем же пульсом уезжают КОРНИ WORKTREE'ЕВ этой машины (`worktreeRoots` —
|
|
6885
|
+
* `owner/repo` → `<worktreesDir>/<repo-basename>`). Это НЕ рантайм и правило «pid и путь
|
|
6886
|
+
* дрона в прод не уходят» не трогает: корень — статичная конфигурация машины, того же
|
|
6887
|
+
* рода, что реестр репо и ёмкость, он не зависит ни от прогона, ни от живости процесса и
|
|
6888
|
+
* не протухает между пульсами. Прод склеивает `<корень>/<слаг>` сам — и отвечает человеку
|
|
6889
|
+
* на вопрос «куда идти руками», когда команда машине не доиграла (а в этом случае, по
|
|
6890
|
+
* определению, спросить рантайм у молчащей машины уже нельзя). Не передали корень (не
|
|
6891
|
+
* знаем своего каталога) → ключа нет, прод прошлое не трогает — как с capacity/resources.
|
|
6892
|
+
*
|
|
6727
6893
|
* Прод недоступен / ответ без поручений → пустые списки (демон работает дальше).
|
|
6728
6894
|
*/
|
|
6729
|
-
async heartbeat(daemonVersion, pause, capacity, resources, storm, signIn) {
|
|
6895
|
+
async heartbeat(daemonVersion, pause, capacity, resources, storm, worktreesDir, signIn) {
|
|
6730
6896
|
try {
|
|
6731
6897
|
const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
|
|
6732
6898
|
if (daemonVersion) args.daemonVersion = daemonVersion;
|
|
@@ -6744,6 +6910,11 @@ var SwarmClient = class _SwarmClient {
|
|
|
6744
6910
|
args.storm = storm.active;
|
|
6745
6911
|
if (storm.reason) args.stormReason = storm.reason;
|
|
6746
6912
|
}
|
|
6913
|
+
if (worktreesDir) {
|
|
6914
|
+
const roots = {};
|
|
6915
|
+
for (const [full, path] of this.repoPathByFullName) roots[full] = droneWorktreeRoot(worktreesDir, path);
|
|
6916
|
+
if (Object.keys(roots).length) args.worktreeRoots = roots;
|
|
6917
|
+
}
|
|
6747
6918
|
if (this.host) {
|
|
6748
6919
|
args.hostId = this.host.hostId;
|
|
6749
6920
|
args.hostName = this.host.hostName;
|
|
@@ -6892,10 +7063,20 @@ var SwarmClient = class _SwarmClient {
|
|
|
6892
7063
|
* именно путь. Не нашли в реестре (репо не наш / ручной `--repo <path>` спавн, где
|
|
6893
7064
|
* repo — ВСЕГДА уже путь, старый формат) → возвращаем как есть без изменений
|
|
6894
7065
|
* (обратная совместимость: раньше pr.repo ВСЕГДА был локальным путём).
|
|
7066
|
+
*
|
|
7067
|
+
* РЕГИСТР НЕ РАЗЛИЧАЕМ (TASWM-422). Ключи карты приведены к нижнему регистру
|
|
7068
|
+
* (parseRepoFullName — «GitHub сам не различает case в owner/repo»), а прод присылает
|
|
7069
|
+
* `run.repo` тем регистром, каким репозиторий записан в проекте. 17.08 это разошлось:
|
|
7070
|
+
* машина объявляла `ilyasmurov/ansamblist`, задача пришла с `ilyasmurov/Ansamblist`,
|
|
7071
|
+
* `Map.get` промахнулся — и вместо пути в прогон уехало полное имя репозитория. Дальше
|
|
7072
|
+
* git-операции шли в несуществующий каталог, прогон умирал за 5 секунд, а задача
|
|
7073
|
+
* возвращалась в очередь и бралась снова: пять павших прогонов за пять минут.
|
|
7074
|
+
* Возвращаем ИСХОДНУЮ строку, а не приведённую: промах бывает и на локальном пути
|
|
7075
|
+
* (ручной спавн), а путь регистр различает — вернув lowercase, мы сломали бы его.
|
|
6895
7076
|
*/
|
|
6896
7077
|
resolveRepo(raw) {
|
|
6897
7078
|
if (!raw) return "";
|
|
6898
|
-
return this.repoPathByFullName.get(raw) ?? raw;
|
|
7079
|
+
return this.repoPathByFullName.get(raw) ?? this.repoPathByFullName.get(raw.toLowerCase()) ?? raw;
|
|
6899
7080
|
}
|
|
6900
7081
|
/**
|
|
6901
7082
|
* TASWM-281: МОЯ ли это работа. Три исхода:
|
|
@@ -7493,20 +7674,20 @@ function tailFile(path, lines) {
|
|
|
7493
7674
|
}
|
|
7494
7675
|
|
|
7495
7676
|
// src/logRotate.ts
|
|
7496
|
-
import { copyFileSync, renameSync as
|
|
7677
|
+
import { copyFileSync, renameSync as renameSync3, statSync as statSync5, fstatSync, truncateSync, unlinkSync, existsSync as existsSync6 } from "fs";
|
|
7497
7678
|
var realIo = {
|
|
7498
7679
|
size: (path) => {
|
|
7499
7680
|
try {
|
|
7500
|
-
return
|
|
7681
|
+
return statSync5(path).size;
|
|
7501
7682
|
} catch {
|
|
7502
7683
|
return null;
|
|
7503
7684
|
}
|
|
7504
7685
|
},
|
|
7505
7686
|
copy: copyFileSync,
|
|
7506
|
-
rename:
|
|
7687
|
+
rename: renameSync3,
|
|
7507
7688
|
truncate: (path) => truncateSync(path, 0),
|
|
7508
7689
|
unlink: unlinkSync,
|
|
7509
|
-
exists:
|
|
7690
|
+
exists: existsSync6
|
|
7510
7691
|
};
|
|
7511
7692
|
var STRAY_GENERATIONS = 8;
|
|
7512
7693
|
function rotateDaemonLog(path, opts, io = realIo) {
|
|
@@ -7764,18 +7945,18 @@ init_repoRegistry();
|
|
|
7764
7945
|
|
|
7765
7946
|
// src/hostIdentity.ts
|
|
7766
7947
|
import { hostname } from "os";
|
|
7767
|
-
import { existsSync as
|
|
7768
|
-
import { join as
|
|
7948
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
7949
|
+
import { join as join7 } from "path";
|
|
7769
7950
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
7770
7951
|
function hostIdentityPath(home) {
|
|
7771
|
-
return
|
|
7952
|
+
return join7(home, "host.json");
|
|
7772
7953
|
}
|
|
7773
7954
|
function loadHostIdentity(deps) {
|
|
7774
|
-
const exists = deps.exists ??
|
|
7955
|
+
const exists = deps.exists ?? existsSync7;
|
|
7775
7956
|
const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
|
|
7776
7957
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
7777
7958
|
writeFileSync4(`${p}.tmp`, data);
|
|
7778
|
-
|
|
7959
|
+
renameSync4(`${p}.tmp`, p);
|
|
7779
7960
|
});
|
|
7780
7961
|
const host = deps.hostname ?? hostname;
|
|
7781
7962
|
const newId = deps.newId ?? randomUUID2;
|
|
@@ -7889,8 +8070,8 @@ import { basename as basename2 } from "path";
|
|
|
7889
8070
|
import { serve as serve2 } from "@hono/node-server";
|
|
7890
8071
|
|
|
7891
8072
|
// src/diff.ts
|
|
7892
|
-
import { statSync as
|
|
7893
|
-
import { join as
|
|
8073
|
+
import { statSync as statSync6 } from "fs";
|
|
8074
|
+
import { join as join8 } from "path";
|
|
7894
8075
|
import { $ as $3 } from "execa";
|
|
7895
8076
|
var MAX_FILES = 200;
|
|
7896
8077
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -8033,7 +8214,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
8033
8214
|
}
|
|
8034
8215
|
function mtime(path) {
|
|
8035
8216
|
try {
|
|
8036
|
-
return
|
|
8217
|
+
return statSync6(path).mtimeMs;
|
|
8037
8218
|
} catch {
|
|
8038
8219
|
return 0;
|
|
8039
8220
|
}
|
|
@@ -8058,7 +8239,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
8058
8239
|
files.push(pf);
|
|
8059
8240
|
}
|
|
8060
8241
|
}
|
|
8061
|
-
files.sort((a, b) => mtime(
|
|
8242
|
+
files.sort((a, b) => mtime(join8(worktree, b.path)) - mtime(join8(worktree, a.path)));
|
|
8062
8243
|
const truncatedFiles = files.length > MAX_FILES;
|
|
8063
8244
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
8064
8245
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -8211,13 +8392,13 @@ function deniedSinceLine(nowMs) {
|
|
|
8211
8392
|
init_config();
|
|
8212
8393
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
8213
8394
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
8214
|
-
import { join as
|
|
8395
|
+
import { join as join10 } from "path";
|
|
8215
8396
|
var REGISTER = "/api/oauth/register";
|
|
8216
8397
|
var AUTHORIZE = "/api/oauth/authorize";
|
|
8217
8398
|
var TOKEN = "/api/oauth/token";
|
|
8218
8399
|
var SCOPE = "openid profile email taskless:full";
|
|
8219
|
-
var STORE_PATH2 =
|
|
8220
|
-
var CLIENT_PATH =
|
|
8400
|
+
var STORE_PATH2 = join10(SWARM_HOME, "oauth.json");
|
|
8401
|
+
var CLIENT_PATH = join10(SWARM_HOME, "oauth-client.json");
|
|
8221
8402
|
function makePkce() {
|
|
8222
8403
|
const verifier = randomBytes2(48).toString("base64url");
|
|
8223
8404
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -9944,15 +10125,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
9944
10125
|
|
|
9945
10126
|
// src/launcher.ts
|
|
9946
10127
|
import { spawn as spawn2 } from "child_process";
|
|
9947
|
-
import { closeSync as closeSync3, existsSync as
|
|
10128
|
+
import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync8, openSync as openSync3, readdirSync as readdirSync7 } from "fs";
|
|
9948
10129
|
import { createRequire } from "module";
|
|
9949
10130
|
import { homedir as homedir3 } from "os";
|
|
9950
|
-
import { dirname as dirname3, join as
|
|
10131
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
9951
10132
|
import { fileURLToPath } from "url";
|
|
9952
10133
|
|
|
9953
10134
|
// src/droneHome.ts
|
|
9954
|
-
import { lstatSync, mkdirSync as
|
|
9955
|
-
import { join as
|
|
10135
|
+
import { lstatSync, mkdirSync as mkdirSync7, readdirSync as readdirSync6, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
|
|
10136
|
+
import { join as join11, relative, sep } from "path";
|
|
9956
10137
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
9957
10138
|
function droneHomeDeny(env = process.env) {
|
|
9958
10139
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -9970,29 +10151,29 @@ function buildDroneHome(opts) {
|
|
|
9970
10151
|
const { realHome, droneHome } = opts;
|
|
9971
10152
|
const deny = opts.deny ?? droneHomeDeny();
|
|
9972
10153
|
const platform = opts.platform ?? process.platform;
|
|
9973
|
-
|
|
9974
|
-
const wanted = new Set(mirroredEntries(
|
|
10154
|
+
mkdirSync7(droneHome, { recursive: true, mode: 448 });
|
|
10155
|
+
const wanted = new Set(mirroredEntries(readdirSync6(realHome), deny, platform));
|
|
9975
10156
|
const rel = relative(realHome, droneHome);
|
|
9976
10157
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
9977
|
-
for (const existing of
|
|
10158
|
+
for (const existing of readdirSync6(droneHome)) {
|
|
9978
10159
|
if (wanted.has(existing)) continue;
|
|
9979
|
-
const p =
|
|
10160
|
+
const p = join11(droneHome, existing);
|
|
9980
10161
|
try {
|
|
9981
10162
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
9982
10163
|
} catch {
|
|
9983
10164
|
}
|
|
9984
10165
|
}
|
|
9985
10166
|
for (const name of wanted) {
|
|
9986
|
-
const link =
|
|
10167
|
+
const link = join11(droneHome, name);
|
|
9987
10168
|
try {
|
|
9988
10169
|
lstatSync(link);
|
|
9989
10170
|
continue;
|
|
9990
10171
|
} catch {
|
|
9991
10172
|
}
|
|
9992
|
-
const target =
|
|
10173
|
+
const target = join11(realHome, name);
|
|
9993
10174
|
let isDir;
|
|
9994
10175
|
try {
|
|
9995
|
-
isDir =
|
|
10176
|
+
isDir = statSync7(target).isDirectory();
|
|
9996
10177
|
} catch {
|
|
9997
10178
|
continue;
|
|
9998
10179
|
}
|
|
@@ -10010,7 +10191,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
10010
10191
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
10011
10192
|
];
|
|
10012
10193
|
function realPathEnvPins(realHome) {
|
|
10013
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
10194
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join11(realHome, ...segments)]));
|
|
10014
10195
|
}
|
|
10015
10196
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
10016
10197
|
return [
|
|
@@ -10122,7 +10303,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
10122
10303
|
const env = { ...base3 };
|
|
10123
10304
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
10124
10305
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
10125
|
-
env.TURBO_CACHE_DIR =
|
|
10306
|
+
env.TURBO_CACHE_DIR = join12(worktree, ".turbo", "cache");
|
|
10126
10307
|
}
|
|
10127
10308
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
10128
10309
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -10150,20 +10331,21 @@ var DAEMON_STATE_ENV = [
|
|
|
10150
10331
|
"TASKLESS_TOKEN"
|
|
10151
10332
|
];
|
|
10152
10333
|
function bundledRoleDir(kind) {
|
|
10153
|
-
return
|
|
10334
|
+
return join12(dirname3(fileURLToPath(import.meta.url)), "..", "roles", kind);
|
|
10154
10335
|
}
|
|
10155
10336
|
function roleDir(worktree, kind) {
|
|
10156
|
-
return
|
|
10337
|
+
return join12(worktree, ".claude", "roles", kind);
|
|
10157
10338
|
}
|
|
10158
10339
|
function roleDirs(worktree, kind) {
|
|
10159
|
-
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) =>
|
|
10340
|
+
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync8(dir));
|
|
10160
10341
|
}
|
|
10161
|
-
function nextRunLogPath(runLogsDir,
|
|
10162
|
-
const dir =
|
|
10163
|
-
|
|
10342
|
+
function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
10343
|
+
const dir = runLogDirForWrite(runLogsDir, runLogKeys);
|
|
10344
|
+
if (!dir) throw new Error("\u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 (SwarmStore.runLogKeys)");
|
|
10345
|
+
mkdirSync8(dir, { recursive: true });
|
|
10164
10346
|
let files = [];
|
|
10165
10347
|
try {
|
|
10166
|
-
files =
|
|
10348
|
+
files = readdirSync7(dir);
|
|
10167
10349
|
} catch {
|
|
10168
10350
|
}
|
|
10169
10351
|
let next = 0;
|
|
@@ -10171,11 +10353,11 @@ function nextRunLogPath(runLogsDir, runId) {
|
|
|
10171
10353
|
const m = /^(\d+)\.json/.exec(f);
|
|
10172
10354
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
10173
10355
|
}
|
|
10174
|
-
return
|
|
10356
|
+
return join12(dir, `${next}.json`);
|
|
10175
10357
|
}
|
|
10176
|
-
function openNextRunLog(runLogsDir,
|
|
10358
|
+
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
10177
10359
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
10178
|
-
const path = nextRunLogPath(runLogsDir,
|
|
10360
|
+
const path = nextRunLogPath(runLogsDir, runLogKeys);
|
|
10179
10361
|
let out;
|
|
10180
10362
|
try {
|
|
10181
10363
|
out = openSync3(path, "wx");
|
|
@@ -10191,7 +10373,7 @@ function openNextRunLog(runLogsDir, runId) {
|
|
|
10191
10373
|
}
|
|
10192
10374
|
return { out, err };
|
|
10193
10375
|
}
|
|
10194
|
-
throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0439\u0442\u0438 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0439 run-log \u0444\u0430\u0439\u043B \
|
|
10376
|
+
throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0439\u0442\u0438 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0439 run-log \u0444\u0430\u0439\u043B \u043F\u0440\u043E\u0433\u043E\u043D\u0430 (${runLogKeys[0]}) \u2014 \u0433\u043E\u043D\u043A\u0430?`);
|
|
10195
10377
|
}
|
|
10196
10378
|
function makeLauncher(cfg) {
|
|
10197
10379
|
return ({ worktree, run, prompt, continueSession }) => {
|
|
@@ -10254,9 +10436,9 @@ function makeLauncher(cfg) {
|
|
|
10254
10436
|
}
|
|
10255
10437
|
const realHome = cfg.droneIsolation?.realHome ?? homedir3();
|
|
10256
10438
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
10257
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !
|
|
10439
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync8(join12(cfg.droneIsolation.swarmHome, "token"))) {
|
|
10258
10440
|
console.error(
|
|
10259
|
-
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${
|
|
10441
|
+
`[launcher] TASKLESS_TOKEN \u0437\u0430\u0434\u0430\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0432 env \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0434\u0440\u043E\u043D\u0443 \u043E\u043D \u043D\u0435 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442\u0441\u044F (TASWM-289), \u0430 \u0444\u0430\u0439\u043B\u0430 ${join12(cfg.droneIsolation.swarmHome, "token")} \u043D\u0435\u0442. read_task \u0434\u0440\u043E\u043D\u0430 \u0431\u0443\u0434\u0435\u0442 slug-only. \u041F\u043E\u0447\u0438\u043D\u0438: \`swarm login\` (oauth.json) \u0438\u043B\u0438 \u043F\u043E\u043B\u043E\u0436\u0438 \u0442\u043E\u043A\u0435\u043D \u0432 \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B.`
|
|
10260
10442
|
);
|
|
10261
10443
|
}
|
|
10262
10444
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -10283,7 +10465,13 @@ function makeLauncher(cfg) {
|
|
|
10283
10465
|
let outFd;
|
|
10284
10466
|
let errFd;
|
|
10285
10467
|
if (cfg.runLogsDir) {
|
|
10286
|
-
const
|
|
10468
|
+
const logKeys = cfg.runLogKeys?.(run) ?? [];
|
|
10469
|
+
if (logKeys.length === 0) {
|
|
10470
|
+
throw new Error(
|
|
10471
|
+
`\u043F\u0440\u043E\u0433\u043E\u043D\u0443 #${run.id} (${run.task_slug}) \u043D\u0435\u0447\u0435\u043C \u0430\u0434\u0440\u0435\u0441\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 run-log'\u043E\u0432: \u0443 \u0441\u0442\u043E\u0440\u0430 \u043D\u0435\u0442 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u043B\u044E\u0447\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430. \u0414\u0440\u043E\u043D \u043D\u0435 \u0441\u043F\u0430\u0432\u043D\u0435\u043D \u2014 \u0438\u043D\u0430\u0447\u0435 \u0435\u0433\u043E \u043B\u043E\u0433 \u043B\u0451\u0433 \u0431\u044B \u0432 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0447\u0443\u0436\u043E\u0439 (TASWM-419).`
|
|
10472
|
+
);
|
|
10473
|
+
}
|
|
10474
|
+
const fds = openNextRunLog(cfg.runLogsDir, logKeys);
|
|
10287
10475
|
outFd = fds.out;
|
|
10288
10476
|
errFd = fds.err ?? void 0;
|
|
10289
10477
|
stdio = ["ignore", outFd, errFd ?? "ignore"];
|
|
@@ -10311,14 +10499,14 @@ function makeLauncher(cfg) {
|
|
|
10311
10499
|
init_droneChannel();
|
|
10312
10500
|
|
|
10313
10501
|
// src/git.ts
|
|
10314
|
-
import { existsSync as
|
|
10502
|
+
import { existsSync as existsSync9, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
|
|
10315
10503
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
10316
|
-
import { isAbsolute, join as
|
|
10504
|
+
import { isAbsolute, join as join13, sep as sep2 } from "path";
|
|
10317
10505
|
import { $ as $5 } from "execa";
|
|
10318
10506
|
init_swarmFiles();
|
|
10319
10507
|
function isLinkedWorktree(dir) {
|
|
10320
10508
|
try {
|
|
10321
|
-
return
|
|
10509
|
+
return statSync8(join13(dir, ".git")).isFile();
|
|
10322
10510
|
} catch {
|
|
10323
10511
|
return false;
|
|
10324
10512
|
}
|
|
@@ -10330,7 +10518,7 @@ async function detachWorktree(repo, worktree) {
|
|
|
10330
10518
|
}
|
|
10331
10519
|
}
|
|
10332
10520
|
async function ensureWorktreeDirGone(worktree) {
|
|
10333
|
-
if (!
|
|
10521
|
+
if (!existsSync9(worktree)) return false;
|
|
10334
10522
|
if (!isLinkedWorktree(worktree)) {
|
|
10335
10523
|
console.error(
|
|
10336
10524
|
`[git] ${worktree} \u2014 \u043D\u0435 \u043F\u0440\u0438\u0441\u0442\u0451\u0433\u043D\u0443\u0442\u044B\u0439 worktree (.git \u043D\u0435 \u0444\u0430\u0439\u043B): \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u041D\u0415 \u0442\u0440\u043E\u0433\u0430\u044E. \u0415\u0441\u043B\u0438 \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E worktree \u0434\u0440\u043E\u043D\u0430 \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0435\u0433\u043E \u0440\u0443\u043A\u0430\u043C\u0438; \u0435\u0441\u043B\u0438 \u044D\u0442\u043E \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0447\u0435\u043A\u0430\u0443\u0442, \u0437\u043D\u0430\u0447\u0438\u0442 \u0432\u0435\u0442\u043A\u0430 \u0441\u043B\u0430\u0433\u0430 \u0447\u0435\u043A\u043D\u0443\u0442\u0430 \u0432 \u043D\u0451\u043C (git checkout <slug>) \u2014 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0435\u0433\u043E \u043D\u0430 \u0434\u0440\u0443\u0433\u0443\u044E \u0432\u0435\u0442\u043A\u0443.`
|
|
@@ -10345,7 +10533,7 @@ async function ensureWorktreeDirGone(worktree) {
|
|
|
10345
10533
|
);
|
|
10346
10534
|
return true;
|
|
10347
10535
|
}
|
|
10348
|
-
if (
|
|
10536
|
+
if (existsSync9(worktree)) {
|
|
10349
10537
|
console.error(`[git] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree ${worktree} \u043F\u0435\u0440\u0435\u0436\u0438\u043B rm \u2014 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0443\u043A\u0430\u043C\u0438, \u0438\u043D\u0430\u0447\u0435 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0441\u043F\u0430\u0432\u043D \u0441\u043B\u0430\u0433\u0430 \u0432\u0441\u0442\u0430\u043D\u0435\u0442.`);
|
|
10350
10538
|
}
|
|
10351
10539
|
return true;
|
|
@@ -10373,7 +10561,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
10373
10561
|
|
|
10374
10562
|
// src/attachments.ts
|
|
10375
10563
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
10376
|
-
import { join as
|
|
10564
|
+
import { join as join14 } from "path";
|
|
10377
10565
|
init_swarmFiles();
|
|
10378
10566
|
var ATTACHMENTS_DIR = `${SWARM_TASK_DIR}/attachments`;
|
|
10379
10567
|
var ATTACH_MAX_TOTAL_BYTES = 24 * 1024 * 1024;
|
|
@@ -10385,7 +10573,7 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
|
|
|
10385
10573
|
|
|
10386
10574
|
// src/shotUpload.ts
|
|
10387
10575
|
import { readFile as fsReadFile } from "fs/promises";
|
|
10388
|
-
import { basename as basename3, isAbsolute as isAbsolute2, join as
|
|
10576
|
+
import { basename as basename3, isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
10389
10577
|
var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
10390
10578
|
|
|
10391
10579
|
// src/taskless.ts
|
|
@@ -10396,8 +10584,8 @@ init_linkFailure();
|
|
|
10396
10584
|
// src/workspace.ts
|
|
10397
10585
|
init_config();
|
|
10398
10586
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";
|
|
10399
|
-
import { join as
|
|
10400
|
-
var WORKSPACE_PATH =
|
|
10587
|
+
import { join as join16 } from "path";
|
|
10588
|
+
var WORKSPACE_PATH = join16(SWARM_HOME, "workspace.json");
|
|
10401
10589
|
function loadWorkspace() {
|
|
10402
10590
|
try {
|
|
10403
10591
|
const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
|
|
@@ -11288,7 +11476,7 @@ async function runDaemonLogs(argv) {
|
|
|
11288
11476
|
console.error(`\u043B\u043E\u0433 \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u044D\u0442\u043E \xABswarm log ${stray}\xBB (\u0431\u0435\u0437 s). \xABswarm logs\xBB \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430; \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A \u2014 \xABswarm logs -n 200\xBB.`);
|
|
11289
11477
|
return 1;
|
|
11290
11478
|
}
|
|
11291
|
-
if (!
|
|
11479
|
+
if (!existsSync16(DAEMON_LOG_PATH)) {
|
|
11292
11480
|
console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
|
|
11293
11481
|
console.error("\u043E\u043D \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0444\u043E\u043D\u043E\u0432\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 (swarm start). \u0414\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 (--foreground) \u2014 \u0432\u044B\u0432\u043E\u0434 \u0438\u0434\u0451\u0442 \u0442\u0443\u0434\u0430.");
|
|
11294
11482
|
return 1;
|
|
@@ -11296,12 +11484,12 @@ async function runDaemonLogs(argv) {
|
|
|
11296
11484
|
const head = tailFile(DAEMON_LOG_PATH, lines);
|
|
11297
11485
|
if (head) console.log(head);
|
|
11298
11486
|
if (!follow) return 0;
|
|
11299
|
-
let pos =
|
|
11487
|
+
let pos = statSync10(DAEMON_LOG_PATH).size;
|
|
11300
11488
|
for (; ; ) {
|
|
11301
11489
|
await sleep2(500);
|
|
11302
11490
|
let size;
|
|
11303
11491
|
try {
|
|
11304
|
-
size =
|
|
11492
|
+
size = statSync10(DAEMON_LOG_PATH).size;
|
|
11305
11493
|
} catch {
|
|
11306
11494
|
continue;
|
|
11307
11495
|
}
|
|
@@ -11560,10 +11748,10 @@ function effectiveWorktreesDir() {
|
|
|
11560
11748
|
legacyDir: WORKTREES_LEGACY_DIR,
|
|
11561
11749
|
targetDir: WORKTREES_DIR,
|
|
11562
11750
|
envOverride: process.env.SWARM_WORKTREES || "",
|
|
11563
|
-
exists: (d) =>
|
|
11751
|
+
exists: (d) => existsSync16(d),
|
|
11564
11752
|
isNonEmptyDir: (d) => {
|
|
11565
11753
|
try {
|
|
11566
|
-
return
|
|
11754
|
+
return readdirSync10(d).length > 0;
|
|
11567
11755
|
} catch {
|
|
11568
11756
|
return false;
|
|
11569
11757
|
}
|
|
@@ -11600,7 +11788,7 @@ async function runScoutCommand(repo) {
|
|
|
11600
11788
|
});
|
|
11601
11789
|
const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, autoFlush: false });
|
|
11602
11790
|
const name = pickName([]);
|
|
11603
|
-
const outFile =
|
|
11791
|
+
const outFile = join24(SWARM_HOME, `scout-${repoFullName.replace(/[^\w.-]+/g, "-")}.json`);
|
|
11604
11792
|
const bin = resolveExecutor().resolveBin();
|
|
11605
11793
|
const code = await runRepoScout({
|
|
11606
11794
|
repoFullName,
|
|
@@ -11620,7 +11808,7 @@ async function runScoutCommand(repo) {
|
|
|
11620
11808
|
});
|
|
11621
11809
|
child.on("exit", (c) => resolve4(c ?? 1));
|
|
11622
11810
|
}),
|
|
11623
|
-
readOut: () =>
|
|
11811
|
+
readOut: () => existsSync16(outFile) ? readFileSync17(outFile, "utf8") : null,
|
|
11624
11812
|
clearOut: () => rmSync2(outFile, { force: true }),
|
|
11625
11813
|
report: (report) => client.reportRepoScout(repoFullName, report, name),
|
|
11626
11814
|
log: (line) => console.log(line)
|
|
@@ -11696,7 +11884,7 @@ async function runZones(db2, repo, args) {
|
|
|
11696
11884
|
}
|
|
11697
11885
|
if (model.spine.length) {
|
|
11698
11886
|
console.log(`
|
|
11699
|
-
\u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${model.spine.length} \u0444\u0430\u0439\u043B\u043E\u0432) \u2014 \u0437\u0430\u0434\u0430\u0447\u0438 \
|
|
11887
|
+
\u043E\u0431\u0449\u0438\u0439 \u0445\u0440\u0435\u0431\u0435\u0442 (${model.spine.length} \u0444\u0430\u0439\u043B\u043E\u0432) \u2014 \u0434\u0432\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0432 \u041E\u0414\u041D\u041E\u041C \u0442\u0430\u043A\u043E\u043C \u0444\u0430\u0439\u043B\u0435 \u0438\u0434\u0443\u0442 \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E \u043E\u0442 \u0437\u043E\u043D\u044B:`);
|
|
11700
11888
|
for (const f of model.spine.slice(0, 15)) console.log(` ${f}`);
|
|
11701
11889
|
}
|
|
11702
11890
|
console.log("\n\u0441\u0432\u0435\u0440\u0438\u0442\u044C \u0441 \u0440\u0443\u0447\u043D\u043E\u0439 \u0440\u0430\u0437\u043C\u0435\u0442\u043A\u043E\u0439: swarm zones check");
|
|
@@ -12017,6 +12205,8 @@ switch (cmd) {
|
|
|
12017
12205
|
overmindEntry: OVERMIND_ENTRY,
|
|
12018
12206
|
dbPath: DB_PATH,
|
|
12019
12207
|
runLogsDir: RUN_LOGS_DIR,
|
|
12208
|
+
// TASWM-419: каталог лога — по стабильному ключу прогона (тот же адрес, что у демона).
|
|
12209
|
+
runLogKeys: (r) => db.runLogKeys(r.id),
|
|
12020
12210
|
// Тот же исполнитель, что у демона (TASWM-349): `swarm wake` при мёртвом демоне не
|
|
12021
12211
|
// должен внезапно поднимать дрона другим харнессом.
|
|
12022
12212
|
executor: EXECUTOR,
|