@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.
Files changed (3) hide show
  1. package/dist/cli.js +288 -153
  2. package/dist/overmind.js +780 -323
  3. 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/executors/claudeCode.ts
2022
- import { readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
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 transcriptDir(worktree, projectsDir) {
2057
- return join2(projectsDir, worktree.replace(/[/.]/g, "-"));
2140
+ function transcriptDir2(worktree, projectsDir) {
2141
+ return join3(projectsDir, worktree.replace(/[/.]/g, "-"));
2058
2142
  }
2059
- function sessionTranscriptExists(worktree, projectsDir = join2(homedir2(), ".claude", "projects")) {
2143
+ function sessionTranscriptExists(worktree, projectsDir = join3(homedir3(), ".claude", "projects")) {
2060
2144
  try {
2061
- return readdirSync2(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
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 = join2(homedir2(), ".claude", "projects")) {
2067
- const dir = transcriptDir(worktree, projectsDir);
2150
+ function transcriptMtimeMs(worktree, projectsDir = join3(homedir3(), ".claude", "projects")) {
2151
+ const dir = transcriptDir2(worktree, projectsDir);
2068
2152
  let files;
2069
2153
  try {
2070
- files = readdirSync2(dir);
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 = statSync(join2(dir, f)).mtimeMs;
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 = join2(spec.worktree, MCP_CONFIG_FILE);
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 homedir3 } from "os";
2162
- import { join as join3 } from "path";
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 || join3(homedir3(), ".taskless-swarm");
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 readdirSync3, statSync as statSync2, symlinkSync } from "fs";
2175
- import { join as join4 } from "path";
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 join4(swarmHome, "executor-state", "codex", worktree.replace(/[/.]/g, "-"));
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() || join4(realHome, ".codex");
2290
+ const real = (env.CODEX_HOME ?? "").trim() || join5(realHome, ".codex");
2186
2291
  for (const file of ["auth.json", "config.toml"]) {
2187
- const target = join4(real, file);
2188
- const link = join4(home, file);
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 = readdirSync3(dir);
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 = join4(dir, name);
2358
+ const full = join5(dir, name);
2254
2359
  let st;
2255
2360
  try {
2256
- st = statSync2(full);
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(join4(codexHomePath(worktree), "sessions")) !== null,
2328
- mtimeMs: (worktree) => newestRolloutMtime(join4(codexHomePath(worktree), "sessions"))
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 join5, resolve } from "path";
2376
- import { readFileSync as readFileSync2, realpathSync, renameSync, writeFileSync as writeFileSync3 } from "fs";
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 join5(droneWorktreeRoot(worktreesDir, repo), slug);
2495
+ return join6(droneWorktreeRoot(worktreesDir, repo), slug);
2391
2496
  }
2392
2497
  function droneWorktreeRoot(worktreesDir, repo) {
2393
- return join5(worktreesDir, basename(normalizeRepo(repo)));
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 readFileSync2(join5(SWARM_HOME, "token"), "utf8").trim();
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 || join5(SWARM_HOME, "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 || join5(SWARM_HOME, "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 = join5(SWARM_HOME, "worktrees.noindex");
2439
- WORKTREES_LEGACY_DIR = join5(SWARM_HOME, "worktrees");
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 || join5(SWARM_HOME, "run-logs");
2442
- DAEMON_LOG_PATH = process.env.SWARM_DAEMON_LOG || join5(SWARM_HOME, "overmind.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 || join5(SWARM_HOME, "outbox.jsonl");
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 || join5(SWARM_HOME, "env-cache");
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 || join5(SWARM_HOME, "drone-channel.key");
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 || join5(SWARM_HOME, "overmind.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 || join5(SWARM_HOME, "repo-map.json");
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 || join5(SWARM_HOME, "review-cutoff.json");
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 readFileSync3, statfsSync } from "fs";
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] = readFileSync3("/proc/sys/fs/file-nr", "utf8").trim().split(/\s+/).map(Number);
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 readFileSync4, writeFileSync as writeFileSync4, unlinkSync } from "fs";
2810
- import { join as join6 } from "path";
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(readFileSync4(STORE_PATH, "utf8"));
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 = join6(SWARM_HOME, "agent.json");
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.37",
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: "3b1a04ef992f6bba42847d1dc29d22e39cadfc25"
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 readFileSync8, writeFileSync as writeFileSync7 } from "fs";
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 = readFileSync8(path, "utf8").trim();
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 readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
3583
- import { join as join11 } from "path";
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(readFileSync9(STORE_PATH2, "utf8"));
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(readFileSync9(CLIENT_PATH, "utf8")).clientId;
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 = join11(SWARM_HOME, "oauth.json");
3719
- CLIENT_PATH = join11(SWARM_HOME, "oauth-client.json");
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 readdirSync8, statSync as statSync8 } from "fs";
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 join14, sep as sep2 } from "path";
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 statSync8(join14(dir, ".git")).isFile();
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 = join14(commonDir, "info");
3848
- const excludeFile = join14(infoDir, "exclude");
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 : join14(worktree, 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 = join14(gitDir, "swarm-hooks");
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(join14(hooksDir, hook), guardHookScript(hook, prev ? `${posix(prev)}/${hook}` : null));
3883
- await chmod(join14(hooksDir, hook), 493);
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 ? join14(commonDir, "hooks") : null;
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 join15 } from "path";
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 join16 } from "path";
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 readFileSync10, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4 } from "fs";
3989
- import { join as join17 } from "path";
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(readFileSync10(WORKSPACE_PATH, "utf8"));
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 = join17(SWARM_HOME, "workspace.json");
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 readFileSync11, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "fs";
4434
- import { join as join18 } from "path";
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 join18(home, "pause.json");
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) => readFileSync11(p, "utf8"));
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 readFileSync12, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
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(readFileSync12(path, "utf8").trim());
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
- input.area ?? null,
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 readdirSync9, readFileSync as readFileSync13, statSync as statSync9 } from "fs";
5474
- import { join as join19 } from "path";
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 homedir5 } from "os";
5484
- import { readFileSync as readFileSync14, renameSync as renameSync7, writeFileSync as writeFileSync12 } from "fs";
5485
- import { isAbsolute as isAbsolute3, join as join20, resolve as resolve3 } from "path";
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(readFileSync14(path, "utf8"));
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 readFileSync15 } from "fs";
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 readFileSync15(path, "utf8").split("\n").filter((l) => l.trim()).length;
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 readFileSync16 } from "fs";
6081
- import { delimiter, join as join21 } from "path";
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 readFileSync16(path, "utf8");
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 = join21(dir, name + ext);
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 join22 } from "path";
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(join22(repo, f)));
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 join23 } from "path";
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(join23(env, ".git"))) {
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(join23(p, ".git")));
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 readFileSync17, writeFileSync as writeFileSync13 } from "fs";
6645
- import { join as join24 } from "path";
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 = readFileSync17(join24(home, FILE), "utf8");
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 join25 } from "path";
6969
- import { existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync10, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
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 readFileSync5, writeFileSync as writeFileSync5 } from "fs";
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 readdirSync5, statSync as statSync4 } from "fs";
7009
- import { join as join8 } from "path";
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 readdirSync4, renameSync as renameSync2, statSync as statSync3 } from "fs";
7013
- import { join as join7 } from "path";
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 join7(runLogsDir, runLogDirName(runLogKey));
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(readFileSync5(this.mapPath, "utf8"));
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 = readFileSync5(this.outboxPath, "utf8");
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 = readFileSync5(this.stormPath, "utf8");
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 ?? null;
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 ?? null,
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 ?? null;
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 readFileSync6 } from "fs";
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(readFileSync6(path, "utf8"), lines);
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 statSync5, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "fs";
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 statSync5(path).size;
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 readFileSync7, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
9633
- import { join as join9 } from "path";
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 join9(home, "host.json");
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) => readFileSync7(p, "utf8"));
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 statSync6 } from "fs";
9758
- import { join as join10 } from "path";
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 statSync6(path).mtimeMs;
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(join10(worktree, b.path)) - mtime(join10(worktree, a.path)));
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 readdirSync7 } from "fs";
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 homedir4 } from "os";
11820
- import { dirname as dirname4, join as join13 } from "path";
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 readdirSync6, rmSync, statSync as statSync7, symlinkSync as symlinkSync2 } from "fs";
11825
- import { join as join12, relative, sep } from "path";
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(readdirSync6(realHome), deny, platform));
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 readdirSync6(droneHome)) {
11982
+ for (const existing of readdirSync7(droneHome)) {
11848
11983
  if (wanted.has(existing)) continue;
11849
- const p = join12(droneHome, existing);
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 = join12(droneHome, name);
11991
+ const link = join13(droneHome, name);
11857
11992
  try {
11858
11993
  lstatSync(link);
11859
11994
  continue;
11860
11995
  } catch {
11861
11996
  }
11862
- const target = join12(realHome, name);
11997
+ const target = join13(realHome, name);
11863
11998
  let isDir;
11864
11999
  try {
11865
- isDir = statSync7(target).isDirectory();
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, join12(realHome, ...segments)]));
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 = join13(worktree, ".turbo", "cache");
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 join13(dirname4(fileURLToPath2(import.meta.url)), "..", "roles", kind);
12158
+ return join14(dirname4(fileURLToPath2(import.meta.url)), "..", "roles", kind);
12024
12159
  }
12025
12160
  function roleDir(worktree, kind) {
12026
- return join13(worktree, ".claude", "roles", kind);
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 = readdirSync7(dir);
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 join13(dir, `${next}.json`);
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 ?? homedir4();
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(join13(cfg.droneIsolation.swarmHome, "token"))) {
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 ${join13(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.`
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 = statSync10(DAEMON_LOG_PATH).size;
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 = statSync10(DAEMON_LOG_PATH).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 readdirSync10(d).length > 0;
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) => join25(worktree, 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)) ? readFileSync18(abs(rel), "utf8") : null,
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);