@taskless-app/swarm 0.6.37 → 0.6.39
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 +288 -153
- package/dist/overmind.js +780 -323
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -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
|
}
|
|
@@ -2805,12 +2910,23 @@ var init_storm = __esm({
|
|
|
2805
2910
|
}
|
|
2806
2911
|
});
|
|
2807
2912
|
|
|
2913
|
+
// src/zoneNotes.ts
|
|
2914
|
+
function normalizeZoneLabel(zone) {
|
|
2915
|
+
return (zone ?? "").trim().replace(/^area:/i, "").trim().toLowerCase();
|
|
2916
|
+
}
|
|
2917
|
+
var init_zoneNotes = __esm({
|
|
2918
|
+
"src/zoneNotes.ts"() {
|
|
2919
|
+
"use strict";
|
|
2920
|
+
init_src();
|
|
2921
|
+
}
|
|
2922
|
+
});
|
|
2923
|
+
|
|
2808
2924
|
// src/agentToken.ts
|
|
2809
|
-
import { readFileSync as
|
|
2810
|
-
import { join as
|
|
2925
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync } from "fs";
|
|
2926
|
+
import { join as join7 } from "path";
|
|
2811
2927
|
function loadAgentToken() {
|
|
2812
2928
|
try {
|
|
2813
|
-
const s = JSON.parse(
|
|
2929
|
+
const s = JSON.parse(readFileSync5(STORE_PATH, "utf8"));
|
|
2814
2930
|
return s.token ? s : null;
|
|
2815
2931
|
} catch {
|
|
2816
2932
|
return null;
|
|
@@ -2888,7 +3004,7 @@ var init_agentToken = __esm({
|
|
|
2888
3004
|
"src/agentToken.ts"() {
|
|
2889
3005
|
"use strict";
|
|
2890
3006
|
init_config();
|
|
2891
|
-
STORE_PATH =
|
|
3007
|
+
STORE_PATH = join7(SWARM_HOME, "agent.json");
|
|
2892
3008
|
base = (api) => api.replace(/\/$/, "");
|
|
2893
3009
|
TOKEN_PROBE_TIMEOUT_MS = 8e3;
|
|
2894
3010
|
}
|
|
@@ -3274,7 +3390,7 @@ var init_package = __esm({
|
|
|
3274
3390
|
"package.json"() {
|
|
3275
3391
|
package_default = {
|
|
3276
3392
|
name: "@taskless-app/swarm",
|
|
3277
|
-
version: "0.6.
|
|
3393
|
+
version: "0.6.39",
|
|
3278
3394
|
type: "module",
|
|
3279
3395
|
description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
|
|
3280
3396
|
keywords: [
|
|
@@ -3325,7 +3441,7 @@ var init_package = __esm({
|
|
|
3325
3441
|
execa: "^10.0.0",
|
|
3326
3442
|
zod: "3.25.76"
|
|
3327
3443
|
},
|
|
3328
|
-
gitHead: "
|
|
3444
|
+
gitHead: "0c0b1bc0ee05add0d2b5b5d503a05ed7b7834dd8"
|
|
3329
3445
|
};
|
|
3330
3446
|
}
|
|
3331
3447
|
});
|
|
@@ -3491,12 +3607,12 @@ var init_netTimeout = __esm({
|
|
|
3491
3607
|
|
|
3492
3608
|
// src/droneChannel.ts
|
|
3493
3609
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3494
|
-
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";
|
|
3495
3611
|
import { dirname as dirname3 } from "path";
|
|
3496
3612
|
import { serve } from "@hono/node-server";
|
|
3497
3613
|
function loadChannelSecret(path) {
|
|
3498
3614
|
try {
|
|
3499
|
-
const s =
|
|
3615
|
+
const s = readFileSync9(path, "utf8").trim();
|
|
3500
3616
|
if (s.length >= 32) return s;
|
|
3501
3617
|
} catch {
|
|
3502
3618
|
}
|
|
@@ -3579,8 +3695,8 @@ var init_authState = __esm({
|
|
|
3579
3695
|
|
|
3580
3696
|
// src/oauth.ts
|
|
3581
3697
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
3582
|
-
import { readFileSync as
|
|
3583
|
-
import { join as
|
|
3698
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
3699
|
+
import { join as join12 } from "path";
|
|
3584
3700
|
function makePkce() {
|
|
3585
3701
|
const verifier = randomBytes2(48).toString("base64url");
|
|
3586
3702
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -3664,7 +3780,7 @@ function toStore(clientId, t, now) {
|
|
|
3664
3780
|
}
|
|
3665
3781
|
function loadStore() {
|
|
3666
3782
|
try {
|
|
3667
|
-
return JSON.parse(
|
|
3783
|
+
return JSON.parse(readFileSync10(STORE_PATH2, "utf8"));
|
|
3668
3784
|
} catch {
|
|
3669
3785
|
return null;
|
|
3670
3786
|
}
|
|
@@ -3680,7 +3796,7 @@ function clearStore() {
|
|
|
3680
3796
|
}
|
|
3681
3797
|
function loadClientId() {
|
|
3682
3798
|
try {
|
|
3683
|
-
return JSON.parse(
|
|
3799
|
+
return JSON.parse(readFileSync10(CLIENT_PATH, "utf8")).clientId;
|
|
3684
3800
|
} catch {
|
|
3685
3801
|
return null;
|
|
3686
3802
|
}
|
|
@@ -3715,16 +3831,16 @@ var init_oauth = __esm({
|
|
|
3715
3831
|
AUTHORIZE = "/api/oauth/authorize";
|
|
3716
3832
|
TOKEN = "/api/oauth/token";
|
|
3717
3833
|
SCOPE = "openid profile email taskless:full";
|
|
3718
|
-
STORE_PATH2 =
|
|
3719
|
-
CLIENT_PATH =
|
|
3834
|
+
STORE_PATH2 = join12(SWARM_HOME, "oauth.json");
|
|
3835
|
+
CLIENT_PATH = join12(SWARM_HOME, "oauth-client.json");
|
|
3720
3836
|
base2 = (api) => api.replace(/\/$/, "");
|
|
3721
3837
|
}
|
|
3722
3838
|
});
|
|
3723
3839
|
|
|
3724
3840
|
// src/git.ts
|
|
3725
|
-
import { existsSync as existsSync10, readdirSync as
|
|
3841
|
+
import { existsSync as existsSync10, readdirSync as readdirSync9, statSync as statSync9 } from "fs";
|
|
3726
3842
|
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3727
|
-
import { isAbsolute, join as
|
|
3843
|
+
import { isAbsolute, join as join15, sep as sep2 } from "path";
|
|
3728
3844
|
import { $ as $5 } from "execa";
|
|
3729
3845
|
function gitNet(repo, args, timeoutMs) {
|
|
3730
3846
|
return withHardTimeout(
|
|
@@ -3739,7 +3855,7 @@ async function reclaimStaleWorktree(repo, worktree) {
|
|
|
3739
3855
|
}
|
|
3740
3856
|
function isLinkedWorktree(dir) {
|
|
3741
3857
|
try {
|
|
3742
|
-
return
|
|
3858
|
+
return statSync9(join15(dir, ".git")).isFile();
|
|
3743
3859
|
} catch {
|
|
3744
3860
|
return false;
|
|
3745
3861
|
}
|
|
@@ -3844,8 +3960,8 @@ async function ensureGitExcluded(worktree, pattern) {
|
|
|
3844
3960
|
try {
|
|
3845
3961
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3846
3962
|
if (!commonDir) return false;
|
|
3847
|
-
const infoDir =
|
|
3848
|
-
const excludeFile =
|
|
3963
|
+
const infoDir = join15(commonDir, "info");
|
|
3964
|
+
const excludeFile = join15(infoDir, "exclude");
|
|
3849
3965
|
let current = "";
|
|
3850
3966
|
try {
|
|
3851
3967
|
current = await readFile(excludeFile, "utf8");
|
|
@@ -3865,7 +3981,7 @@ async function gitPath(worktree, flag) {
|
|
|
3865
3981
|
const out = await $5({ reject: false })`git -C ${worktree} rev-parse ${flag}`;
|
|
3866
3982
|
const raw = out.stdout.trim();
|
|
3867
3983
|
if (out.exitCode !== 0 || !raw) return null;
|
|
3868
|
-
return isAbsolute(raw) ? raw :
|
|
3984
|
+
return isAbsolute(raw) ? raw : join15(worktree, raw);
|
|
3869
3985
|
}
|
|
3870
3986
|
async function ensureWorktreeHygiene(worktree) {
|
|
3871
3987
|
for (const pattern of SWARM_EXCLUDE_PATTERNS) await ensureGitExcluded(worktree, pattern);
|
|
@@ -3875,12 +3991,12 @@ async function installGuardHooks(worktree) {
|
|
|
3875
3991
|
try {
|
|
3876
3992
|
const gitDir = await gitPath(worktree, "--git-dir");
|
|
3877
3993
|
if (!gitDir) return false;
|
|
3878
|
-
const hooksDir =
|
|
3994
|
+
const hooksDir = join15(gitDir, "swarm-hooks");
|
|
3879
3995
|
const prev = await previousHooksPath(worktree);
|
|
3880
3996
|
await mkdir(hooksDir, { recursive: true });
|
|
3881
3997
|
for (const hook of GUARD_HOOKS) {
|
|
3882
|
-
await writeFile(
|
|
3883
|
-
await chmod(
|
|
3998
|
+
await writeFile(join15(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
|
|
3999
|
+
await chmod(join15(hooksDir, hook), 493);
|
|
3884
4000
|
}
|
|
3885
4001
|
await $5`git -C ${worktree} config extensions.worktreeConfig true`;
|
|
3886
4002
|
await $5`git -C ${worktree} config --worktree core.hooksPath ${posix(hooksDir)}`;
|
|
@@ -3899,7 +4015,7 @@ async function previousHooksPath(worktree) {
|
|
|
3899
4015
|
if (out.exitCode === 0 && value) return value;
|
|
3900
4016
|
}
|
|
3901
4017
|
const commonDir = await gitPath(worktree, "--git-common-dir");
|
|
3902
|
-
return commonDir ?
|
|
4018
|
+
return commonDir ? join15(commonDir, "hooks") : null;
|
|
3903
4019
|
}
|
|
3904
4020
|
async function removeWorktree(repo, path) {
|
|
3905
4021
|
const branch = (await $5({ reject: false })`git -C ${path} symbolic-ref --quiet --short HEAD`).stdout.trim();
|
|
@@ -3956,7 +4072,7 @@ var init_git = __esm({
|
|
|
3956
4072
|
|
|
3957
4073
|
// src/attachments.ts
|
|
3958
4074
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3959
|
-
import { join as
|
|
4075
|
+
import { join as join16 } from "path";
|
|
3960
4076
|
var ATTACHMENTS_DIR, ATTACH_MAX_TOTAL_BYTES, ATTACH_MAX_FILE_BYTES, IMAGE_EXT, DOC_EXT, TEXT_EXT, KNOWN_EXT;
|
|
3961
4077
|
var init_attachments = __esm({
|
|
3962
4078
|
"src/attachments.ts"() {
|
|
@@ -3975,7 +4091,7 @@ var init_attachments = __esm({
|
|
|
3975
4091
|
|
|
3976
4092
|
// src/shotUpload.ts
|
|
3977
4093
|
import { readFile as fsReadFile } from "fs/promises";
|
|
3978
|
-
import { basename as basename5, isAbsolute as isAbsolute2, join as
|
|
4094
|
+
import { basename as basename5, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
3979
4095
|
var SHOT_MAX_FILE_BYTES;
|
|
3980
4096
|
var init_shotUpload = __esm({
|
|
3981
4097
|
"src/shotUpload.ts"() {
|
|
@@ -3985,11 +4101,11 @@ var init_shotUpload = __esm({
|
|
|
3985
4101
|
});
|
|
3986
4102
|
|
|
3987
4103
|
// src/workspace.ts
|
|
3988
|
-
import { readFileSync as
|
|
3989
|
-
import { join as
|
|
4104
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
|
|
4105
|
+
import { join as join18 } from "path";
|
|
3990
4106
|
function loadWorkspace() {
|
|
3991
4107
|
try {
|
|
3992
|
-
const w = JSON.parse(
|
|
4108
|
+
const w = JSON.parse(readFileSync11(WORKSPACE_PATH, "utf8"));
|
|
3993
4109
|
return w && typeof w.id === "string" && w.id ? w : null;
|
|
3994
4110
|
} catch {
|
|
3995
4111
|
return null;
|
|
@@ -4012,7 +4128,7 @@ var init_workspace = __esm({
|
|
|
4012
4128
|
"src/workspace.ts"() {
|
|
4013
4129
|
"use strict";
|
|
4014
4130
|
init_config();
|
|
4015
|
-
WORKSPACE_PATH =
|
|
4131
|
+
WORKSPACE_PATH = join18(SWARM_HOME, "workspace.json");
|
|
4016
4132
|
}
|
|
4017
4133
|
});
|
|
4018
4134
|
|
|
@@ -4430,14 +4546,14 @@ var init_daemonLink = __esm({
|
|
|
4430
4546
|
});
|
|
4431
4547
|
|
|
4432
4548
|
// src/pauseState.ts
|
|
4433
|
-
import { existsSync as existsSync11, readFileSync as
|
|
4434
|
-
import { join as
|
|
4549
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
|
|
4550
|
+
import { join as join19 } from "path";
|
|
4435
4551
|
function pauseStatePath(home) {
|
|
4436
|
-
return
|
|
4552
|
+
return join19(home, "pause.json");
|
|
4437
4553
|
}
|
|
4438
4554
|
function loadPauseState(home, io = {}) {
|
|
4439
4555
|
const exists = io.exists ?? existsSync11;
|
|
4440
|
-
const readFile2 = io.readFile ?? ((p) =>
|
|
4556
|
+
const readFile2 = io.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
4441
4557
|
const path = pauseStatePath(home);
|
|
4442
4558
|
if (!exists(path)) return ACTIVE;
|
|
4443
4559
|
try {
|
|
@@ -4498,10 +4614,10 @@ var init_pauseState = __esm({
|
|
|
4498
4614
|
});
|
|
4499
4615
|
|
|
4500
4616
|
// src/heartbeat.ts
|
|
4501
|
-
import { readFileSync as
|
|
4617
|
+
import { readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
4502
4618
|
function readHeartbeat(path) {
|
|
4503
4619
|
try {
|
|
4504
|
-
const ms = Number(
|
|
4620
|
+
const ms = Number(readFileSync13(path, "utf8").trim());
|
|
4505
4621
|
return Number.isFinite(ms) ? ms : null;
|
|
4506
4622
|
} catch {
|
|
4507
4623
|
return null;
|
|
@@ -4563,6 +4679,7 @@ function rowToRun(row) {
|
|
|
4563
4679
|
tokens_in: row.tokens_in ?? 0,
|
|
4564
4680
|
tokens_out: row.tokens_out ?? 0,
|
|
4565
4681
|
duration_ms: row.duration_ms ?? 0,
|
|
4682
|
+
cost_unmeasured: row.cost_unmeasured ?? 0,
|
|
4566
4683
|
pr_url: row.pr_url ?? null,
|
|
4567
4684
|
// TASWM-506: файлы, занятые прогоном с открытым PR — JSON-массив, как depends_on.
|
|
4568
4685
|
changed_paths: row.changed_paths ? JSON.parse(row.changed_paths) : [],
|
|
@@ -4617,6 +4734,7 @@ var init_db = __esm({
|
|
|
4617
4734
|
"use strict";
|
|
4618
4735
|
init_config();
|
|
4619
4736
|
init_names2();
|
|
4737
|
+
init_zoneNotes();
|
|
4620
4738
|
init_storm();
|
|
4621
4739
|
init_src();
|
|
4622
4740
|
TERMINAL_SQL = TERMINAL_STATUSES.map((s) => `'${s}'`).join(",");
|
|
@@ -4642,6 +4760,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
4642
4760
|
tokens_in INTEGER NOT NULL DEFAULT 0,
|
|
4643
4761
|
tokens_out INTEGER NOT NULL DEFAULT 0,
|
|
4644
4762
|
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
4763
|
+
cost_unmeasured INTEGER NOT NULL DEFAULT 0,
|
|
4645
4764
|
pr_url TEXT,
|
|
4646
4765
|
superseded_by INTEGER,
|
|
4647
4766
|
decision TEXT,
|
|
@@ -4757,6 +4876,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4757
4876
|
"ALTER TABLE runs ADD COLUMN tokens_in INTEGER NOT NULL DEFAULT 0",
|
|
4758
4877
|
"ALTER TABLE runs ADD COLUMN tokens_out INTEGER NOT NULL DEFAULT 0",
|
|
4759
4878
|
"ALTER TABLE runs ADD COLUMN duration_ms INTEGER NOT NULL DEFAULT 0",
|
|
4879
|
+
// TASWM-548: сколько запусков прогона остались без снятой цены — отличает «бесплатно»
|
|
4880
|
+
// от «не измерено». Прогоны до фичи получают 0: их нули мы перепроверить уже не можем,
|
|
4881
|
+
// а задним числом объявлять их неизмеренными значило бы утверждать непроверенное.
|
|
4882
|
+
"ALTER TABLE runs ADD COLUMN cost_unmeasured INTEGER NOT NULL DEFAULT 0",
|
|
4760
4883
|
// TASWM-395: усилие рассуждения прогона (`claude --effort`). Nullable без DEFAULT —
|
|
4761
4884
|
// NULL это и все прогоны до фичи, и прогоны на дефолте харнесса.
|
|
4762
4885
|
"ALTER TABLE runs ADD COLUMN effort TEXT",
|
|
@@ -4818,7 +4941,10 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4818
4941
|
// отряд героя (задел иерархии, TASWM-78)
|
|
4819
4942
|
normalizeRepo(input.repo || this.defaultRepo),
|
|
4820
4943
|
// канон при записи → один area-lock на физрепо
|
|
4821
|
-
|
|
4944
|
+
// TASWM-541 (по ревью): зона — тоже канон при записи, ровно по образцу repo строкой
|
|
4945
|
+
// выше. Лок сравнивает строки как есть, поэтому «MCP» из ручного `swarm spawn` и
|
|
4946
|
+
// «mcp» из area:*-лейбла были бы ДВУМЯ зонами — и защиты в смешанном сценарии ноль.
|
|
4947
|
+
normalizeZoneLabel(input.area) || null,
|
|
4822
4948
|
input.depends_on && input.depends_on.length ? JSON.stringify(input.depends_on) : null,
|
|
4823
4949
|
input.model ?? null,
|
|
4824
4950
|
input.review_model ?? null,
|
|
@@ -4922,6 +5048,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
4922
5048
|
const arr = v;
|
|
4923
5049
|
return arr && arr.length ? JSON.stringify(arr) : null;
|
|
4924
5050
|
}
|
|
5051
|
+
if (f === "area" && typeof v === "string") return normalizeZoneLabel(v) || null;
|
|
4925
5052
|
return v;
|
|
4926
5053
|
});
|
|
4927
5054
|
this.db.prepare(`UPDATE runs SET ${set}, updated_at = datetime('now') WHERE id = ?`).run(...vals, id);
|
|
@@ -5077,7 +5204,7 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5077
5204
|
runningAreas(repo) {
|
|
5078
5205
|
const base3 = "SELECT DISTINCT area FROM runs WHERE status IN ('running','blocked') AND area IS NOT NULL";
|
|
5079
5206
|
const rows = repo === void 0 ? this.db.prepare(base3).all() : this.db.prepare(`${base3} AND repo = ?`).all(repo);
|
|
5080
|
-
return rows.map((r) => r.area);
|
|
5207
|
+
return rows.map((r) => normalizeZoneLabel(r.area)).filter((a) => a.length > 0);
|
|
5081
5208
|
}
|
|
5082
5209
|
/**
|
|
5083
5210
|
* Из depends_on прогона — слаги зависимостей, ещё НЕ исчерпанных. Пусто → все выполнены,
|
|
@@ -5470,8 +5597,8 @@ CREATE TABLE IF NOT EXISTS imports (
|
|
|
5470
5597
|
});
|
|
5471
5598
|
|
|
5472
5599
|
// src/repoScan.ts
|
|
5473
|
-
import { readdirSync as
|
|
5474
|
-
import { join as
|
|
5600
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync10 } from "fs";
|
|
5601
|
+
import { join as join20 } from "path";
|
|
5475
5602
|
var init_repoScan = __esm({
|
|
5476
5603
|
"src/repoScan.ts"() {
|
|
5477
5604
|
"use strict";
|
|
@@ -5480,9 +5607,9 @@ var init_repoScan = __esm({
|
|
|
5480
5607
|
});
|
|
5481
5608
|
|
|
5482
5609
|
// src/repoDiscovery.ts
|
|
5483
|
-
import { homedir as
|
|
5484
|
-
import { readFileSync as
|
|
5485
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5610
|
+
import { homedir as homedir6 } from "os";
|
|
5611
|
+
import { readFileSync as readFileSync15, renameSync as renameSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
5612
|
+
import { isAbsolute as isAbsolute3, join as join21, resolve as resolve3 } from "path";
|
|
5486
5613
|
function repoMapLines(rows) {
|
|
5487
5614
|
if (!rows?.length) return [];
|
|
5488
5615
|
const missing = rows.filter((r) => !r.path).length;
|
|
@@ -5497,7 +5624,7 @@ function repoMapLines(rows) {
|
|
|
5497
5624
|
}
|
|
5498
5625
|
function loadRepoMapCache(path) {
|
|
5499
5626
|
try {
|
|
5500
|
-
const raw = JSON.parse(
|
|
5627
|
+
const raw = JSON.parse(readFileSync15(path, "utf8"));
|
|
5501
5628
|
if (!Array.isArray(raw?.entries)) return { scannedAt: null, entries: [] };
|
|
5502
5629
|
return {
|
|
5503
5630
|
scannedAt: typeof raw.scannedAt === "string" && raw.scannedAt ? raw.scannedAt : null,
|
|
@@ -5866,11 +5993,11 @@ __export(statusCli_exports, {
|
|
|
5866
5993
|
probeLink: () => probeLink,
|
|
5867
5994
|
runStatusCli: () => runStatusCli
|
|
5868
5995
|
});
|
|
5869
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5996
|
+
import { existsSync as existsSync13, readFileSync as readFileSync16 } from "fs";
|
|
5870
5997
|
function outboxPending(path) {
|
|
5871
5998
|
try {
|
|
5872
5999
|
if (!existsSync13(path)) return 0;
|
|
5873
|
-
return
|
|
6000
|
+
return readFileSync16(path, "utf8").split("\n").filter((l) => l.trim()).length;
|
|
5874
6001
|
} catch {
|
|
5875
6002
|
return null;
|
|
5876
6003
|
}
|
|
@@ -6077,11 +6204,11 @@ var init_prodMergeGate = __esm({
|
|
|
6077
6204
|
});
|
|
6078
6205
|
|
|
6079
6206
|
// src/platform.ts
|
|
6080
|
-
import { existsSync as existsSync14, readFileSync as
|
|
6081
|
-
import { delimiter, join as
|
|
6207
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
6208
|
+
import { delimiter, join as join22 } from "path";
|
|
6082
6209
|
function safeReadText(path) {
|
|
6083
6210
|
try {
|
|
6084
|
-
return
|
|
6211
|
+
return readFileSync17(path, "utf8");
|
|
6085
6212
|
} catch {
|
|
6086
6213
|
return null;
|
|
6087
6214
|
}
|
|
@@ -6092,7 +6219,7 @@ function resolveCommandPath(name, env = process.env, platform = process.platform
|
|
|
6092
6219
|
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
|
|
6093
6220
|
for (const dir of dirs) {
|
|
6094
6221
|
for (const ext of exts) {
|
|
6095
|
-
const candidate =
|
|
6222
|
+
const candidate = join22(dir, name + ext);
|
|
6096
6223
|
if (existsSync14(candidate)) return candidate;
|
|
6097
6224
|
}
|
|
6098
6225
|
}
|
|
@@ -6197,12 +6324,12 @@ var init_platform = __esm({
|
|
|
6197
6324
|
|
|
6198
6325
|
// src/envGate.ts
|
|
6199
6326
|
import { existsSync as existsSync15 } from "fs";
|
|
6200
|
-
import { join as
|
|
6327
|
+
import { join as join23 } from "path";
|
|
6201
6328
|
import { createConnection } from "net";
|
|
6202
6329
|
import { execa as execa3 } from "execa";
|
|
6203
6330
|
function detectRequirements(repo, io = {}) {
|
|
6204
6331
|
const exists = io.exists ?? existsSync15;
|
|
6205
|
-
const marker = COMPOSE_MARKERS.find((f) => exists(
|
|
6332
|
+
const marker = COMPOSE_MARKERS.find((f) => exists(join23(repo, f)));
|
|
6206
6333
|
return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
|
|
6207
6334
|
}
|
|
6208
6335
|
async function dockerAlive() {
|
|
@@ -6234,7 +6361,7 @@ var init_envGate = __esm({
|
|
|
6234
6361
|
});
|
|
6235
6362
|
|
|
6236
6363
|
// src/doctor.ts
|
|
6237
|
-
import { isAbsolute as isAbsolute4, join as
|
|
6364
|
+
import { isAbsolute as isAbsolute4, join as join24 } from "path";
|
|
6238
6365
|
function ok(id, title, detail) {
|
|
6239
6366
|
return { id, title, status: "ok", detail, remedies: [], critical: false };
|
|
6240
6367
|
}
|
|
@@ -6441,7 +6568,7 @@ function checkRepos(io, snap) {
|
|
|
6441
6568
|
critical: true
|
|
6442
6569
|
};
|
|
6443
6570
|
}
|
|
6444
|
-
if (env && !io.exists(
|
|
6571
|
+
if (env && !io.exists(join24(env, ".git"))) {
|
|
6445
6572
|
return {
|
|
6446
6573
|
id: "repos",
|
|
6447
6574
|
title,
|
|
@@ -6451,7 +6578,7 @@ function checkRepos(io, snap) {
|
|
|
6451
6578
|
critical: true
|
|
6452
6579
|
};
|
|
6453
6580
|
}
|
|
6454
|
-
const broken = snap.paths.filter((p) => !io.exists(
|
|
6581
|
+
const broken = snap.paths.filter((p) => !io.exists(join24(p, ".git")));
|
|
6455
6582
|
if (broken.length) {
|
|
6456
6583
|
return {
|
|
6457
6584
|
id: "repos",
|
|
@@ -6641,12 +6768,12 @@ var init_doctor = __esm({
|
|
|
6641
6768
|
});
|
|
6642
6769
|
|
|
6643
6770
|
// src/repoMemory.ts
|
|
6644
|
-
import { readFileSync as
|
|
6645
|
-
import { join as
|
|
6771
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
|
|
6772
|
+
import { join as join25 } from "path";
|
|
6646
6773
|
function loadRememberedRepos(home) {
|
|
6647
6774
|
let raw;
|
|
6648
6775
|
try {
|
|
6649
|
-
raw =
|
|
6776
|
+
raw = readFileSync18(join25(home, FILE), "utf8");
|
|
6650
6777
|
} catch {
|
|
6651
6778
|
return [];
|
|
6652
6779
|
}
|
|
@@ -6965,8 +7092,8 @@ var init_updateCli = __esm({
|
|
|
6965
7092
|
|
|
6966
7093
|
// src/cli.ts
|
|
6967
7094
|
init_cliIo();
|
|
6968
|
-
import { basename as basename6, dirname as dirname5, join as
|
|
6969
|
-
import { existsSync as existsSync17, readFileSync as
|
|
7095
|
+
import { basename as basename6, dirname as dirname5, join as join26 } from "path";
|
|
7096
|
+
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";
|
|
6970
7097
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
6971
7098
|
|
|
6972
7099
|
// src/swarmClient.ts
|
|
@@ -6974,7 +7101,7 @@ init_config();
|
|
|
6974
7101
|
init_resources();
|
|
6975
7102
|
init_names2();
|
|
6976
7103
|
init_storm();
|
|
6977
|
-
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as
|
|
7104
|
+
import { appendFileSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
6978
7105
|
import { dirname as dirname2 } from "path";
|
|
6979
7106
|
import { randomUUID } from "crypto";
|
|
6980
7107
|
|
|
@@ -7002,22 +7129,23 @@ function parsePendingRepoRules(raw) {
|
|
|
7002
7129
|
}
|
|
7003
7130
|
|
|
7004
7131
|
// src/swarmClient.ts
|
|
7132
|
+
init_zoneNotes();
|
|
7005
7133
|
init_toolFailure();
|
|
7006
7134
|
|
|
7007
7135
|
// src/logTail.ts
|
|
7008
|
-
import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as
|
|
7009
|
-
import { join as
|
|
7136
|
+
import { closeSync, existsSync as existsSync5, openSync, readSync, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
7137
|
+
import { join as join9 } from "path";
|
|
7010
7138
|
|
|
7011
7139
|
// src/runLogDir.ts
|
|
7012
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as
|
|
7013
|
-
import { join as
|
|
7140
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync as readdirSync5, renameSync as renameSync2, statSync as statSync4 } from "fs";
|
|
7141
|
+
import { join as join8 } from "path";
|
|
7014
7142
|
function runLogDirName(runLogKey) {
|
|
7015
7143
|
const safe = runLogKey.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
7016
7144
|
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)}`);
|
|
7017
7145
|
return /^\d+$/.test(safe) ? `k-${safe}` : safe;
|
|
7018
7146
|
}
|
|
7019
7147
|
function runLogDirPath(runLogsDir, runLogKey) {
|
|
7020
|
-
return
|
|
7148
|
+
return join8(runLogsDir, runLogDirName(runLogKey));
|
|
7021
7149
|
}
|
|
7022
7150
|
function findRunLogDir(runLogsDir, keys) {
|
|
7023
7151
|
for (const key of keys) {
|
|
@@ -7229,13 +7357,13 @@ var SwarmClient = class _SwarmClient {
|
|
|
7229
7357
|
recover() {
|
|
7230
7358
|
if (existsSync6(this.mapPath)) {
|
|
7231
7359
|
try {
|
|
7232
|
-
const obj = JSON.parse(
|
|
7360
|
+
const obj = JSON.parse(readFileSync6(this.mapPath, "utf8"));
|
|
7233
7361
|
for (const [k, v] of Object.entries(obj)) this.prodIdByKey.set(k, v);
|
|
7234
7362
|
} catch {
|
|
7235
7363
|
}
|
|
7236
7364
|
}
|
|
7237
7365
|
if (existsSync6(this.outboxPath)) {
|
|
7238
|
-
const txt =
|
|
7366
|
+
const txt = readFileSync6(this.outboxPath, "utf8");
|
|
7239
7367
|
for (const line of txt.split("\n")) {
|
|
7240
7368
|
const s = line.trim();
|
|
7241
7369
|
if (!s) continue;
|
|
@@ -7258,7 +7386,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7258
7386
|
if (!existsSync6(this.stormPath)) return;
|
|
7259
7387
|
let txt;
|
|
7260
7388
|
try {
|
|
7261
|
-
txt =
|
|
7389
|
+
txt = readFileSync6(this.stormPath, "utf8");
|
|
7262
7390
|
} catch {
|
|
7263
7391
|
return;
|
|
7264
7392
|
}
|
|
@@ -7333,7 +7461,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7333
7461
|
const out = {};
|
|
7334
7462
|
if ("status" in patch && patch.status !== void 0) out.status = patch.status;
|
|
7335
7463
|
if (patch.status === "running") out.executor = this.executorId;
|
|
7336
|
-
if ("area" in patch) out.area = patch.area
|
|
7464
|
+
if ("area" in patch) out.area = normalizeZoneLabel(patch.area) || null;
|
|
7337
7465
|
if ("model" in patch) out.model = patch.model ?? null;
|
|
7338
7466
|
if ("review_model" in patch) out.reviewModel = patch.review_model ?? null;
|
|
7339
7467
|
if ("effort" in patch) out.effort = patch.effort ?? null;
|
|
@@ -7342,6 +7470,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
7342
7470
|
if ("tokens_in" in patch && patch.tokens_in !== void 0) out.tokensIn = patch.tokens_in;
|
|
7343
7471
|
if ("tokens_out" in patch && patch.tokens_out !== void 0) out.tokensOut = patch.tokens_out;
|
|
7344
7472
|
if ("duration_ms" in patch && patch.duration_ms !== void 0) out.durationMs = patch.duration_ms;
|
|
7473
|
+
if ("cost_unmeasured" in patch && patch.cost_unmeasured !== void 0) out.costUnmeasured = patch.cost_unmeasured;
|
|
7345
7474
|
if ("respawns" in patch && patch.respawns !== void 0) out.respawns = patch.respawns;
|
|
7346
7475
|
if ("pr_url" in patch) out.prUrl = patch.pr_url ?? null;
|
|
7347
7476
|
if ("changed_paths" in patch && patch.changed_paths) out.changedPaths = patch.changed_paths;
|
|
@@ -7408,7 +7537,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7408
7537
|
repo: normalizeRepo(input.repo || this.defaultRepo),
|
|
7409
7538
|
branch: null,
|
|
7410
7539
|
status: "queued",
|
|
7411
|
-
area: input.area
|
|
7540
|
+
area: normalizeZoneLabel(input.area) || null,
|
|
7541
|
+
// канон при записи (TASWM-541) — как в SwarmDb.createRun
|
|
7412
7542
|
depends_on: input.depends_on && input.depends_on.length ? input.depends_on : [],
|
|
7413
7543
|
model: input.model ?? null,
|
|
7414
7544
|
// TASWM-451: чем проверять эту работу. У прогона исполнителя приезжает из задачи
|
|
@@ -7423,6 +7553,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
7423
7553
|
// копятся учётом запусков claude (cost.ts), как cost_usd — TASWM-353
|
|
7424
7554
|
tokens_out: 0,
|
|
7425
7555
|
duration_ms: 0,
|
|
7556
|
+
cost_unmeasured: 0,
|
|
7557
|
+
// запусков без снятой цены (TASWM-548) — копится тем же учётом
|
|
7426
7558
|
pr_url: null,
|
|
7427
7559
|
changed_paths: [],
|
|
7428
7560
|
// файлы прогона снимаются при открытии PR (TASWM-506)
|
|
@@ -9043,6 +9175,8 @@ var SwarmClient = class _SwarmClient {
|
|
|
9043
9175
|
// токены и время работы (TASWM-353) — прод-поля, заполняются ниже
|
|
9044
9176
|
tokens_out: 0,
|
|
9045
9177
|
duration_ms: 0,
|
|
9178
|
+
cost_unmeasured: 0,
|
|
9179
|
+
// запусков без снятой цены (TASWM-548) — прод-поле, заполняется ниже
|
|
9046
9180
|
pr_url: null,
|
|
9047
9181
|
changed_paths: [],
|
|
9048
9182
|
// занятые файлы (TASWM-506) — прод-поле, заполняется ниже
|
|
@@ -9115,7 +9249,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
9115
9249
|
local.base_branch = pr.baseBranch ?? null;
|
|
9116
9250
|
local.workspace_id = pr.workspaceId ?? local.workspace_id ?? null;
|
|
9117
9251
|
local.status = pr.status ?? local.status;
|
|
9118
|
-
local.area = pr.area
|
|
9252
|
+
local.area = normalizeZoneLabel(pr.area) || null;
|
|
9119
9253
|
local.depends_on = Array.isArray(pr.dependsOn) ? pr.dependsOn : [];
|
|
9120
9254
|
local.model = pr.model ?? null;
|
|
9121
9255
|
local.review_model = pr.reviewModel ?? null;
|
|
@@ -9124,6 +9258,7 @@ var SwarmClient = class _SwarmClient {
|
|
|
9124
9258
|
local.tokens_in = typeof pr.tokensIn === "number" ? pr.tokensIn : 0;
|
|
9125
9259
|
local.tokens_out = typeof pr.tokensOut === "number" ? pr.tokensOut : 0;
|
|
9126
9260
|
local.duration_ms = typeof pr.durationMs === "number" ? pr.durationMs : 0;
|
|
9261
|
+
local.cost_unmeasured = typeof pr.costUnmeasured === "number" ? pr.costUnmeasured : 0;
|
|
9127
9262
|
local.pr_url = pr.prUrl ?? null;
|
|
9128
9263
|
local.changed_paths = Array.isArray(pr.changedPaths) ? pr.changedPaths : local.changed_paths ?? [];
|
|
9129
9264
|
local.stop_reason = pr.stopReason ?? null;
|
|
@@ -9521,7 +9656,7 @@ init_config();
|
|
|
9521
9656
|
import { $ as $6 } from "execa";
|
|
9522
9657
|
|
|
9523
9658
|
// src/daemonLaunch.ts
|
|
9524
|
-
import { openSync as openSync2, closeSync as closeSync2, readFileSync as
|
|
9659
|
+
import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync7 } from "fs";
|
|
9525
9660
|
import { spawn } from "child_process";
|
|
9526
9661
|
var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
|
|
9527
9662
|
function parseStartMode(argv) {
|
|
@@ -9581,18 +9716,18 @@ function launchDetached(opts) {
|
|
|
9581
9716
|
}
|
|
9582
9717
|
function tailFile(path, lines) {
|
|
9583
9718
|
try {
|
|
9584
|
-
return tailText(
|
|
9719
|
+
return tailText(readFileSync7(path, "utf8"), lines);
|
|
9585
9720
|
} catch {
|
|
9586
9721
|
return "";
|
|
9587
9722
|
}
|
|
9588
9723
|
}
|
|
9589
9724
|
|
|
9590
9725
|
// src/logRotate.ts
|
|
9591
|
-
import { copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as
|
|
9726
|
+
import { copyFileSync as copyFileSync2, renameSync as renameSync3, statSync as statSync6, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
|
|
9592
9727
|
var realIo = {
|
|
9593
9728
|
size: (path) => {
|
|
9594
9729
|
try {
|
|
9595
|
-
return
|
|
9730
|
+
return statSync6(path).size;
|
|
9596
9731
|
} catch {
|
|
9597
9732
|
return null;
|
|
9598
9733
|
}
|
|
@@ -9629,15 +9764,15 @@ init_repoRegistry();
|
|
|
9629
9764
|
|
|
9630
9765
|
// src/hostIdentity.ts
|
|
9631
9766
|
import { hostname } from "os";
|
|
9632
|
-
import { existsSync as existsSync8, readFileSync as
|
|
9633
|
-
import { join as
|
|
9767
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
9768
|
+
import { join as join10 } from "path";
|
|
9634
9769
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9635
9770
|
function hostIdentityPath(home) {
|
|
9636
|
-
return
|
|
9771
|
+
return join10(home, "host.json");
|
|
9637
9772
|
}
|
|
9638
9773
|
function loadHostIdentity(deps) {
|
|
9639
9774
|
const exists = deps.exists ?? existsSync8;
|
|
9640
|
-
const readFile2 = deps.readFile ?? ((p) =>
|
|
9775
|
+
const readFile2 = deps.readFile ?? ((p) => readFileSync8(p, "utf8"));
|
|
9641
9776
|
const writeFile3 = deps.writeFile ?? ((p, data) => {
|
|
9642
9777
|
writeFileSync6(`${p}.tmp`, data);
|
|
9643
9778
|
renameSync4(`${p}.tmp`, p);
|
|
@@ -9754,8 +9889,8 @@ import { basename as basename4 } from "path";
|
|
|
9754
9889
|
import { serve as serve2 } from "@hono/node-server";
|
|
9755
9890
|
|
|
9756
9891
|
// src/diff.ts
|
|
9757
|
-
import { statSync as
|
|
9758
|
-
import { join as
|
|
9892
|
+
import { statSync as statSync7 } from "fs";
|
|
9893
|
+
import { join as join11 } from "path";
|
|
9759
9894
|
import { $ as $3 } from "execa";
|
|
9760
9895
|
var MAX_FILES = 200;
|
|
9761
9896
|
var MAX_LINES_PER_FILE = 1500;
|
|
@@ -9898,7 +10033,7 @@ async function resolveBase(wt, baseBranch) {
|
|
|
9898
10033
|
}
|
|
9899
10034
|
function mtime(path) {
|
|
9900
10035
|
try {
|
|
9901
|
-
return
|
|
10036
|
+
return statSync7(path).mtimeMs;
|
|
9902
10037
|
} catch {
|
|
9903
10038
|
return 0;
|
|
9904
10039
|
}
|
|
@@ -9923,7 +10058,7 @@ async function collectRunDiff(worktree, opts = {}) {
|
|
|
9923
10058
|
files.push(pf);
|
|
9924
10059
|
}
|
|
9925
10060
|
}
|
|
9926
|
-
files.sort((a, b) => mtime(
|
|
10061
|
+
files.sort((a, b) => mtime(join11(worktree, b.path)) - mtime(join11(worktree, a.path)));
|
|
9927
10062
|
const truncatedFiles = files.length > MAX_FILES;
|
|
9928
10063
|
if (truncatedFiles) files = files.slice(0, MAX_FILES);
|
|
9929
10064
|
const added = files.reduce((s, f) => s + f.added, 0);
|
|
@@ -11814,15 +11949,15 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
|
|
|
11814
11949
|
|
|
11815
11950
|
// src/launcher.ts
|
|
11816
11951
|
import { spawn as spawn2 } from "child_process";
|
|
11817
|
-
import { closeSync as closeSync3, existsSync as existsSync9, mkdirSync as mkdirSync9, openSync as openSync3, readdirSync as
|
|
11952
|
+
import { closeSync as closeSync3, existsSync as existsSync9, mkdirSync as mkdirSync9, openSync as openSync3, readdirSync as readdirSync8 } from "fs";
|
|
11818
11953
|
import { createRequire } from "module";
|
|
11819
|
-
import { homedir as
|
|
11820
|
-
import { dirname as dirname4, join as
|
|
11954
|
+
import { homedir as homedir5 } from "os";
|
|
11955
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
11821
11956
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11822
11957
|
|
|
11823
11958
|
// src/droneHome.ts
|
|
11824
|
-
import { lstatSync, mkdirSync as mkdirSync8, readdirSync as
|
|
11825
|
-
import { join as
|
|
11959
|
+
import { lstatSync, mkdirSync as mkdirSync8, readdirSync as readdirSync7, rmSync, statSync as statSync8, symlinkSync as symlinkSync2 } from "fs";
|
|
11960
|
+
import { join as join13, relative, sep } from "path";
|
|
11826
11961
|
var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
|
|
11827
11962
|
function droneHomeDeny(env = process.env) {
|
|
11828
11963
|
const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -11841,28 +11976,28 @@ function buildDroneHome(opts) {
|
|
|
11841
11976
|
const deny = opts.deny ?? droneHomeDeny();
|
|
11842
11977
|
const platform = opts.platform ?? process.platform;
|
|
11843
11978
|
mkdirSync8(droneHome, { recursive: true, mode: 448 });
|
|
11844
|
-
const wanted = new Set(mirroredEntries(
|
|
11979
|
+
const wanted = new Set(mirroredEntries(readdirSync7(realHome), deny, platform));
|
|
11845
11980
|
const rel = relative(realHome, droneHome);
|
|
11846
11981
|
if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
|
|
11847
|
-
for (const existing of
|
|
11982
|
+
for (const existing of readdirSync7(droneHome)) {
|
|
11848
11983
|
if (wanted.has(existing)) continue;
|
|
11849
|
-
const p =
|
|
11984
|
+
const p = join13(droneHome, existing);
|
|
11850
11985
|
try {
|
|
11851
11986
|
if (lstatSync(p).isSymbolicLink()) rmSync(p);
|
|
11852
11987
|
} catch {
|
|
11853
11988
|
}
|
|
11854
11989
|
}
|
|
11855
11990
|
for (const name of wanted) {
|
|
11856
|
-
const link =
|
|
11991
|
+
const link = join13(droneHome, name);
|
|
11857
11992
|
try {
|
|
11858
11993
|
lstatSync(link);
|
|
11859
11994
|
continue;
|
|
11860
11995
|
} catch {
|
|
11861
11996
|
}
|
|
11862
|
-
const target =
|
|
11997
|
+
const target = join13(realHome, name);
|
|
11863
11998
|
let isDir;
|
|
11864
11999
|
try {
|
|
11865
|
-
isDir =
|
|
12000
|
+
isDir = statSync8(target).isDirectory();
|
|
11866
12001
|
} catch {
|
|
11867
12002
|
continue;
|
|
11868
12003
|
}
|
|
@@ -11880,7 +12015,7 @@ var REAL_PATH_ENV_PINS = [
|
|
|
11880
12015
|
{ env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
|
|
11881
12016
|
];
|
|
11882
12017
|
function realPathEnvPins(realHome) {
|
|
11883
|
-
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env,
|
|
12018
|
+
return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join13(realHome, ...segments)]));
|
|
11884
12019
|
}
|
|
11885
12020
|
function dronePrivatePaths(realHome, swarmHome) {
|
|
11886
12021
|
return [
|
|
@@ -11992,7 +12127,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
|
|
|
11992
12127
|
const env = { ...base3 };
|
|
11993
12128
|
const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
|
|
11994
12129
|
if (isolate && !base3.TURBO_CACHE_DIR) {
|
|
11995
|
-
env.TURBO_CACHE_DIR =
|
|
12130
|
+
env.TURBO_CACHE_DIR = join14(worktree, ".turbo", "cache");
|
|
11996
12131
|
}
|
|
11997
12132
|
Object.assign(env, droneTestEnv(droneCap, base3));
|
|
11998
12133
|
for (const key of DAEMON_STATE_ENV) delete env[key];
|
|
@@ -12020,10 +12155,10 @@ var DAEMON_STATE_ENV = [
|
|
|
12020
12155
|
"TASKLESS_TOKEN"
|
|
12021
12156
|
];
|
|
12022
12157
|
function bundledRoleDir(kind) {
|
|
12023
|
-
return
|
|
12158
|
+
return join14(dirname4(fileURLToPath2(import.meta.url)), "..", "roles", kind);
|
|
12024
12159
|
}
|
|
12025
12160
|
function roleDir(worktree, kind) {
|
|
12026
|
-
return
|
|
12161
|
+
return join14(worktree, ".claude", "roles", kind);
|
|
12027
12162
|
}
|
|
12028
12163
|
function roleDirs(worktree, kind) {
|
|
12029
12164
|
return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync9(dir));
|
|
@@ -12034,7 +12169,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
12034
12169
|
mkdirSync9(dir, { recursive: true });
|
|
12035
12170
|
let files = [];
|
|
12036
12171
|
try {
|
|
12037
|
-
files =
|
|
12172
|
+
files = readdirSync8(dir);
|
|
12038
12173
|
} catch {
|
|
12039
12174
|
}
|
|
12040
12175
|
let next = 0;
|
|
@@ -12042,7 +12177,7 @@ function nextRunLogPath(runLogsDir, runLogKeys) {
|
|
|
12042
12177
|
const m = /^(\d+)\.json/.exec(f);
|
|
12043
12178
|
if (m) next = Math.max(next, Number(m[1]) + 1);
|
|
12044
12179
|
}
|
|
12045
|
-
return
|
|
12180
|
+
return join14(dir, `${next}.json`);
|
|
12046
12181
|
}
|
|
12047
12182
|
function openNextRunLog(runLogsDir, runLogKeys) {
|
|
12048
12183
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -12123,11 +12258,11 @@ function makeLauncher(cfg) {
|
|
|
12123
12258
|
} catch (e) {
|
|
12124
12259
|
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)}`);
|
|
12125
12260
|
}
|
|
12126
|
-
const realHome = cfg.droneIsolation?.realHome ??
|
|
12261
|
+
const realHome = cfg.droneIsolation?.realHome ?? homedir5();
|
|
12127
12262
|
const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
|
|
12128
|
-
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(
|
|
12263
|
+
if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync9(join14(cfg.droneIsolation.swarmHome, "token"))) {
|
|
12129
12264
|
console.error(
|
|
12130
|
-
`[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 ${
|
|
12265
|
+
`[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.`
|
|
12131
12266
|
);
|
|
12132
12267
|
}
|
|
12133
12268
|
const executor = cfg.executor ?? resolveExecutor();
|
|
@@ -13033,12 +13168,12 @@ async function runDaemonLogs(argv) {
|
|
|
13033
13168
|
const head = tailFile(DAEMON_LOG_PATH, lines);
|
|
13034
13169
|
if (head) console.log(head);
|
|
13035
13170
|
if (!follow) return 0;
|
|
13036
|
-
let pos =
|
|
13171
|
+
let pos = statSync11(DAEMON_LOG_PATH).size;
|
|
13037
13172
|
for (; ; ) {
|
|
13038
13173
|
await sleep2(500);
|
|
13039
13174
|
let size;
|
|
13040
13175
|
try {
|
|
13041
|
-
size =
|
|
13176
|
+
size = statSync11(DAEMON_LOG_PATH).size;
|
|
13042
13177
|
} catch {
|
|
13043
13178
|
continue;
|
|
13044
13179
|
}
|
|
@@ -13310,7 +13445,7 @@ function effectiveWorktreesDir() {
|
|
|
13310
13445
|
exists: (d) => existsSync17(d),
|
|
13311
13446
|
isNonEmptyDir: (d) => {
|
|
13312
13447
|
try {
|
|
13313
|
-
return
|
|
13448
|
+
return readdirSync11(d).length > 0;
|
|
13314
13449
|
} catch {
|
|
13315
13450
|
return false;
|
|
13316
13451
|
}
|
|
@@ -13366,12 +13501,12 @@ async function scoutRepo(repo) {
|
|
|
13366
13501
|
await $6({ reject: false })`git -C ${worktree} reset --hard ${baseRef}`;
|
|
13367
13502
|
}
|
|
13368
13503
|
await setWorktreeIdentity(worktree, name);
|
|
13369
|
-
const abs = (rel) =>
|
|
13504
|
+
const abs = (rel) => join26(worktree, rel);
|
|
13370
13505
|
const code = await runRepoScout({
|
|
13371
13506
|
repoFullName,
|
|
13372
13507
|
repoPath: worktree,
|
|
13373
13508
|
name,
|
|
13374
|
-
readFile: (rel) => existsSync17(abs(rel)) ?
|
|
13509
|
+
readFile: (rel) => existsSync17(abs(rel)) ? readFileSync19(abs(rel), "utf8") : null,
|
|
13375
13510
|
writeFile: (rel, text) => {
|
|
13376
13511
|
mkdirSync10(dirname5(abs(rel)), { recursive: true });
|
|
13377
13512
|
writeFileSync14(abs(rel), text);
|