@taskless-app/swarm 0.6.38 → 0.6.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +364 -168
- package/dist/overmind.js +752 -278
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1366,7 +1366,7 @@ function buildZoneModel(entries, opts = {}) {
|
|
|
1366
1366
|
if (!m) labelVotes.set(z, m = /* @__PURE__ */ new Map());
|
|
1367
1367
|
bump(m, manual);
|
|
1368
1368
|
}
|
|
1369
|
-
const
|
|
1369
|
+
const rename2 = /* @__PURE__ */ new Map();
|
|
1370
1370
|
const takenNames = /* @__PURE__ */ new Set([SPINE_ZONE]);
|
|
1371
1371
|
const clustersByEvidence = [...labelVotes.entries()].sort((a, b) => {
|
|
1372
1372
|
const sum = (m) => [...m.values()].reduce((x, y) => x + y, 0);
|
|
@@ -1375,11 +1375,11 @@ function buildZoneModel(entries, opts = {}) {
|
|
|
1375
1375
|
for (const [zone, votes] of clustersByEvidence) {
|
|
1376
1376
|
const top = topKey(votes);
|
|
1377
1377
|
if (!top || top.share < 0.6 || takenNames.has(top.key) || top.key === zone) continue;
|
|
1378
|
-
|
|
1378
|
+
rename2.set(zone, top.key);
|
|
1379
1379
|
takenNames.add(top.key);
|
|
1380
1380
|
}
|
|
1381
|
-
const renamed = (z) =>
|
|
1382
|
-
if (
|
|
1381
|
+
const renamed = (z) => rename2.get(z) ?? z;
|
|
1382
|
+
if (rename2.size) {
|
|
1383
1383
|
for (const [f, z] of fileZone) fileZone.set(f, renamed(z));
|
|
1384
1384
|
for (const [s, z] of taskZone) taskZone.set(s, renamed(z));
|
|
1385
1385
|
}
|
|
@@ -2018,10 +2018,94 @@ var init_claudeResult = __esm({
|
|
|
2018
2018
|
}
|
|
2019
2019
|
});
|
|
2020
2020
|
|
|
2021
|
-
// src/
|
|
2022
|
-
import { readdirSync as readdirSync2, statSync
|
|
2021
|
+
// src/transcriptSpend.ts
|
|
2022
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync } from "fs";
|
|
2023
2023
|
import { homedir as homedir2 } from "os";
|
|
2024
2024
|
import { join as join2 } from "path";
|
|
2025
|
+
function transcriptDir(worktree, projectsDir) {
|
|
2026
|
+
return join2(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
2027
|
+
}
|
|
2028
|
+
function transcriptSpendInWindow(worktree, fromMs, toMs, opts = {}) {
|
|
2029
|
+
const projectsDir = opts.projectsDir ?? join2(homedir2(), ".claude", "projects");
|
|
2030
|
+
const empty = { costUsd: 0, tokensIn: 0, tokensOut: 0, durationMs: 0, replies: 0, unpriced: 0, unpricedModel: null };
|
|
2031
|
+
const dir = transcriptDir(worktree, projectsDir);
|
|
2032
|
+
let files;
|
|
2033
|
+
try {
|
|
2034
|
+
files = readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
2035
|
+
} catch {
|
|
2036
|
+
return empty;
|
|
2037
|
+
}
|
|
2038
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2039
|
+
let costUsd = 0;
|
|
2040
|
+
let tokensIn = 0;
|
|
2041
|
+
let tokensOut = 0;
|
|
2042
|
+
let firstAt = null;
|
|
2043
|
+
let lastAt = null;
|
|
2044
|
+
let replies = 0;
|
|
2045
|
+
let unpriced = 0;
|
|
2046
|
+
let unpricedModel = null;
|
|
2047
|
+
for (const f of files) {
|
|
2048
|
+
const path = join2(dir, f);
|
|
2049
|
+
let raw;
|
|
2050
|
+
try {
|
|
2051
|
+
if (statSync(path).mtimeMs < fromMs) continue;
|
|
2052
|
+
raw = readFileSync2(path, "utf8");
|
|
2053
|
+
} catch {
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
for (const line of raw.split("\n")) {
|
|
2057
|
+
if (!line.startsWith("{") || !line.includes('"usage"')) continue;
|
|
2058
|
+
let entry;
|
|
2059
|
+
try {
|
|
2060
|
+
entry = JSON.parse(line);
|
|
2061
|
+
} catch {
|
|
2062
|
+
continue;
|
|
2063
|
+
}
|
|
2064
|
+
if (entry.type !== "assistant") continue;
|
|
2065
|
+
const usage2 = entry.message?.usage;
|
|
2066
|
+
if (!usage2) continue;
|
|
2067
|
+
const at = entry.timestamp ? Date.parse(entry.timestamp) : NaN;
|
|
2068
|
+
if (!Number.isFinite(at) || at < fromMs || at >= toMs) continue;
|
|
2069
|
+
const key = entry.requestId ?? entry.message?.id ?? entry.uuid;
|
|
2070
|
+
if (!key || seen.has(key)) continue;
|
|
2071
|
+
seen.add(key);
|
|
2072
|
+
const model = entry.message?.model ?? null;
|
|
2073
|
+
const est = costFromUsage(usage2, model, opts.table);
|
|
2074
|
+
costUsd += est.costUsd;
|
|
2075
|
+
if (est.warning) {
|
|
2076
|
+
unpriced += 1;
|
|
2077
|
+
unpricedModel ??= model;
|
|
2078
|
+
}
|
|
2079
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
2080
|
+
tokensIn += num(usage2.input_tokens) + num(usage2.cache_creation_input_tokens) + num(usage2.cache_read_input_tokens);
|
|
2081
|
+
tokensOut += num(usage2.output_tokens);
|
|
2082
|
+
if (firstAt === null || at < firstAt) firstAt = at;
|
|
2083
|
+
if (lastAt === null || at > lastAt) lastAt = at;
|
|
2084
|
+
replies += 1;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
if (replies === 0) return empty;
|
|
2088
|
+
return {
|
|
2089
|
+
costUsd,
|
|
2090
|
+
tokensIn,
|
|
2091
|
+
tokensOut,
|
|
2092
|
+
durationMs: firstAt !== null && lastAt !== null ? lastAt - firstAt : 0,
|
|
2093
|
+
replies,
|
|
2094
|
+
unpriced,
|
|
2095
|
+
unpricedModel
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
var init_transcriptSpend = __esm({
|
|
2099
|
+
"src/transcriptSpend.ts"() {
|
|
2100
|
+
"use strict";
|
|
2101
|
+
init_pricing();
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2105
|
+
// src/executors/claudeCode.ts
|
|
2106
|
+
import { readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2107
|
+
import { homedir as homedir3 } from "os";
|
|
2108
|
+
import { join as join3 } from "path";
|
|
2025
2109
|
function claudeArgs(cfgPath, prompt, continueSession = false, model, pluginDirs = [], settings, maxBudgetUsd, effort) {
|
|
2026
2110
|
const pluginFlags = pluginDirs.flatMap((dir) => ["--plugin-dir", dir]);
|
|
2027
2111
|
const budgetFlags = typeof maxBudgetUsd === "number" && maxBudgetUsd > 0 ? ["--max-budget-usd", String(maxBudgetUsd)] : [];
|
|
@@ -2053,21 +2137,21 @@ function droneSettingsJson(paths, mergeGateHook = null) {
|
|
|
2053
2137
|
if (mergeGateHook) settings.hooks = { PreToolUse: [{ matcher: "Bash", hooks: [{ type: "command", command: mergeGateHook }] }] };
|
|
2054
2138
|
return JSON.stringify(settings);
|
|
2055
2139
|
}
|
|
2056
|
-
function
|
|
2057
|
-
return
|
|
2140
|
+
function transcriptDir2(worktree, projectsDir) {
|
|
2141
|
+
return join3(projectsDir, worktree.replace(/[/.]/g, "-"));
|
|
2058
2142
|
}
|
|
2059
|
-
function sessionTranscriptExists(worktree, projectsDir =
|
|
2143
|
+
function sessionTranscriptExists(worktree, projectsDir = join3(homedir3(), ".claude", "projects")) {
|
|
2060
2144
|
try {
|
|
2061
|
-
return
|
|
2145
|
+
return readdirSync3(transcriptDir2(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
|
|
2062
2146
|
} catch {
|
|
2063
2147
|
return false;
|
|
2064
2148
|
}
|
|
2065
2149
|
}
|
|
2066
|
-
function transcriptMtimeMs(worktree, projectsDir =
|
|
2067
|
-
const dir =
|
|
2150
|
+
function transcriptMtimeMs(worktree, projectsDir = join3(homedir3(), ".claude", "projects")) {
|
|
2151
|
+
const dir = transcriptDir2(worktree, projectsDir);
|
|
2068
2152
|
let files;
|
|
2069
2153
|
try {
|
|
2070
|
-
files =
|
|
2154
|
+
files = readdirSync3(dir);
|
|
2071
2155
|
} catch {
|
|
2072
2156
|
return null;
|
|
2073
2157
|
}
|
|
@@ -2075,7 +2159,7 @@ function transcriptMtimeMs(worktree, projectsDir = join2(homedir2(), ".claude",
|
|
|
2075
2159
|
for (const f of files) {
|
|
2076
2160
|
if (!f.endsWith(".jsonl")) continue;
|
|
2077
2161
|
try {
|
|
2078
|
-
const mtime2 =
|
|
2162
|
+
const mtime2 = statSync2(join3(dir, f)).mtimeMs;
|
|
2079
2163
|
if (latest === null || mtime2 > latest) latest = mtime2;
|
|
2080
2164
|
} catch {
|
|
2081
2165
|
}
|
|
@@ -2088,7 +2172,7 @@ function writeMcpConfig(spec) {
|
|
|
2088
2172
|
[spec.mcp.name]: { command: spec.mcp.command, args: spec.mcp.args, env: spec.mcp.env }
|
|
2089
2173
|
}
|
|
2090
2174
|
};
|
|
2091
|
-
const path =
|
|
2175
|
+
const path = join3(spec.worktree, MCP_CONFIG_FILE);
|
|
2092
2176
|
writeFileSync2(path, JSON.stringify(cfg, null, 2));
|
|
2093
2177
|
return path;
|
|
2094
2178
|
}
|
|
@@ -2103,6 +2187,7 @@ var init_claudeCode = __esm({
|
|
|
2103
2187
|
init_swarmFiles();
|
|
2104
2188
|
init_mergeGateHook();
|
|
2105
2189
|
init_claudeResult();
|
|
2190
|
+
init_transcriptSpend();
|
|
2106
2191
|
ISOLATION_FLAGS = ["--setting-sources", "project", "--strict-mcp-config"];
|
|
2107
2192
|
MCP_CONFIG_FILE = SWARM_MCP_CONFIG_FILE;
|
|
2108
2193
|
claudeCodeExecutor = {
|
|
@@ -2141,6 +2226,26 @@ var init_claudeCode = __esm({
|
|
|
2141
2226
|
...abortField(sessionAbort(result))
|
|
2142
2227
|
};
|
|
2143
2228
|
},
|
|
2229
|
+
// TASWM-548: итога нет — берём трату из транскрипта сессии. Тот пишется по ходу работы и
|
|
2230
|
+
// переживает SIGTERM разгрузки, из-за которого итог и не был напечатан; формат usage там
|
|
2231
|
+
// тот же, что в итоге, поэтому цена считается тем же прайсингом (см. transcriptSpend.ts).
|
|
2232
|
+
rescueSpend(worktree, fromMs, toMs) {
|
|
2233
|
+
const spend = transcriptSpendInWindow(worktree, fromMs, toMs);
|
|
2234
|
+
if (spend.replies === 0) return null;
|
|
2235
|
+
return {
|
|
2236
|
+
costUsd: spend.costUsd,
|
|
2237
|
+
tokensIn: spend.tokensIn,
|
|
2238
|
+
tokensOut: spend.tokensOut,
|
|
2239
|
+
durationMs: spend.durationMs,
|
|
2240
|
+
// Часть ответов не по чему было тарифицировать (модели нет в MODEL_PRICING) — их
|
|
2241
|
+
// токены в трату вошли, а деньги НЕТ. Тот же канал, что и у warning штатного итога
|
|
2242
|
+
// (cost.accountRunCost его печатает): иначе спасённые $0 при сожжённых токенах
|
|
2243
|
+
// выглядели бы как честный ноль.
|
|
2244
|
+
...spend.unpriced > 0 ? {
|
|
2245
|
+
warning: `\u0437\u0430\u043F\u0438\u0441\u044C \u0441\u0435\u0441\u0441\u0438\u0438: ${spend.unpriced} \u0438\u0437 ${spend.replies} \u043E\u0442\u0432\u0435\u0442\u043E\u0432 \u0431\u0435\u0437 \u0441\u0442\u0440\u043E\u043A\u0438 \u0432 \u043F\u0440\u0430\u0439\u0441\u0438\u043D\u0433\u0435 (\u043C\u043E\u0434\u0435\u043B\u044C "${spend.unpricedModel ?? ""}") \u2014 \u0438\u0445 \u0446\u0435\u043D\u0430 \u0432 \u0441\u043F\u0430\u0441\u0451\u043D\u043D\u0443\u044E \u0442\u0440\u0430\u0442\u0443 \u041D\u0415 \u0432\u043E\u0448\u043B\u0430`
|
|
2246
|
+
} : {}
|
|
2247
|
+
};
|
|
2248
|
+
},
|
|
2144
2249
|
session: {
|
|
2145
2250
|
exists: (worktree) => sessionTranscriptExists(worktree),
|
|
2146
2251
|
mtimeMs: (worktree) => transcriptMtimeMs(worktree)
|
|
@@ -2158,34 +2263,34 @@ var init_claudeCode = __esm({
|
|
|
2158
2263
|
});
|
|
2159
2264
|
|
|
2160
2265
|
// src/swarmHome.ts
|
|
2161
|
-
import { homedir as
|
|
2162
|
-
import { join as
|
|
2266
|
+
import { homedir as homedir4 } from "os";
|
|
2267
|
+
import { join as join4 } from "path";
|
|
2163
2268
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
2164
2269
|
var SWARM_HOME;
|
|
2165
2270
|
var init_swarmHome = __esm({
|
|
2166
2271
|
"src/swarmHome.ts"() {
|
|
2167
2272
|
"use strict";
|
|
2168
|
-
SWARM_HOME = process.env.SWARM_HOME ||
|
|
2273
|
+
SWARM_HOME = process.env.SWARM_HOME || join4(homedir4(), ".taskless-swarm");
|
|
2169
2274
|
mkdirSync2(SWARM_HOME, { recursive: true });
|
|
2170
2275
|
}
|
|
2171
2276
|
});
|
|
2172
2277
|
|
|
2173
2278
|
// src/executors/codex.ts
|
|
2174
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as
|
|
2175
|
-
import { join as
|
|
2279
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync4, statSync as statSync3, symlinkSync } from "fs";
|
|
2280
|
+
import { join as join5 } from "path";
|
|
2176
2281
|
function resolveCodexBin(env = process.env) {
|
|
2177
2282
|
return (env.SWARM_CODEX_BIN ?? "").trim() || "codex";
|
|
2178
2283
|
}
|
|
2179
2284
|
function codexHomePath(worktree, swarmHome = SWARM_HOME) {
|
|
2180
|
-
return
|
|
2285
|
+
return join5(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
|
|
2181
2286
|
}
|
|
2182
2287
|
function ensureCodexHome(worktree, realHome, env = process.env, swarmHome = SWARM_HOME) {
|
|
2183
2288
|
const home = codexHomePath(worktree, swarmHome);
|
|
2184
2289
|
mkdirSync3(home, { recursive: true });
|
|
2185
|
-
const real = (env.CODEX_HOME ?? "").trim() ||
|
|
2290
|
+
const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
|
|
2186
2291
|
for (const file of ["auth.json", "config.toml"]) {
|
|
2187
|
-
const target =
|
|
2188
|
-
const link =
|
|
2292
|
+
const target = join5(real, file);
|
|
2293
|
+
const link = join5(home, file);
|
|
2189
2294
|
if (!existsSync3(target) || existsSync3(link)) continue;
|
|
2190
2295
|
try {
|
|
2191
2296
|
symlinkSync(target, link);
|
|
@@ -2244,16 +2349,16 @@ function parseCodexStream(raw) {
|
|
|
2244
2349
|
function newestRolloutMtime(dir, depth = 4) {
|
|
2245
2350
|
let entries;
|
|
2246
2351
|
try {
|
|
2247
|
-
entries =
|
|
2352
|
+
entries = readdirSync4(dir);
|
|
2248
2353
|
} catch {
|
|
2249
2354
|
return null;
|
|
2250
2355
|
}
|
|
2251
2356
|
let latest = null;
|
|
2252
2357
|
for (const name of entries) {
|
|
2253
|
-
const full =
|
|
2358
|
+
const full = join5(dir, name);
|
|
2254
2359
|
let st;
|
|
2255
2360
|
try {
|
|
2256
|
-
st =
|
|
2361
|
+
st = statSync3(full);
|
|
2257
2362
|
} catch {
|
|
2258
2363
|
continue;
|
|
2259
2364
|
}
|
|
@@ -2324,8 +2429,8 @@ ${notice}` : spec.prompt;
|
|
|
2324
2429
|
return est.warning ? { costUsd: est.costUsd, warning: est.warning } : { costUsd: est.costUsd };
|
|
2325
2430
|
},
|
|
2326
2431
|
session: {
|
|
2327
|
-
exists: (worktree) => newestRolloutMtime(
|
|
2328
|
-
mtimeMs: (worktree) => newestRolloutMtime(
|
|
2432
|
+
exists: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions")) !== null,
|
|
2433
|
+
mtimeMs: (worktree) => newestRolloutMtime(join5(codexHomePath(worktree), "sessions"))
|
|
2329
2434
|
},
|
|
2330
2435
|
probe: {
|
|
2331
2436
|
// Тот же принцип, что у claude: дешёвый реальный прогон — он проверяет и auth, и живость
|
|
@@ -2372,8 +2477,8 @@ var init_executors = __esm({
|
|
|
2372
2477
|
});
|
|
2373
2478
|
|
|
2374
2479
|
// src/config.ts
|
|
2375
|
-
import { basename, join as
|
|
2376
|
-
import { readFileSync as
|
|
2480
|
+
import { basename, join as join6, resolve } from "path";
|
|
2481
|
+
import { readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2377
2482
|
function normalizeRepo(repo) {
|
|
2378
2483
|
const r = (repo ?? "").trim();
|
|
2379
2484
|
if (!r) return "";
|
|
@@ -2387,10 +2492,10 @@ function effectiveRepoOf(repo, fallback) {
|
|
|
2387
2492
|
return normalizeRepo(repo || fallback || "");
|
|
2388
2493
|
}
|
|
2389
2494
|
function droneWorktreePath(worktreesDir, repo, slug) {
|
|
2390
|
-
return
|
|
2495
|
+
return join6(droneWorktreeRoot(worktreesDir, repo), slug);
|
|
2391
2496
|
}
|
|
2392
2497
|
function droneWorktreeRoot(worktreesDir, repo) {
|
|
2393
|
-
return
|
|
2498
|
+
return join6(worktreesDir, basename(normalizeRepo(repo)));
|
|
2394
2499
|
}
|
|
2395
2500
|
function resolveEffectiveWorktreesDir(deps) {
|
|
2396
2501
|
if (deps.envOverride) return deps.envOverride;
|
|
@@ -2417,7 +2522,7 @@ function mcpEndpoint(api, path = TASKLESS_MCP_PATH) {
|
|
|
2417
2522
|
}
|
|
2418
2523
|
function tokenFromFile() {
|
|
2419
2524
|
try {
|
|
2420
|
-
return
|
|
2525
|
+
return readFileSync3(join6(SWARM_HOME, "token"), "utf8").trim();
|
|
2421
2526
|
} catch {
|
|
2422
2527
|
return "";
|
|
2423
2528
|
}
|
|
@@ -2430,25 +2535,25 @@ var init_config = __esm({
|
|
|
2430
2535
|
init_executors();
|
|
2431
2536
|
init_swarmHome();
|
|
2432
2537
|
init_swarmHome();
|
|
2433
|
-
DB_PATH = process.env.SWARM_DB ||
|
|
2538
|
+
DB_PATH = process.env.SWARM_DB || join6(SWARM_HOME, "swarm.db");
|
|
2434
2539
|
EXECUTOR = resolveExecutor(process.env.SWARM_EXECUTOR);
|
|
2435
|
-
DRONE_HOME = process.env.SWARM_DRONE_HOME ||
|
|
2540
|
+
DRONE_HOME = process.env.SWARM_DRONE_HOME || join6(SWARM_HOME, "drone-home");
|
|
2436
2541
|
DRONE_HOME_ISOLATION = process.env.SWARM_DRONE_ISOLATE_HOME !== "0" && process.platform !== "win32";
|
|
2437
2542
|
DRONE_ISOLATION = DRONE_HOME_ISOLATION ? { home: DRONE_HOME, swarmHome: SWARM_HOME } : void 0;
|
|
2438
|
-
WORKTREES_NOINDEX_DIR =
|
|
2439
|
-
WORKTREES_LEGACY_DIR =
|
|
2543
|
+
WORKTREES_NOINDEX_DIR = join6(SWARM_HOME, "worktrees.noindex");
|
|
2544
|
+
WORKTREES_LEGACY_DIR = join6(SWARM_HOME, "worktrees");
|
|
2440
2545
|
WORKTREES_DIR = process.env.SWARM_WORKTREES || WORKTREES_NOINDEX_DIR;
|
|
2441
|
-
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS ||
|
|
2442
|
-
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG ||
|
|
2546
|
+
RUN_LOGS_DIR = process.env.SWARM_RUN_LOGS || join6(SWARM_HOME, "run-logs");
|
|
2547
|
+
DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join6(SWARM_HOME, "overmind.log");
|
|
2443
2548
|
DAEMON_LOG_MAX_BYTES = envNum("SWARM_DAEMON_LOG_MAX_BYTES", 16 * 1024 * 1024);
|
|
2444
2549
|
DAEMON_LOG_KEEP = envNum("SWARM_DAEMON_LOG_KEEP", 2);
|
|
2445
2550
|
DAEMON_LOG_CHECK_MS = envNum("SWARM_DAEMON_LOG_CHECK_MS", 6e4);
|
|
2446
2551
|
DIST_BUILD = true;
|
|
2447
2552
|
SWARM_BACKEND = process.env.SWARM_BACKEND === "client" ? "client" : process.env.SWARM_BACKEND === "sqlite" ? "sqlite" : DIST_BUILD ? "client" : "sqlite";
|
|
2448
|
-
SWARM_OUTBOX = process.env.SWARM_OUTBOX ||
|
|
2553
|
+
SWARM_OUTBOX = process.env.SWARM_OUTBOX || join6(SWARM_HOME, "outbox.jsonl");
|
|
2449
2554
|
SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
|
|
2450
2555
|
SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
|
|
2451
|
-
ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR ||
|
|
2556
|
+
ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR || join6(SWARM_HOME, "env-cache");
|
|
2452
2557
|
ENV_CACHE_ENABLED = process.env.SWARM_ENV_CACHE !== "0";
|
|
2453
2558
|
ENV_CACHE_KEEP = Math.trunc(envNum("SWARM_ENV_CACHE_KEEP", 4));
|
|
2454
2559
|
SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
|
|
@@ -2478,15 +2583,15 @@ var init_config = __esm({
|
|
|
2478
2583
|
DRONE_CHANNEL_ENABLED = process.env.SWARM_DRONE_CHANNEL !== "0";
|
|
2479
2584
|
DRONE_CHANNEL_PORT = Number(process.env.SWARM_DRONE_CHANNEL_PORT) || 7421;
|
|
2480
2585
|
DRONE_CHANNEL_URL = process.env.SWARM_DRONE_CHANNEL_URL || `http://127.0.0.1:${DRONE_CHANNEL_PORT}`;
|
|
2481
|
-
DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET ||
|
|
2586
|
+
DRONE_CHANNEL_SECRET_PATH = process.env.SWARM_DRONE_CHANNEL_SECRET || join6(SWARM_HOME, "drone-channel.key");
|
|
2482
2587
|
DRONE_CHANNEL_GRACE_MS = envNum("SWARM_DRONE_CHANNEL_GRACE_MS", 12e4);
|
|
2483
|
-
HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT ||
|
|
2588
|
+
HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join6(SWARM_HOME, "overmind.heartbeat");
|
|
2484
2589
|
HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
|
|
2485
2590
|
SWARM_REPO = process.env.SWARM_REPO || "";
|
|
2486
2591
|
SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
|
|
2487
2592
|
SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2488
2593
|
SWARM_SCAN_ROOTS_RAW = process.env.SWARM_SCAN_ROOTS || "";
|
|
2489
|
-
REPO_MAP_PATH = process.env.SWARM_REPO_MAP ||
|
|
2594
|
+
REPO_MAP_PATH = process.env.SWARM_REPO_MAP || join6(SWARM_HOME, "repo-map.json");
|
|
2490
2595
|
SWARM_REPO_MAP_MS = envNum("SWARM_REPO_MAP_MS", 15 * 6e4);
|
|
2491
2596
|
SWARM_REPO_SCAN_TTL_MS = envNum("SWARM_REPO_SCAN_TTL_MS", 24 * 60 * 6e4);
|
|
2492
2597
|
TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
|
|
@@ -2501,7 +2606,7 @@ var init_config = __esm({
|
|
|
2501
2606
|
SWARM_AUTO_REVIEW = process.env.SWARM_AUTO_REVIEW !== "0";
|
|
2502
2607
|
SWARM_AUTO_VERIFY = process.env.SWARM_AUTO_VERIFY !== "0";
|
|
2503
2608
|
SWARM_MERGE_QUEUE = process.env.SWARM_MERGE_QUEUE !== "0";
|
|
2504
|
-
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF ||
|
|
2609
|
+
REVIEW_CUTOFF_PATH = process.env.SWARM_REVIEW_CUTOFF || join6(SWARM_HOME, "review-cutoff.json");
|
|
2505
2610
|
SWARM_REVIEW_CAP = Math.trunc(envNum("SWARM_REVIEW_CAP", 1));
|
|
2506
2611
|
SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
|
|
2507
2612
|
WEB_PORT = Number(process.env.SWARM_WEB_PORT) || 7420;
|
|
@@ -2541,7 +2646,7 @@ var init_quietExec = __esm({
|
|
|
2541
2646
|
|
|
2542
2647
|
// src/resources.ts
|
|
2543
2648
|
import os from "os";
|
|
2544
|
-
import { readFileSync as
|
|
2649
|
+
import { readFileSync as readFileSync4, statfsSync } from "fs";
|
|
2545
2650
|
function isTransientResourceError(e) {
|
|
2546
2651
|
const code = e?.code;
|
|
2547
2652
|
return code === "ENFILE" || code === "EMFILE" || code === "EAGAIN";
|
|
@@ -2593,7 +2698,7 @@ function fileDescriptorLoadRatio() {
|
|
|
2593
2698
|
}
|
|
2594
2699
|
if (process.platform === "linux") {
|
|
2595
2700
|
try {
|
|
2596
|
-
const [allocated, , max] =
|
|
2701
|
+
const [allocated, , max] = readFileSync4("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
|
|
2597
2702
|
if (Number.isFinite(allocated ?? NaN) && Number.isFinite(max ?? NaN) && max) return allocated / max;
|
|
2598
2703
|
} catch {
|
|
2599
2704
|
}
|
|
@@ -2817,11 +2922,11 @@ var init_zoneNotes = __esm({
|
|
|
2817
2922
|
});
|
|
2818
2923
|
|
|
2819
2924
|
// src/agentToken.ts
|
|
2820
|
-
import { readFileSync as
|
|
2821
|
-
import { join as
|
|
2925
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync } from "fs";
|
|
2926
|
+
import { join as join7 } from "path";
|
|
2822
2927
|
function loadAgentToken() {
|
|
2823
2928
|
try {
|
|
2824
|
-
const s = JSON.parse(
|
|
2929
|
+
const s = JSON.parse(readFileSync5(STORE_PATH, "utf8"));
|
|
2825
2930
|
return s.token ? s : null;
|
|
2826
2931
|
} catch {
|
|
2827
2932
|
return null;
|
|
@@ -2899,7 +3004,7 @@ var init_agentToken = __esm({
|
|
|
2899
3004
|
"src/agentToken.ts"() {
|
|
2900
3005
|
"use strict";
|
|
2901
3006
|
init_config();
|
|
2902
|
-
STORE_PATH =
|
|
3007
|
+
STORE_PATH = join7(SWARM_HOME, "agent.json");
|
|
2903
3008
|
base = (api) => api.replace(/\/$/, "");
|
|
2904
3009
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2905
3010
|
}
|
|
@@ -3086,7 +3191,7 @@ var init_procList = __esm({
|
|
|
3086
3191
|
|
|
3087
3192
|
// src/procCleanup.ts
|
|
3088
3193
|
function sleep(ms) {
|
|
3089
|
-
return new Promise((
|
|
3194
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3090
3195
|
}
|
|
3091
3196
|
function parseLsofCwd(output) {
|
|
3092
3197
|
const rows = [];
|
|
@@ -3285,7 +3390,7 @@ var init_package = __esm({
|
|
|
3285
3390
|
"package.json"() {
|
|
3286
3391
|
package_default = {
|
|
3287
3392
|
name: "@taskless-app/swarm",
|
|
3288
|
-
version: "0.6.
|
|
3393
|
+
version: "0.6.40",
|
|
3289
3394
|
type: "module",
|
|
3290
3395
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
3291
3396
|
keywords: [
|
|
@@ -3336,7 +3441,7 @@ var init_package = __esm({
|
|
|
3336
3441
|
execa: "^10.0.0",
|
|
3337
3442
|
zod: "3.25.76"
|
|
3338
3443
|
},
|
|
3339
|
-
gitHead: "
|
|
3444
|
+
gitHead: "b58ef18678e8c0e03e0602a40bf76d6f0a6975c8"
|
|
3340
3445
|
};
|
|
3341
3446
|
}
|
|
3342
3447
|
});
|
|
@@ -3502,12 +3607,12 @@ var init_netTimeout = __esm({
|
|
|
3502
3607
|
|
|
3503
3608
|
// src/droneChannel.ts
|
|
3504
3609
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3505
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as
|
|
3610
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
3506
3611
|
import { dirname as dirname3 } from "path";
|
|
3507
3612
|
import { serve } from "@hono/node-server";
|
|
3508
3613
|
function loadChannelSecret(path) {
|
|
3509
3614
|
try {
|
|
3510
|
-
const s =
|
|
3615
|
+
const s = readFileSync9(path, "utf8").trim();
|
|
3511
3616
|
if (s.length >= 32) return s;
|
|
3512
3617
|
} catch {
|
|
3513
3618
|
}
|
|
@@ -3590,8 +3695,8 @@ var init_authState = __esm({
|
|
|
3590
3695
|
|
|
3591
3696
|
// src/oauth.ts
|
|
3592
3697
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
3593
|
-
import { readFileSync as
|
|
3594
|
-
import { join as
|
|
3698
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
3699
|
+
import { join as join12 } from "path";
|
|
3595
3700
|
function makePkce() {
|
|
3596
3701
|
const verifier = randomBytes2(48).toString("base64url");
|
|
3597
3702
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -3675,7 +3780,7 @@ function toStore(clientId, t, now) {
|
|
|
3675
3780
|
}
|
|
3676
3781
|
function loadStore() {
|
|
3677
3782
|
try {
|
|
3678
|
-
return JSON.parse(
|
|
3783
|
+
return JSON.parse(readFileSync10(STORE_PATH2, "utf8"));
|
|
3679
3784
|
} catch {
|
|
3680
3785
|
return null;
|
|
3681
3786
|
}
|
|
@@ -3691,7 +3796,7 @@ function clearStore() {
|
|
|
3691
3796
|
}
|
|
3692
3797
|
function loadClientId() {
|
|
3693
3798
|
try {
|
|
3694
|
-
return JSON.parse(
|
|
3799
|
+
return JSON.parse(readFileSync10(CLIENT_PATH, "utf8")).clientId;
|
|
3695
3800
|
} catch {
|
|
3696
3801
|
return null;
|
|
3697
3802
|
}
|
|
@@ -3726,16 +3831,16 @@ var init_oauth = __esm({
|
|
|
3726
3831
|
AUTHORIZE = "/api/oauth/authorize";
|
|
3727
3832
|
TOKEN = "/api/oauth/token";
|
|
3728
3833
|
SCOPE = "openid profile email taskless:full";
|
|
3729
|
-
STORE_PATH2 =
|
|
3730
|
-
CLIENT_PATH =
|
|
3834
|
+
STORE_PATH2 = join12(SWARM_HOME, "oauth.json");
|
|
3835
|
+
CLIENT_PATH = join12(SWARM_HOME, "oauth-client.json");
|
|
3731
3836
|
base2 = (api) => api.replace(/\/$/, "");
|
|
3732
3837
|
}
|
|
3733
3838
|
});
|
|
3734
3839
|
|
|
3735
3840
|
// src/git.ts
|
|
3736
|
-
import { existsSync as existsSync10, readdirSync as
|
|
3737
|
-
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3738
|
-
import { isAbsolute, join as
|
|
3841
|
+
import { existsSync as existsSync10, readdirSync as readdirSync9, realpathSync as realpathSync2, statSync as statSync9 } from "fs";
|
|
3842
|
+
import { appendFile, chmod, mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
3843
|
+
import { isAbsolute, join as join15, resolve as resolve2, sep as sep2 } from "path";
|
|
3739
3844
|
import { $ as $5 } from "execa";
|
|
3740
3845
|
function gitNet(repo, args, timeoutMs) {
|
|
3741
3846
|
return withHardTimeout(
|
|
@@ -3748,9 +3853,75 @@ function gitNet(repo, args, timeoutMs) {
|
|
|
3748
3853
|
async function reclaimStaleWorktree(repo, worktree) {
|
|
3749
3854
|
await detachWorktree(repo, worktree);
|
|
3750
3855
|
}
|
|
3856
|
+
async function inspectWorktreePath(path) {
|
|
3857
|
+
if (!existsSync10(path)) return "free";
|
|
3858
|
+
try {
|
|
3859
|
+
if (readdirSync9(path).length === 0) return "free";
|
|
3860
|
+
} catch {
|
|
3861
|
+
return "orphan";
|
|
3862
|
+
}
|
|
3863
|
+
try {
|
|
3864
|
+
if (statSync9(join15(path, ".git")).isDirectory()) return "main-checkout";
|
|
3865
|
+
} catch {
|
|
3866
|
+
}
|
|
3867
|
+
const top = await $5({ reject: false })`git -C ${path} rev-parse --show-toplevel`;
|
|
3868
|
+
return top.exitCode === 0 && samePath(top.stdout.trim(), path) ? "worktree" : "orphan";
|
|
3869
|
+
}
|
|
3870
|
+
function samePath(a, b) {
|
|
3871
|
+
const norm = (p) => {
|
|
3872
|
+
try {
|
|
3873
|
+
return realpathSync2(p);
|
|
3874
|
+
} catch {
|
|
3875
|
+
return resolve2(p);
|
|
3876
|
+
}
|
|
3877
|
+
};
|
|
3878
|
+
const [x, y] = [norm(a), norm(b)];
|
|
3879
|
+
return process.platform === "win32" ? posix(x).toLowerCase() === posix(y).toLowerCase() : x === y;
|
|
3880
|
+
}
|
|
3881
|
+
async function clearWorktreePath(repo, path, branch, opts = {}) {
|
|
3882
|
+
const holder = await inspectWorktreePath(path);
|
|
3883
|
+
if (holder === "free") return;
|
|
3884
|
+
if (holder === "main-checkout") {
|
|
3885
|
+
throw new WorktreeBusyError(
|
|
3886
|
+
branch,
|
|
3887
|
+
path,
|
|
3888
|
+
`\u043F\u043E \u043F\u0443\u0442\u0438 worktree ${path} \u043B\u0435\u0436\u0438\u0442 \u041E\u0421\u041D\u041E\u0412\u041D\u041E\u0419 \u0447\u0435\u043A\u0430\u0443\u0442 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (\`.git\` \u2014 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u0430 \u043D\u0435 \u0444\u0430\u0439\u043B) \u2014 \u044F \u0435\u0433\u043E \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E. \u041F\u043E\u0445\u043E\u0436\u0435, \u043A\u0430\u0442\u0430\u043B\u043E\u0433 worktree'\u0435\u0432 \u0440\u043E\u044F \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D \u043D\u0430 \u0440\u0430\u0431\u043E\u0447\u0443\u044E \u043A\u043E\u043F\u0438\u044E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430; \u043F\u043E\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u043F\u0443\u0442\u044C, \u0441\u043F\u0430\u0432\u043D \u0442\u0443\u0434\u0430 \u0437\u0430\u0441\u0435\u043B\u044F\u0442\u044C\u0441\u044F \u043D\u0435 \u0431\u0443\u0434\u0435\u0442.`
|
|
3889
|
+
);
|
|
3890
|
+
}
|
|
3891
|
+
if (holder === "worktree") {
|
|
3892
|
+
if (opts.isWorktreeHeldLive ? await opts.isWorktreeHeldLive(path) : true) throw new WorktreeBusyError(branch, path);
|
|
3893
|
+
if (await worktreeDirty(path)) {
|
|
3894
|
+
throw new WorktreeBusyError(
|
|
3895
|
+
branch,
|
|
3896
|
+
path,
|
|
3897
|
+
`worktree ${path} \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u043B, \u043D\u043E \u0432\u043D\u0443\u0442\u0440\u0438 \u0435\u0441\u0442\u044C \u043D\u0435\u0441\u0434\u0430\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 (\u043D\u0435\u0437\u0430\u043A\u043E\u043C\u043C\u0438\u0447\u0435\u043D\u043D\u043E\u0435 \u0438\u043B\u0438 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435) \u2014 \u043D\u0435 \u0441\u043D\u043E\u0448\u0443. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438\u0442\u0435 \u0441\u0430\u043C\u0438 (\`git -C ${path} status --untracked-files=all\`) \u0438 \u0443\u0431\u0435\u0440\u0438\u0442\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u043A\u043E\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u043D\u0435 \u0436\u0430\u043B\u043A\u043E.`
|
|
3898
|
+
);
|
|
3899
|
+
}
|
|
3900
|
+
await detachWorktree(repo, path);
|
|
3901
|
+
opts.onStaleReclaim?.(path);
|
|
3902
|
+
}
|
|
3903
|
+
if (!existsSync10(path)) return;
|
|
3904
|
+
await parkOrphanDir(repo, path, branch);
|
|
3905
|
+
}
|
|
3906
|
+
async function parkOrphanDir(repo, path, branch) {
|
|
3907
|
+
const parked = `${path}.orphan-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
3908
|
+
try {
|
|
3909
|
+
await rename(path, parked);
|
|
3910
|
+
} catch (e) {
|
|
3911
|
+
throw new WorktreeBusyError(
|
|
3912
|
+
branch,
|
|
3913
|
+
path,
|
|
3914
|
+
`\u043F\u0443\u0442\u044C ${path} \u0437\u0430\u043D\u044F\u0442 \u043F\u043E\u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0438\u043C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u043E\u043C, \u0438 \u043E\u0442\u043E\u0434\u0432\u0438\u043D\u0443\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${String(e)}) \u2014 \u0441\u043F\u0430\u0432\u043D ${branch} \u0442\u0443\u0434\u0430 \u043D\u0435 \u0432\u0441\u0442\u0430\u043D\u0435\u0442. \u0423\u0431\u0435\u0440\u0438\u0442\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0440\u0443\u043A\u0430\u043C\u0438 \u0438 \u0441\u0434\u0435\u043B\u0430\u0439\u0442\u0435 \`git -C ${repo} worktree prune\`.`
|
|
3915
|
+
);
|
|
3916
|
+
}
|
|
3917
|
+
console.error(
|
|
3918
|
+
`[git] \u043F\u043E \u043F\u0443\u0442\u0438 worktree ${path} \u043B\u0435\u0436\u0430\u043B \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 git \u0441\u0432\u043E\u0438\u043C \u0440\u0430\u0431\u043E\u0447\u0438\u043C \u0434\u0435\u0440\u0435\u0432\u043E\u043C \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442 \u2014 \u043E\u0442\u043E\u0434\u0432\u0438\u043D\u0443\u043B \u0432 ${parked}, \u0447\u0442\u043E\u0431\u044B \u0441\u043F\u0430\u0432\u043D ${branch} \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F. \u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438\u0442\u0435, \u0447\u0442\u043E \u0432\u043D\u0443\u0442\u0440\u0438, \u0438 \u0443\u0434\u0430\u043B\u0438\u0442\u0435, \u0435\u0441\u043B\u0438 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`
|
|
3919
|
+
);
|
|
3920
|
+
await $5({ reject: false })`git -C ${repo} worktree prune`;
|
|
3921
|
+
}
|
|
3751
3922
|
function isLinkedWorktree(dir) {
|
|
3752
3923
|
try {
|
|
3753
|
-
return
|
|
3924
|
+
return statSync9(join15(dir, ".git")).isFile();
|
|
3754
3925
|
} catch {
|
|
3755
3926
|
return false;
|
|
3756
3927
|
}
|
|
@@ -3820,6 +3991,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3820
3991
|
const originSha = await revParse(repo, `refs/remotes/origin/${branch}`);
|
|
3821
3992
|
if (!localSha && !originSha) {
|
|
3822
3993
|
const freshBase = opts.base ?? await freshWorktreeBase(repo);
|
|
3994
|
+
await clearWorktreePath(repo, path, branch, opts);
|
|
3823
3995
|
await $5`git -C ${repo} worktree add -b ${branch} ${path} ${freshBase}`;
|
|
3824
3996
|
return { reused: false };
|
|
3825
3997
|
}
|
|
@@ -3832,6 +4004,7 @@ async function checkoutWorktree(repo, path, branch, opts) {
|
|
|
3832
4004
|
await reclaimStaleWorktree(repo, busyAt);
|
|
3833
4005
|
opts.onStaleReclaim?.(busyAt);
|
|
3834
4006
|
}
|
|
4007
|
+
await clearWorktreePath(repo, path, branch, opts);
|
|
3835
4008
|
if (originSha) {
|
|
3836
4009
|
await $5`git -C ${repo} worktree add -B ${branch} ${path} ${`origin/${branch}`}`;
|
|
3837
4010
|
return { reused: true, from: "origin", sha: originSha };
|
|
@@ -3855,8 +4028,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3855
4028
|
try {
|
|
3856
4029
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3857
4030
|
if (!commonDir) return false;
|
|
3858
|
-
const infoDir =
|
|
3859
|
-
const excludeFile =
|
|
4031
|
+
const infoDir = join15(commonDir, "info");
|
|
4032
|
+
const excludeFile = join15(infoDir, "exclude");
|
|
3860
4033
|
let current = "";
|
|
3861
4034
|
try {
|
|
3862
4035
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3876,7 +4049,7 @@ async function gitPath(worktree, flag) {
|
|
|
3876
4049
|
const out = await $5({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3877
4050
|
const raw = out.stdout.trim();
|
|
3878
4051
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3879
|
-
return isAbsolute(raw) ? raw :
|
|
4052
|
+
return isAbsolute(raw) ? raw : join15(worktree, raw);
|
|
3880
4053
|
}
|
|
3881
4054
|
async function ensureWorktreeHygiene(worktree) {
|
|
3882
4055
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3886,12 +4059,12 @@ async function installGuardHooks(worktree) {
|
|
|
3886
4059
|
try {
|
|
3887
4060
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3888
4061
|
if (!gitDir) return false;
|
|
3889
|
-
const hooksDir =
|
|
4062
|
+
const hooksDir = join15(gitDir, "swarm-hooks");
|
|
3890
4063
|
const prev = await previousHooksPath(worktree);
|
|
3891
4064
|
await mkdir(hooksDir, { recursive: true });
|
|
3892
4065
|
for (const hook of GUARD_HOOKS) {
|
|
3893
|
-
await writeFile(
|
|
3894
|
-
await chmod(
|
|
4066
|
+
await writeFile(join15(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
4067
|
+
await chmod(join15(hooksDir, hook), 493);
|
|
3895
4068
|
}
|
|
3896
4069
|
await $5`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3897
4070
|
await $5`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3910,7 +4083,7 @@ async function previousHooksPath(worktree) {
|
|
|
3910
4083
|
if (out.exitCode === 0 && value) return value;
|
|
3911
4084
|
}
|
|
3912
4085
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3913
|
-
return commonDir ?
|
|
4086
|
+
return commonDir ? join15(commonDir, "hooks") : null;
|
|
3914
4087
|
}
|
|
3915
4088
|
async function removeWorktree(repo, path) {
|
|
3916
4089
|
const branch = (await $5({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3952,8 +4125,14 @@ var init_git = __esm({
|
|
|
3952
4125
|
init_netTimeout();
|
|
3953
4126
|
init_swarmFiles();
|
|
3954
4127
|
WorktreeBusyError = class extends Error {
|
|
3955
|
-
|
|
3956
|
-
|
|
4128
|
+
/**
|
|
4129
|
+
* `why` (TASWM-553) — ПОЧЕМУ путь не отдали, если причина не «его держит живой прогон»:
|
|
4130
|
+
* основной чекаут по пути worktree, каталог с несданной работой, каталог, который не
|
|
4131
|
+
* получилось убрать. Дефолтная формулировка («прибери worktree или отмени прогон»)
|
|
4132
|
+
* человеку в таких случаях не помогает — она отправляет его не туда.
|
|
4133
|
+
*/
|
|
4134
|
+
constructor(branch, worktreePath, why) {
|
|
4135
|
+
super(why ?? `worktree \u0437\u0430\u043D\u044F\u0442: ${worktreePath} (\u0432\u0435\u0442\u043A\u0430 ${branch}) \u2014 \u043F\u0440\u0438\u0431\u0435\u0440\u0438 worktree \u0438\u043B\u0438 \u043E\u0442\u043C\u0435\u043D\u0438 \u043F\u0440\u043E\u0433\u043E\u043D (swarm cancel ${branch})`);
|
|
3957
4136
|
this.branch = branch;
|
|
3958
4137
|
this.worktreePath = worktreePath;
|
|
3959
4138
|
this.name = "WorktreeBusyError";
|
|
@@ -3967,7 +4146,7 @@ var init_git = __esm({
|
|
|
3967
4146
|
|
|
3968
4147
|
// src/attachments.ts
|
|
3969
4148
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3970
|
-
import { join as
|
|
4149
|
+
import { join as join16 } from "path";
|
|
3971
4150
|
var ATTACHMENTS_DIR, ATTACH_MAX_TOTAL_BYTES, ATTACH_MAX_FILE_BYTES, IMAGE_EXT, DOC_EXT, TEXT_EXT, KNOWN_EXT;
|
|
3972
4151
|
var init_attachments = __esm({
|
|
3973
4152
|
"src/attachments.ts"() {
|
|
@@ -3986,7 +4165,7 @@ var init_attachments = __esm({
|
|
|
3986
4165
|
|
|
3987
4166
|
// src/shotUpload.ts
|
|
3988
4167
|
import { readFile as fsReadFile } from "fs/promises";
|
|
3989
|
-
import { basename as basename5, isAbsolute as isAbsolute2, join as
|
|
4168
|
+
import { basename as basename5, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
3990
4169
|
var SHOT_MAX_FILE_BYTES;
|
|
3991
4170
|
var init_shotUpload = __esm({
|
|
3992
4171
|
"src/shotUpload.ts"() {
|
|
@@ -3996,11 +4175,11 @@ var init_shotUpload = __esm({
|
|
|
3996
4175
|
});
|
|
3997
4176
|
|
|
3998
4177
|
// src/workspace.ts
|
|
3999
|
-
import { readFileSync as
|
|
4000
|
-
import { join as
|
|
4178
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
|
|
4179
|
+
import { join as join18 } from "path";
|
|
4001
4180
|
function loadWorkspace() {
|
|
4002
4181
|
try {
|
|
4003
|
-
const w = JSON.parse(
|
|
4182
|
+
const w = JSON.parse(readFileSync11(WORKSPACE_PATH, "utf8"));
|
|
4004
4183
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
4005
4184
|
} catch {
|
|
4006
4185
|
return null;
|
|
@@ -4023,7 +4202,7 @@ var init_workspace = __esm({
|
|
|
4023
4202
|
"src/workspace.ts"() {
|
|
4024
4203
|
"use strict";
|
|
4025
4204
|
init_config();
|
|
4026
|
-
WORKSPACE_PATH =
|
|
4205
|
+
WORKSPACE_PATH = join18(SWARM_HOME, "workspace.json");
|
|
4027
4206
|
}
|
|
4028
4207
|
});
|
|
4029
4208
|
|
|
@@ -4248,14 +4427,14 @@ function createNetworkSettleTracker(target) {
|
|
|
4248
4427
|
});
|
|
4249
4428
|
target.on("requestfinished", onDone);
|
|
4250
4429
|
target.on("requestfailed", onDone);
|
|
4251
|
-
const settleOnce = (quietMs, budgetMs) => new Promise((
|
|
4430
|
+
const settleOnce = (quietMs, budgetMs) => new Promise((resolve5) => {
|
|
4252
4431
|
let quietTimer;
|
|
4253
4432
|
const hardTimer = setTimeout(finish, Math.max(0, budgetMs));
|
|
4254
4433
|
function finish() {
|
|
4255
4434
|
clearTimeout(quietTimer);
|
|
4256
4435
|
clearTimeout(hardTimer);
|
|
4257
4436
|
onChange.delete(check);
|
|
4258
|
-
|
|
4437
|
+
resolve5();
|
|
4259
4438
|
}
|
|
4260
4439
|
function check() {
|
|
4261
4440
|
clearTimeout(quietTimer);
|
|
@@ -4441,14 +4620,14 @@ var init_daemonLink = __esm({
|
|
|
4441
4620
|
});
|
|
4442
4621
|
|
|
4443
4622
|
// src/pauseState.ts
|
|
4444
|
-
import { existsSync as existsSync11, readFileSync as
|
|
4445
|
-
import { join as
|
|
4623
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
4624
|
+
import { join as join19 } from "path";
|
|
4446
4625
|
function pauseStatePath(home) {
|
|
4447
|
-
return
|
|
4626
|
+
return join19(home, "pause.json");
|
|
4448
4627
|
}
|
|
4449
4628
|
function loadPauseState(home, io = {}) {
|
|
4450
4629
|
const exists = io.exists ?? existsSync11;
|
|
4451
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
4630
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
4452
4631
|
const path = pauseStatePath(home);
|
|
4453
4632
|
if (!exists(path)) return ACTIVE;
|
|
4454
4633
|
try {
|
|
@@ -4509,10 +4688,10 @@ var init_pauseState = __esm({
|
|
|
4509
4688
|
});
|
|
4510
4689
|
|
|
4511
4690
|
// src/heartbeat.ts
|
|
4512
|
-
import { readFileSync as
|
|
4691
|
+
import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
4513
4692
|
function readHeartbeat(path) {
|
|
4514
4693
|
try {
|
|
4515
|
-
const ms = Number(
|
|
4694
|
+
const ms = Number(readFileSync13(path, "utf8").trim());
|
|
4516
4695
|
return Number.isFinite(ms) ? ms : null;
|
|
4517
4696
|
} catch {
|
|
4518
4697
|
return null;
|
|
@@ -4532,7 +4711,7 @@ __export(db_exports, {
|
|
|
4532
4711
|
import { createRequire as createRequire2 } from "module";
|
|
4533
4712
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4534
4713
|
import { existsSync as existsSync12 } from "fs";
|
|
4535
|
-
import { resolve as
|
|
4714
|
+
import { resolve as resolve3 } from "path";
|
|
4536
4715
|
function rowToQuestion(row) {
|
|
4537
4716
|
return {
|
|
4538
4717
|
id: row.id,
|
|
@@ -4574,6 +4753,7 @@ function rowToRun(row) {
|
|
|
4574
4753
|
tokens_in: row.tokens_in ?? 0,
|
|
4575
4754
|
tokens_out: row.tokens_out ?? 0,
|
|
4576
4755
|
duration_ms: row.duration_ms ?? 0,
|
|
4756
|
+
cost_unmeasured: row.cost_unmeasured ?? 0,
|
|
4577
4757
|
pr_url: row.pr_url ?? null,
|
|
4578
4758
|
// TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
|
|
4579
4759
|
changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
|
|
@@ -4654,6 +4834,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4654
4834
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
4655
4835
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
4656
4836
|
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
4837
|
+
cost_unmeasured INTEGER NOT NULL DEFAULT 0,
|
|
4657
4838
|
pr_url TEXT,
|
|
4658
4839
|
superseded_by INTEGER,
|
|
4659
4840
|
decision TEXT,
|
|
@@ -4769,6 +4950,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4769
4950
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
4770
4951
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
4771
4952
|
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
4953
|
+
// TASWM-548: сколько запусков прогона остались без снятой цены — отличает «бесплатно»
|
|
4954
|
+
// от «не измерено». Прогоны до фичи получают 0: их нули мы перепроверить уже не можем,
|
|
4955
|
+
// а задним числом объявлять их неизмеренными значило бы утверждать непроверенное.
|
|
4956
|
+
"ALTER TABLE runs ADD COLUMN cost_unmeasured INTEGER NOT NULL DEFAULT 0",
|
|
4772
4957
|
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
4773
4958
|
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
4774
4959
|
"ALTER TABLE runs ADD COLUMN effort TEXT",
|
|
@@ -5155,7 +5340,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5155
5340
|
* транзакции: прервётся — маркер не встанет, повтор безопасен.
|
|
5156
5341
|
*/
|
|
5157
5342
|
importDb(sourcePath, repo) {
|
|
5158
|
-
const abs =
|
|
5343
|
+
const abs = resolve3(sourcePath);
|
|
5159
5344
|
const repoN = normalizeRepo(repo);
|
|
5160
5345
|
const already = this.db.prepare("SELECT run_count FROM imports WHERE source = ?").get(abs);
|
|
5161
5346
|
if (already) return { imported: false, runs: already.run_count };
|
|
@@ -5486,8 +5671,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5486
5671
|
});
|
|
5487
5672
|
|
|
5488
5673
|
// src/repoScan.ts
|
|
5489
|
-
import { readdirSync as
|
|
5490
|
-
import { join as
|
|
5674
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync10 } from "fs";
|
|
5675
|
+
import { join as join20 } from "path";
|
|
5491
5676
|
var init_repoScan = __esm({
|
|
5492
5677
|
"src/repoScan.ts"() {
|
|
5493
5678
|
"use strict";
|
|
@@ -5496,9 +5681,9 @@ var init_repoScan = __esm({
|
|
|
5496
5681
|
});
|
|
5497
5682
|
|
|
5498
5683
|
// src/repoDiscovery.ts
|
|
5499
|
-
import { homedir as
|
|
5500
|
-
import { readFileSync as
|
|
5501
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5684
|
+
import { homedir as homedir6 } from "os";
|
|
5685
|
+
import { readFileSync as readFileSync15, renameSync as renameSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
5686
|
+
import { isAbsolute as isAbsolute3, join as join21, resolve as resolve4 } from "path";
|
|
5502
5687
|
function repoMapLines(rows) {
|
|
5503
5688
|
if (!rows?.length) return [];
|
|
5504
5689
|
const missing = rows.filter((r) => !r.path).length;
|
|
@@ -5513,7 +5698,7 @@ function repoMapLines(rows) {
|
|
|
5513
5698
|
}
|
|
5514
5699
|
function loadRepoMapCache(path) {
|
|
5515
5700
|
try {
|
|
5516
|
-
const raw = JSON.parse(
|
|
5701
|
+
const raw = JSON.parse(readFileSync15(path, "utf8"));
|
|
5517
5702
|
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
5518
5703
|
return {
|
|
5519
5704
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
@@ -5882,11 +6067,11 @@ __export(statusCli_exports, {
|
|
|
5882
6067
|
probeLink: () => probeLink,
|
|
5883
6068
|
runStatusCli: () => runStatusCli
|
|
5884
6069
|
});
|
|
5885
|
-
import { existsSync as existsSync13, readFileSync as
|
|
6070
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
5886
6071
|
function outboxPending(path) {
|
|
5887
6072
|
try {
|
|
5888
6073
|
if (!existsSync13(path)) return 0;
|
|
5889
|
-
return
|
|
6074
|
+
return readFileSync16(path, "utf8").split("\n").filter((l) => l.trim()).length;
|
|
5890
6075
|
} catch {
|
|
5891
6076
|
return null;
|
|
5892
6077
|
}
|
|
@@ -6093,11 +6278,11 @@ var init_prodMergeGate = __esm({
|
|
|
6093
6278
|
});
|
|
6094
6279
|
|
|
6095
6280
|
// src/platform.ts
|
|
6096
|
-
import { existsSync as existsSync14, readFileSync as
|
|
6097
|
-
import { delimiter, join as
|
|
6281
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
6282
|
+
import { delimiter, join as join22 } from "path";
|
|
6098
6283
|
function safeReadText(path) {
|
|
6099
6284
|
try {
|
|
6100
|
-
return
|
|
6285
|
+
return readFileSync17(path, "utf8");
|
|
6101
6286
|
} catch {
|
|
6102
6287
|
return null;
|
|
6103
6288
|
}
|
|
@@ -6108,7 +6293,7 @@ function resolveCommandPath(name, env = process.env, platform = process.platform
|
|
|
6108
6293
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
6109
6294
|
for (const dir of dirs) {
|
|
6110
6295
|
for (const ext of exts) {
|
|
6111
|
-
const candidate =
|
|
6296
|
+
const candidate = join22(dir, name + ext);
|
|
6112
6297
|
if (existsSync14(candidate)) return candidate;
|
|
6113
6298
|
}
|
|
6114
6299
|
}
|
|
@@ -6213,12 +6398,12 @@ var init_platform = __esm({
|
|
|
6213
6398
|
|
|
6214
6399
|
// src/envGate.ts
|
|
6215
6400
|
import { existsSync as existsSync15 } from "fs";
|
|
6216
|
-
import { join as
|
|
6401
|
+
import { join as join23 } from "path";
|
|
6217
6402
|
import { createConnection } from "net";
|
|
6218
6403
|
import { execa as execa3 } from "execa";
|
|
6219
6404
|
function detectRequirements(repo, io = {}) {
|
|
6220
6405
|
const exists = io.exists ?? existsSync15;
|
|
6221
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
6406
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join23(repo, f)));
|
|
6222
6407
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
6223
6408
|
}
|
|
6224
6409
|
async function dockerAlive() {
|
|
@@ -6250,7 +6435,7 @@ var init_envGate = __esm({
|
|
|
6250
6435
|
});
|
|
6251
6436
|
|
|
6252
6437
|
// src/doctor.ts
|
|
6253
|
-
import { isAbsolute as isAbsolute4, join as
|
|
6438
|
+
import { isAbsolute as isAbsolute4, join as join24 } from "path";
|
|
6254
6439
|
function ok(id, title, detail) {
|
|
6255
6440
|
return { id, title, status: "ok", detail, remedies: [], critical: false };
|
|
6256
6441
|
}
|
|
@@ -6457,7 +6642,7 @@ function checkRepos(io, snap) {
|
|
|
6457
6642
|
critical: true
|
|
6458
6643
|
};
|
|
6459
6644
|
}
|
|
6460
|
-
if (env && !io.exists(
|
|
6645
|
+
if (env && !io.exists(join24(env, ".git"))) {
|
|
6461
6646
|
return {
|
|
6462
6647
|
id: "repos",
|
|
6463
6648
|
title,
|
|
@@ -6467,7 +6652,7 @@ function checkRepos(io, snap) {
|
|
|
6467
6652
|
critical: true
|
|
6468
6653
|
};
|
|
6469
6654
|
}
|
|
6470
|
-
const broken = snap.paths.filter((p) => !io.exists(
|
|
6655
|
+
const broken = snap.paths.filter((p) => !io.exists(join24(p, ".git")));
|
|
6471
6656
|
if (broken.length) {
|
|
6472
6657
|
return {
|
|
6473
6658
|
id: "repos",
|
|
@@ -6657,12 +6842,12 @@ var init_doctor = __esm({
|
|
|
6657
6842
|
});
|
|
6658
6843
|
|
|
6659
6844
|
// src/repoMemory.ts
|
|
6660
|
-
import { readFileSync as
|
|
6661
|
-
import { join as
|
|
6845
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
|
|
6846
|
+
import { join as join25 } from "path";
|
|
6662
6847
|
function loadRememberedRepos(home) {
|
|
6663
6848
|
let raw;
|
|
6664
6849
|
try {
|
|
6665
|
-
raw =
|
|
6850
|
+
raw = readFileSync18(join25(home, FILE), "utf8");
|
|
6666
6851
|
} catch {
|
|
6667
6852
|
return [];
|
|
6668
6853
|
}
|
|
@@ -6981,8 +7166,8 @@ var init_updateCli = __esm({
|
|
|
6981
7166
|
|
|
6982
7167
|
// src/cli.ts
|
|
6983
7168
|
init_cliIo();
|
|
6984
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
6985
|
-
import { existsSync as existsSync17, readFileSync as
|
|
7169
|
+
import { basename as basename6, dirname as dirname5, join as join26 } from "path";
|
|
7170
|
+
import { existsSync as existsSync17, readFileSync as readFileSync19, writeFileSync as writeFileSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync11, statSync as statSync11, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
|
|
6986
7171
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
6987
7172
|
|
|
6988
7173
|
// src/swarmClient.ts
|
|
@@ -6990,7 +7175,7 @@ init_config();
|
|
|
6990
7175
|
init_resources();
|
|
6991
7176
|
init_names2();
|
|
6992
7177
|
init_storm();
|
|
6993
|
-
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as
|
|
7178
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
6994
7179
|
import { dirname as dirname2 } from "path";
|
|
6995
7180
|
import { randomUUID } from "crypto";
|
|
6996
7181
|
|
|
@@ -7022,19 +7207,19 @@ init_zoneNotes();
|
|
|
7022
7207
|
init_toolFailure();
|
|
7023
7208
|
|
|
7024
7209
|
// src/logTail.ts
|
|
7025
|
-
import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as
|
|
7026
|
-
import { join as
|
|
7210
|
+
import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
7211
|
+
import { join as join9 } from "path";
|
|
7027
7212
|
|
|
7028
7213
|
// src/runLogDir.ts
|
|
7029
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as
|
|
7030
|
-
import { join as
|
|
7214
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync5, renameSync as renameSync2, statSync as statSync4 } from "fs";
|
|
7215
|
+
import { join as join8 } from "path";
|
|
7031
7216
|
function runLogDirName(runLogKey) {
|
|
7032
7217
|
const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
7033
7218
|
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)}`);
|
|
7034
7219
|
return /^\d+$/.test(safe) ? `k-${safe}` : safe;
|
|
7035
7220
|
}
|
|
7036
7221
|
function runLogDirPath(runLogsDir, runLogKey) {
|
|
7037
|
-
return
|
|
7222
|
+
return join8(runLogsDir, runLogDirName(runLogKey));
|
|
7038
7223
|
}
|
|
7039
7224
|
function findRunLogDir(runLogsDir, keys) {
|
|
7040
7225
|
for (const key of keys) {
|
|
@@ -7246,13 +7431,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7246
7431
|
recover() {
|
|
7247
7432
|
if (existsSync6(this.mapPath)) {
|
|
7248
7433
|
try {
|
|
7249
|
-
const obj = JSON.parse(
|
|
7434
|
+
const obj = JSON.parse(readFileSync6(this.mapPath, "utf8"));
|
|
7250
7435
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
7251
7436
|
} catch {
|
|
7252
7437
|
}
|
|
7253
7438
|
}
|
|
7254
7439
|
if (existsSync6(this.outboxPath)) {
|
|
7255
|
-
const txt =
|
|
7440
|
+
const txt = readFileSync6(this.outboxPath, "utf8");
|
|
7256
7441
|
for (const line of txt.split("\n")) {
|
|
7257
7442
|
const s = line.trim();
|
|
7258
7443
|
if (!s) continue;
|
|
@@ -7275,7 +7460,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7275
7460
|
if (!existsSync6(this.stormPath)) return;
|
|
7276
7461
|
let txt;
|
|
7277
7462
|
try {
|
|
7278
|
-
txt =
|
|
7463
|
+
txt = readFileSync6(this.stormPath, "utf8");
|
|
7279
7464
|
} catch {
|
|
7280
7465
|
return;
|
|
7281
7466
|
}
|
|
@@ -7359,6 +7544,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7359
7544
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
7360
7545
|
if ("tokens_out" in patch && patch.tokens_out !== void 0) out.tokensOut = patch.tokens_out;
|
|
7361
7546
|
if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
|
|
7547
|
+
if ("cost_unmeasured" in patch && patch.cost_unmeasured !== void 0) out.costUnmeasured = patch.cost_unmeasured;
|
|
7362
7548
|
if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
|
|
7363
7549
|
if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
|
|
7364
7550
|
if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
|
|
@@ -7441,6 +7627,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7441
7627
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
7442
7628
|
tokens_out: 0,
|
|
7443
7629
|
duration_ms: 0,
|
|
7630
|
+
cost_unmeasured: 0,
|
|
7631
|
+
// запусков без снятой цены (TASWM-548) — копится тем же учётом
|
|
7444
7632
|
pr_url: null,
|
|
7445
7633
|
changed_paths: [],
|
|
7446
7634
|
// файлы прогона снимаются при открытии PR (TASWM-506)
|
|
@@ -9061,6 +9249,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
9061
9249
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
9062
9250
|
tokens_out: 0,
|
|
9063
9251
|
duration_ms: 0,
|
|
9252
|
+
cost_unmeasured: 0,
|
|
9253
|
+
// запусков без снятой цены (TASWM-548) — прод-поле, заполняется ниже
|
|
9064
9254
|
pr_url: null,
|
|
9065
9255
|
changed_paths: [],
|
|
9066
9256
|
// занятые файлы (TASWM-506) — прод-поле, заполняется ниже
|
|
@@ -9142,6 +9332,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
9142
9332
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
9143
9333
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
9144
9334
|
local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
|
|
9335
|
+
local.cost_unmeasured = typeof pr.costUnmeasured === "number" ? pr.costUnmeasured : 0;
|
|
9145
9336
|
local.pr_url = pr.prUrl ?? null;
|
|
9146
9337
|
local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
|
|
9147
9338
|
local.stop_reason = pr.stopReason ?? null;
|
|
@@ -9539,7 +9730,7 @@ init_config();
|
|
|
9539
9730
|
import { $ as $6 } from "execa";
|
|
9540
9731
|
|
|
9541
9732
|
// src/daemonLaunch.ts
|
|
9542
|
-
import { openSync as openSync2, closeSync as closeSync2, readFileSync as
|
|
9733
|
+
import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync7 } from "fs";
|
|
9543
9734
|
import { spawn } from "child_process";
|
|
9544
9735
|
var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
|
|
9545
9736
|
function parseStartMode(argv) {
|
|
@@ -9599,18 +9790,18 @@ function launchDetached(opts) {
|
|
|
9599
9790
|
}
|
|
9600
9791
|
function tailFile(path, lines) {
|
|
9601
9792
|
try {
|
|
9602
|
-
return tailText(
|
|
9793
|
+
return tailText(readFileSync7(path, "utf8"), lines);
|
|
9603
9794
|
} catch {
|
|
9604
9795
|
return "";
|
|
9605
9796
|
}
|
|
9606
9797
|
}
|
|
9607
9798
|
|
|
9608
9799
|
// src/logRotate.ts
|
|
9609
|
-
import { copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as
|
|
9800
|
+
import { copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync6, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
|
|
9610
9801
|
var realIo = {
|
|
9611
9802
|
size: (path) => {
|
|
9612
9803
|
try {
|
|
9613
|
-
return
|
|
9804
|
+
return statSync6(path).size;
|
|
9614
9805
|
} catch {
|
|
9615
9806
|
return null;
|
|
9616
9807
|
}
|
|
@@ -9647,15 +9838,15 @@ init_repoRegistry();
|
|
|
9647
9838
|
|
|
9648
9839
|
// src/hostIdentity.ts
|
|
9649
9840
|
import { hostname } from "os";
|
|
9650
|
-
import { existsSync as existsSync8, readFileSync as
|
|
9651
|
-
import { join as
|
|
9841
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
9842
|
+
import { join as join10 } from "path";
|
|
9652
9843
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9653
9844
|
function hostIdentityPath(home) {
|
|
9654
|
-
return
|
|
9845
|
+
return join10(home, "host.json");
|
|
9655
9846
|
}
|
|
9656
9847
|
function loadHostIdentity(deps) {
|
|
9657
9848
|
const exists = deps.exists ?? existsSync8;
|
|
9658
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
9849
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync8(p, "utf8"));
|
|
9659
9850
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
9660
9851
|
writeFileSync6(`${p}.tmp`, data);
|
|
9661
9852
|
renameSync4(`${p}.tmp`, p);
|
|
@@ -9772,8 +9963,8 @@ import { basename as basename4 } from "path";
|
|
|
9772
9963
|
import { serve as serve2 } from "@hono/node-server";
|
|
9773
9964
|
|
|
9774
9965
|
// src/diff.ts
|
|
9775
|
-
import { statSync as
|
|
9776
|
-
import { join as
|
|
9966
|
+
import { statSync as statSync7 } from "fs";
|
|
9967
|
+
import { join as join11 } from "path";
|
|
9777
9968
|
import { $ as $3 } from "execa";
|
|
9778
9969
|
var MAX_FILES = 200;
|
|
9779
9970
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -9916,7 +10107,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
9916
10107
|
}
|
|
9917
10108
|
function mtime(path) {
|
|
9918
10109
|
try {
|
|
9919
|
-
return
|
|
10110
|
+
return statSync7(path).mtimeMs;
|
|
9920
10111
|
} catch {
|
|
9921
10112
|
return 0;
|
|
9922
10113
|
}
|
|
@@ -9941,7 +10132,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9941
10132
|
files.push(pf);
|
|
9942
10133
|
}
|
|
9943
10134
|
}
|
|
9944
|
-
files.sort((a, b) => mtime(
|
|
10135
|
+
files.sort((a, b) => mtime(join11(worktree, b.path)) - mtime(join11(worktree, a.path)));
|
|
9945
10136
|
const truncatedFiles = files.length > MAX_FILES;
|
|
9946
10137
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
9947
10138
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -11667,14 +11858,14 @@ function makeHandler(deps) {
|
|
|
11667
11858
|
const ref = url.searchParams.get("pr") ?? "";
|
|
11668
11859
|
if (!ref.trim()) return Response.json({ error: "\u043D\u0443\u0436\u0435\u043D \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 pr=<url|\u043D\u043E\u043C\u0435\u0440>" }, { status: 400 });
|
|
11669
11860
|
const ctxRepo = url.searchParams.get("repo");
|
|
11670
|
-
const
|
|
11861
|
+
const resolve5 = deps.mergeGate ?? makeMergeGateResolver({
|
|
11671
11862
|
listRuns: () => deps.db.listRuns(),
|
|
11672
11863
|
fallbackAutoReview: SWARM_AUTO_REVIEW,
|
|
11673
11864
|
autoVerify: SWARM_AUTO_VERIFY,
|
|
11674
11865
|
// TASWM-344
|
|
11675
11866
|
...defaultRepo ? { repoFallback: defaultRepo } : {}
|
|
11676
11867
|
});
|
|
11677
|
-
return Response.json(await
|
|
11868
|
+
return Response.json(await resolve5(ref, ctxRepo));
|
|
11678
11869
|
}
|
|
11679
11870
|
if (p === "/resources") return Response.json(hostResourceSnapshot());
|
|
11680
11871
|
if (p === "/timeline") return Response.json(timelineState(deps.db, Date.now(), readNarratorLog(deps.narratorLog)));
|
|
@@ -11832,15 +12023,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
11832
12023
|
|
|
11833
12024
|
// src/launcher.ts
|
|
11834
12025
|
import { spawn as spawn2 } from "child_process";
|
|
11835
|
-
import { closeSync as closeSync3, existsSync as existsSync9, mkdirSync as mkdirSync9, openSync as openSync3, readdirSync as
|
|
12026
|
+
import { closeSync as closeSync3, existsSync as existsSync9, mkdirSync as mkdirSync9, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
|
|
11836
12027
|
import { createRequire } from "module";
|
|
11837
|
-
import { homedir as
|
|
11838
|
-
import { dirname as dirname4, join as
|
|
12028
|
+
import { homedir as homedir5 } from "os";
|
|
12029
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
11839
12030
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11840
12031
|
|
|
11841
12032
|
// src/droneHome.ts
|
|
11842
|
-
import { lstatSync, mkdirSync as mkdirSync8, readdirSync as
|
|
11843
|
-
import { join as
|
|
12033
|
+
import { lstatSync, mkdirSync as mkdirSync8, readdirSync as readdirSync7, rmSync, statSync as statSync8, symlinkSync as symlinkSync2 } from "fs";
|
|
12034
|
+
import { join as join13, relative, sep } from "path";
|
|
11844
12035
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
11845
12036
|
function droneHomeDeny(env = process.env) {
|
|
11846
12037
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -11859,28 +12050,28 @@ function buildDroneHome(opts) {
|
|
|
11859
12050
|
const deny = opts.deny ?? droneHomeDeny();
|
|
11860
12051
|
const platform = opts.platform ?? process.platform;
|
|
11861
12052
|
mkdirSync8(droneHome, { recursive: true, mode: 448 });
|
|
11862
|
-
const wanted = new Set(mirroredEntries(
|
|
12053
|
+
const wanted = new Set(mirroredEntries(readdirSync7(realHome), deny, platform));
|
|
11863
12054
|
const rel = relative(realHome, droneHome);
|
|
11864
12055
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
11865
|
-
for (const existing of
|
|
12056
|
+
for (const existing of readdirSync7(droneHome)) {
|
|
11866
12057
|
if (wanted.has(existing)) continue;
|
|
11867
|
-
const p =
|
|
12058
|
+
const p = join13(droneHome, existing);
|
|
11868
12059
|
try {
|
|
11869
12060
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
11870
12061
|
} catch {
|
|
11871
12062
|
}
|
|
11872
12063
|
}
|
|
11873
12064
|
for (const name of wanted) {
|
|
11874
|
-
const link =
|
|
12065
|
+
const link = join13(droneHome, name);
|
|
11875
12066
|
try {
|
|
11876
12067
|
lstatSync(link);
|
|
11877
12068
|
continue;
|
|
11878
12069
|
} catch {
|
|
11879
12070
|
}
|
|
11880
|
-
const target =
|
|
12071
|
+
const target = join13(realHome, name);
|
|
11881
12072
|
let isDir;
|
|
11882
12073
|
try {
|
|
11883
|
-
isDir =
|
|
12074
|
+
isDir = statSync8(target).isDirectory();
|
|
11884
12075
|
} catch {
|
|
11885
12076
|
continue;
|
|
11886
12077
|
}
|
|
@@ -11898,7 +12089,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
11898
12089
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
11899
12090
|
];
|
|
11900
12091
|
function realPathEnvPins(realHome) {
|
|
11901
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
12092
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join13(realHome, ...segments)]));
|
|
11902
12093
|
}
|
|
11903
12094
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
11904
12095
|
return [
|
|
@@ -12010,7 +12201,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
12010
12201
|
const env = { ...base3 };
|
|
12011
12202
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
12012
12203
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
12013
|
-
env.TURBO_CACHE_DIR =
|
|
12204
|
+
env.TURBO_CACHE_DIR = join14(worktree, ".turbo", "cache");
|
|
12014
12205
|
}
|
|
12015
12206
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
12016
12207
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -12038,10 +12229,10 @@ var DAEMON_STATE_ENV = [
|
|
|
12038
12229
|
"TASKLESS_TOKEN"
|
|
12039
12230
|
];
|
|
12040
12231
|
function bundledRoleDir(kind) {
|
|
12041
|
-
return
|
|
12232
|
+
return join14(dirname4(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
12042
12233
|
}
|
|
12043
12234
|
function roleDir(worktree, kind) {
|
|
12044
|
-
return
|
|
12235
|
+
return join14(worktree, ".claude", "roles", kind);
|
|
12045
12236
|
}
|
|
12046
12237
|
function roleDirs(worktree, kind) {
|
|
12047
12238
|
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync9(dir));
|
|
@@ -12052,7 +12243,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
12052
12243
|
mkdirSync9(dir, { recursive: true });
|
|
12053
12244
|
let files = [];
|
|
12054
12245
|
try {
|
|
12055
|
-
files =
|
|
12246
|
+
files = readdirSync8(dir);
|
|
12056
12247
|
} catch {
|
|
12057
12248
|
}
|
|
12058
12249
|
let next = 0;
|
|
@@ -12060,7 +12251,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
12060
12251
|
const m = /^(\d+)\.json/.exec(f);
|
|
12061
12252
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
12062
12253
|
}
|
|
12063
|
-
return
|
|
12254
|
+
return join14(dir, `${next}.json`);
|
|
12064
12255
|
}
|
|
12065
12256
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
12066
12257
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -12141,11 +12332,11 @@ function makeLauncher(cfg) {
|
|
|
12141
12332
|
} catch (e) {
|
|
12142
12333
|
console.error(`[launcher] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0441\u0438\u043B\u0438\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run.id}: ${String(e)}`);
|
|
12143
12334
|
}
|
|
12144
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
12335
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir5();
|
|
12145
12336
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
12146
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(
|
|
12337
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(join14(cfg.droneIsolation.swarmHome, "token"))) {
|
|
12147
12338
|
console.error(
|
|
12148
|
-
`[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 ${
|
|
12339
|
+
`[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 ${join14(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.`
|
|
12149
12340
|
);
|
|
12150
12341
|
}
|
|
12151
12342
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -12561,10 +12752,10 @@ async function restoreDaemon(d, loginOk) {
|
|
|
12561
12752
|
d.err("\u0434\u0435\u043C\u043E\u043D \u043E\u0431\u0440\u0430\u0442\u043D\u043E \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F (\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432\u044B\u0448\u0435) \u2014 \u0440\u043E\u0439 \u0441\u0442\u043E\u0438\u0442. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0432\u0440\u0443\u0447\u043D\u0443\u044E: swarm start");
|
|
12562
12753
|
}
|
|
12563
12754
|
function isPortFree(port, host = "127.0.0.1") {
|
|
12564
|
-
return new Promise((
|
|
12755
|
+
return new Promise((resolve5) => {
|
|
12565
12756
|
const probe = createServer();
|
|
12566
|
-
probe.once("error", () =>
|
|
12567
|
-
probe.listen(port, host, () => probe.close(() =>
|
|
12757
|
+
probe.once("error", () => resolve5(false));
|
|
12758
|
+
probe.listen(port, host, () => probe.close(() => resolve5(true)));
|
|
12568
12759
|
});
|
|
12569
12760
|
}
|
|
12570
12761
|
async function chooseWorkspace(d) {
|
|
@@ -13051,12 +13242,12 @@ async function runDaemonLogs(argv) {
|
|
|
13051
13242
|
const head = tailFile(DAEMON_LOG_PATH, lines);
|
|
13052
13243
|
if (head) console.log(head);
|
|
13053
13244
|
if (!follow) return 0;
|
|
13054
|
-
let pos =
|
|
13245
|
+
let pos = statSync11(DAEMON_LOG_PATH).size;
|
|
13055
13246
|
for (; ; ) {
|
|
13056
13247
|
await sleep2(500);
|
|
13057
13248
|
let size;
|
|
13058
13249
|
try {
|
|
13059
|
-
size =
|
|
13250
|
+
size = statSync11(DAEMON_LOG_PATH).size;
|
|
13060
13251
|
} catch {
|
|
13061
13252
|
continue;
|
|
13062
13253
|
}
|
|
@@ -13328,7 +13519,7 @@ function effectiveWorktreesDir() {
|
|
|
13328
13519
|
exists: (d) => existsSync17(d),
|
|
13329
13520
|
isNonEmptyDir: (d) => {
|
|
13330
13521
|
try {
|
|
13331
|
-
return
|
|
13522
|
+
return readdirSync11(d).length > 0;
|
|
13332
13523
|
} catch {
|
|
13333
13524
|
return false;
|
|
13334
13525
|
}
|
|
@@ -13384,17 +13575,17 @@ async function scoutRepo(repo) {
|
|
|
13384
13575
|
await $6({ reject: false })`git -C ${worktree} reset --hard ${baseRef}`;
|
|
13385
13576
|
}
|
|
13386
13577
|
await setWorktreeIdentity(worktree, name);
|
|
13387
|
-
const abs = (rel) =>
|
|
13578
|
+
const abs = (rel) => join26(worktree, rel);
|
|
13388
13579
|
const code = await runRepoScout({
|
|
13389
13580
|
repoFullName,
|
|
13390
13581
|
repoPath: worktree,
|
|
13391
13582
|
name,
|
|
13392
|
-
readFile: (rel) => existsSync17(abs(rel)) ?
|
|
13583
|
+
readFile: (rel) => existsSync17(abs(rel)) ? readFileSync19(abs(rel), "utf8") : null,
|
|
13393
13584
|
writeFile: (rel, text) => {
|
|
13394
13585
|
mkdirSync10(dirname5(abs(rel)), { recursive: true });
|
|
13395
13586
|
writeFileSync14(abs(rel), text);
|
|
13396
13587
|
},
|
|
13397
|
-
runScout: (prompt) => new Promise((
|
|
13588
|
+
runScout: (prompt) => new Promise((resolve5) => {
|
|
13398
13589
|
const child = spawn3(bin, [...ISOLATION_FLAGS, "--dangerously-skip-permissions", "-p", prompt], {
|
|
13399
13590
|
cwd: worktree,
|
|
13400
13591
|
stdio: ["ignore", "inherit", "inherit"]
|
|
@@ -13402,9 +13593,9 @@ async function scoutRepo(repo) {
|
|
|
13402
13593
|
});
|
|
13403
13594
|
child.on("error", (e) => {
|
|
13404
13595
|
console.error(`\u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C (${bin}): ${e.message}`);
|
|
13405
|
-
|
|
13596
|
+
resolve5(1);
|
|
13406
13597
|
});
|
|
13407
|
-
child.on("exit", (c) =>
|
|
13598
|
+
child.on("exit", (c) => resolve5(c ?? 1));
|
|
13408
13599
|
}),
|
|
13409
13600
|
publish: (files) => publishScoutPr({ worktree, repo, files, name, baseBranch, openPr: openPr.url, fresh: openPr.known && !openPr.url }),
|
|
13410
13601
|
log: (line) => console.log(line)
|
|
@@ -13942,6 +14133,11 @@ switch (cmd) {
|
|
|
13942
14133
|
{
|
|
13943
14134
|
db,
|
|
13944
14135
|
kill: (pid) => process.kill(pid, "SIGTERM"),
|
|
14136
|
+
// TASWM-553: тот же порядок, что у демона (stop_run / POST /cancel) — снос worktree
|
|
14137
|
+
// ПОСЛЕ смерти процесса, а не следом за просьбой умереть. Здесь это нужнее всего:
|
|
14138
|
+
// сюда попадают ровно тогда, когда демон не ответил, и подчистить за собой некому.
|
|
14139
|
+
isAlive: pidAlive,
|
|
14140
|
+
hardKill: (pid) => process.kill(pid, "SIGKILL"),
|
|
13945
14141
|
removeWorktree: (repo, p) => {
|
|
13946
14142
|
const r = repo || SWARM_REPO;
|
|
13947
14143
|
return r ? removeWorktree(r, p) : Promise.resolve();
|