@taskless-app/swarm 0.4.4 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ CLI, который поднимает у вас на машине **рой др
20
20
 
21
21
  ```bash
22
22
  npx @taskless-app/swarm login --workspace <slug> # войти в свой Taskless-воркспейс
23
- npx @taskless-app/swarm start --repo <путь к репо> # поднять демон роя
23
+ npx @taskless-app/swarm start # поднять демон роя
24
24
  ```
25
25
 
26
26
  Или один раз глобально:
@@ -28,10 +28,16 @@ npx @taskless-app/swarm start --repo <путь к репо> # поднят
28
28
  ```bash
29
29
  npm i -g @taskless-app/swarm
30
30
  swarm login --workspace <slug>
31
- swarm start --repo <путь к репо> # поднять рой: уходит в фон, терминал остаётся вам
31
+ swarm start # поднять рой: уходит в фон, терминал остаётся вам
32
32
  swarm --help # список остальных команд (spawn/ls/questions/answer/…)
33
33
  ```
34
34
 
35
+ **Пути к репозиториям указывать не нужно.** Демон сам находит клоны на машине: у
36
+ каждого в `.git/config` записан origin, и по нему клон сопоставляется с репозиторием
37
+ проекта в Taskless. Смотрит в привычных местах (`~/projects`, `~/code`, `~/src`,
38
+ `~/work`, …) на три уровня вглубь — на типовой машине это десятки миллисекунд. Клоны
39
+ лежат где-то ещё — скажите один раз, `swarm start --repo <путь>`, дальше запомнится.
40
+
35
41
  **Демон открепляется от терминала.** `swarm start` не занимает окно: процесс уходит
36
42
  в фон, а весь вывод пишется в `~/.taskless-swarm/overmind.log` — тот самый файл, из
37
43
  которого агент читает хвост лога удалённо. Посмотреть: `swarm logs -f`. Нужен вывод
package/dist/cli.js CHANGED
@@ -557,6 +557,7 @@ var DRONE_CHANNEL_GRACE_MS = envNum("SWARM_DRONE_CHANNEL_GRACE_MS", 12e4);
557
557
  var HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join(SWARM_HOME, "overmind.heartbeat");
558
558
  var HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
559
559
  var SWARM_REPO = process.env.SWARM_REPO || "";
560
+ var SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
560
561
  var SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
561
562
  var TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
562
563
  var SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
@@ -3480,7 +3481,7 @@ function pidsWithCwdUnder(dir, opts = {}) {
3480
3481
  if ((opts.platform ?? process.platform) === "win32") return winProcsUnder(dir, opts.procs ?? listProcs);
3481
3482
  let out;
3482
3483
  try {
3483
- out = execQuiet("lsof", ["-w", "-a", "-d", "cwd", "-F", "pn"], { maxBuffer: PROC_SNAPSHOT_MAX_BUFFER });
3484
+ out = execQuiet("lsof", ["-a", "-d", "cwd", "-F", "pn"], { maxBuffer: PROC_SNAPSHOT_MAX_BUFFER });
3484
3485
  } catch (e) {
3485
3486
  logTransientSpawnError("lsof", e);
3486
3487
  return [];
@@ -3940,7 +3941,7 @@ async function collectRunDiff(worktree, opts = {}) {
3940
3941
  // package.json
3941
3942
  var package_default = {
3942
3943
  name: "@taskless-app/swarm",
3943
- version: "0.4.4",
3944
+ version: "0.4.7",
3944
3945
  type: "module",
3945
3946
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3946
3947
  keywords: [
@@ -7277,7 +7278,9 @@ function usage() {
7277
7278
  swarm login [--workspace <slug|id>] [--no-open]
7278
7279
  \u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441
7279
7280
  swarm start [--repo <path>] [--foreground]
7280
- \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C, \u0441\u043F\u0430\u0432\u043D\u0438\u0442 \u0438 \u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u043E\u0432
7281
+ \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0440\u043E\u0439 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435: \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C, \u0441\u043F\u0430\u0432\u043D\u0438\u0442 \u0438 \u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u043E\u0432.
7282
+ \u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u043D\u0430\u0445\u043E\u0434\u0438\u0442 \u0441\u0430\u043C \u2014 \u043A\u043B\u043E\u043D\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 Taskless \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435;
7283
+ --repo \u043D\u0443\u0436\u0435\u043D, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u043B\u0435\u0436\u0430\u0442 \u0432 \u043D\u0435\u043E\u0431\u044B\u0447\u043D\u043E\u043C \u043C\u0435\u0441\u0442\u0435
7281
7284
  swarm status \u0436\u0438\u0432 \u043B\u0438 \u0434\u0435\u043C\u043E\u043D, \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u043B\u0438 \u0440\u043E\u0439, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0434\u0440\u043E\u043D\u043E\u0432 \u0432 \u0440\u0430\u0431\u043E\u0442\u0435
7282
7285
 
7283
7286
  \u0414\u0410\u0422\u042C \u0420\u041E\u042E \u0420\u0410\u0411\u041E\u0422\u0423
@@ -7422,14 +7425,11 @@ if (cmd === "start") {
7422
7425
  }
7423
7426
  const { repo: repoFlag } = takeRepo(startArgs);
7424
7427
  const repo = repoFlag ?? SWARM_REPO;
7425
- if (!repo) {
7426
- console.warn("SWARM_REPO \u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0431\u0435\u0437 \u044F\u0432\u043D\u043E\u0433\u043E repo \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u044F\u0442\u0441\u044F (\u043D\u0443\u0436\u0435\u043D spawn --repo).");
7427
- }
7428
7428
  if (!foreground) {
7429
7429
  process.exit(await runStartDetached(startArgs));
7430
7430
  }
7431
7431
  try {
7432
- await runDaemon({ repo });
7432
+ await runDaemon({ repo, repoFlag });
7433
7433
  } catch (e) {
7434
7434
  console.error(`\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F: ${e instanceof Error ? e.message : e}`);
7435
7435
  process.exit(1);
package/dist/overmind.js CHANGED
@@ -407,13 +407,13 @@ var init_slug = __esm({
407
407
  function extractText(d) {
408
408
  if (typeof d === "string") return d;
409
409
  const out = [];
410
- const walk = (n) => {
410
+ const walk2 = (n) => {
411
411
  if (!n || typeof n !== "object") return;
412
412
  const node = n;
413
413
  if (node.text) out.push(node.text);
414
- (node.content ?? []).forEach(walk);
414
+ (node.content ?? []).forEach(walk2);
415
415
  };
416
- walk(d);
416
+ walk2(d);
417
417
  return out.join(" ");
418
418
  }
419
419
  function sanitizeEventText(text) {
@@ -793,6 +793,7 @@ __export(config_exports, {
793
793
  SWARM_REPO: () => SWARM_REPO,
794
794
  SWARM_REPO_PATHS: () => SWARM_REPO_PATHS,
795
795
  SWARM_REVIEW_CAP: () => SWARM_REVIEW_CAP,
796
+ SWARM_SCAN_DEPTH: () => SWARM_SCAN_DEPTH,
796
797
  SWARM_SHOT_TIMEOUT_MS: () => SWARM_SHOT_TIMEOUT_MS,
797
798
  SWARM_STORM_URL: () => SWARM_STORM_URL,
798
799
  SWARM_SWEEP_INTERVAL_MS: () => SWARM_SWEEP_INTERVAL_MS,
@@ -892,7 +893,7 @@ function reviewCutoffAt() {
892
893
  }
893
894
  return at;
894
895
  }
895
- var SWARM_HOME, DB_PATH, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_REPO_PATHS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
896
+ var SWARM_HOME, DB_PATH, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_WATCHDOG_STALL_MS;
896
897
  var init_config = __esm({
897
898
  "src/config.ts"() {
898
899
  "use strict";
@@ -940,6 +941,7 @@ var init_config = __esm({
940
941
  HEARTBEAT_PATH = process.env.SWARM_HEARTBEAT || join(SWARM_HOME, "overmind.heartbeat");
941
942
  HEARTBEAT_STALE_MS = Number(process.env.SWARM_HEARTBEAT_STALE_MS) || 3e4;
942
943
  SWARM_REPO = process.env.SWARM_REPO || "";
944
+ SWARM_SCAN_DEPTH = Number(process.env.SWARM_SCAN_DEPTH) || 3;
943
945
  SWARM_REPO_PATHS = (process.env.SWARM_REPOS || SWARM_REPO).split(",").map((s) => s.trim()).filter(Boolean);
944
946
  TASKLESS_API = process.env.TASKLESS_API || "https://taskless.ru";
945
947
  SWARM_SHOT_TIMEOUT_MS = envNum("SWARM_SHOT_TIMEOUT_MS", 2e4);
@@ -2278,7 +2280,7 @@ function pidsWithCwdUnder(dir, opts = {}) {
2278
2280
  if ((opts.platform ?? process.platform) === "win32") return winProcsUnder(dir, opts.procs ?? listProcs);
2279
2281
  let out;
2280
2282
  try {
2281
- out = execQuiet("lsof", ["-w", "-a", "-d", "cwd", "-F", "pn"], { maxBuffer: PROC_SNAPSHOT_MAX_BUFFER });
2283
+ out = execQuiet("lsof", ["-a", "-d", "cwd", "-F", "pn"], { maxBuffer: PROC_SNAPSHOT_MAX_BUFFER });
2282
2284
  } catch (e) {
2283
2285
  logTransientSpawnError("lsof", e);
2284
2286
  return [];
@@ -2468,16 +2470,16 @@ var init_procCleanup = __esm({
2468
2470
  });
2469
2471
 
2470
2472
  // src/git.ts
2471
- import { existsSync as existsSync8, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
2473
+ import { existsSync as existsSync8, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
2472
2474
  import { appendFile, mkdir, readFile, rm } from "fs/promises";
2473
- import { isAbsolute, join as join9 } from "path";
2475
+ import { isAbsolute, join as join11 } from "path";
2474
2476
  import { $ as $2 } from "execa";
2475
2477
  async function reclaimStaleWorktree(repo, worktree) {
2476
2478
  await detachWorktree(repo, worktree);
2477
2479
  }
2478
2480
  function isLinkedWorktree(dir) {
2479
2481
  try {
2480
- return statSync4(join9(dir, ".git")).isFile();
2482
+ return statSync5(join11(dir, ".git")).isFile();
2481
2483
  } catch {
2482
2484
  return false;
2483
2485
  }
@@ -2583,9 +2585,9 @@ async function ensureGitExcluded(worktree, pattern) {
2583
2585
  const out = await $2({ reject: false })`git -C ${worktree} rev-parse --git-common-dir`;
2584
2586
  const raw = out.stdout.trim();
2585
2587
  if (out.exitCode !== 0 || !raw) return false;
2586
- const commonDir = isAbsolute(raw) ? raw : join9(worktree, raw);
2587
- const infoDir = join9(commonDir, "info");
2588
- const excludeFile = join9(infoDir, "exclude");
2588
+ const commonDir = isAbsolute(raw) ? raw : join11(worktree, raw);
2589
+ const infoDir = join11(commonDir, "info");
2590
+ const excludeFile = join11(infoDir, "exclude");
2589
2591
  let current = "";
2590
2592
  try {
2591
2593
  current = await readFile(excludeFile, "utf8");
@@ -2618,7 +2620,7 @@ async function worktreeDirty(path) {
2618
2620
  async function repairMovedWorktrees(targetDir) {
2619
2621
  const subdirs = (dir) => {
2620
2622
  try {
2621
- return readdirSync7(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join9(dir, d.name));
2623
+ return readdirSync8(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join11(dir, d.name));
2622
2624
  } catch {
2623
2625
  return [];
2624
2626
  }
@@ -2730,18 +2732,18 @@ __export(droneChannel_exports, {
2730
2732
  verifyLocalCliToken: () => verifyLocalCliToken
2731
2733
  });
2732
2734
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
2733
- import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
2735
+ import { chmodSync, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2734
2736
  import { dirname as dirname3 } from "path";
2735
2737
  import { serve } from "@hono/node-server";
2736
2738
  function loadChannelSecret(path) {
2737
2739
  try {
2738
- const s = readFileSync10(path, "utf8").trim();
2740
+ const s = readFileSync12(path, "utf8").trim();
2739
2741
  if (s.length >= 32) return s;
2740
2742
  } catch {
2741
2743
  }
2742
2744
  const secret = randomBytes(32).toString("hex");
2743
2745
  mkdirSync5(dirname3(path), { recursive: true });
2744
- writeFileSync7(path, secret, { mode: 384 });
2746
+ writeFileSync8(path, secret, { mode: 384 });
2745
2747
  try {
2746
2748
  chmodSync(path, 384);
2747
2749
  } catch {
@@ -3071,10 +3073,10 @@ var init_droneChannel = __esm({
3071
3073
 
3072
3074
  // src/attachments.ts
3073
3075
  import { mkdir as mkdir2, rm as rm2, writeFile } from "fs/promises";
3074
- import { join as join11 } from "path";
3076
+ import { join as join13 } from "path";
3075
3077
  function attachmentsFromDoc(doc, origin) {
3076
3078
  const out = [];
3077
- const walk = (node) => {
3079
+ const walk2 = (node) => {
3078
3080
  if (!node || typeof node !== "object") return;
3079
3081
  const n = node;
3080
3082
  const attrs = n.attrs ?? {};
@@ -3093,9 +3095,9 @@ function attachmentsFromDoc(doc, origin) {
3093
3095
  });
3094
3096
  }
3095
3097
  }
3096
- if (Array.isArray(n.content)) for (const c of n.content) walk(c);
3098
+ if (Array.isArray(n.content)) for (const c of n.content) walk2(c);
3097
3099
  };
3098
- walk(doc);
3100
+ walk2(doc);
3099
3101
  return out;
3100
3102
  }
3101
3103
  function collectTaskAttachments(task) {
@@ -3162,7 +3164,7 @@ async function materializeAttachments(opts) {
3162
3164
  };
3163
3165
  const result = { files: [], skipped: [] };
3164
3166
  if (opts.refs.length === 0) return result;
3165
- const dir = join11(opts.worktree, ATTACHMENTS_DIR);
3167
+ const dir = join13(opts.worktree, ATTACHMENTS_DIR);
3166
3168
  await io.removeDir(dir);
3167
3169
  let dirReady = false;
3168
3170
  let total = 0;
@@ -3208,7 +3210,7 @@ async function materializeAttachments(opts) {
3208
3210
  await io.mkdir(dir);
3209
3211
  dirReady = true;
3210
3212
  }
3211
- await io.writeFile(join11(dir, name), file.bytes);
3213
+ await io.writeFile(join13(dir, name), file.bytes);
3212
3214
  } catch (e) {
3213
3215
  result.skipped.push(`${label} \u2014 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043B\u043E\u0441\u044C \u043D\u0430 \u0434\u0438\u0441\u043A (${errText(e)})`);
3214
3216
  continue;
@@ -3361,7 +3363,7 @@ var init_attachments = __esm({
3361
3363
 
3362
3364
  // src/shotUpload.ts
3363
3365
  import { readFile as fsReadFile } from "fs/promises";
3364
- import { basename as basename2, isAbsolute as isAbsolute2, join as join12 } from "path";
3366
+ import { basename as basename2, isAbsolute as isAbsolute2, join as join14 } from "path";
3365
3367
  function shotMimetype(path) {
3366
3368
  const dot = path.lastIndexOf(".");
3367
3369
  if (dot < 0) return null;
@@ -3387,7 +3389,7 @@ async function readShots(paths, opts) {
3387
3389
  }
3388
3390
  let bytes;
3389
3391
  try {
3390
- bytes = await read(isAbsolute2(path) ? path : join12(opts.worktree, path));
3392
+ bytes = await read(isAbsolute2(path) ? path : join14(opts.worktree, path));
3391
3393
  } catch (e) {
3392
3394
  skipped.push({ path, reason: `\u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D: ${e instanceof Error ? e.message : String(e)}` });
3393
3395
  continue;
@@ -3465,8 +3467,8 @@ var init_shotUpload = __esm({
3465
3467
 
3466
3468
  // src/oauth.ts
3467
3469
  import { createHash, randomBytes as randomBytes2 } from "crypto";
3468
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync2 } from "fs";
3469
- import { join as join13 } from "path";
3470
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync2 } from "fs";
3471
+ import { join as join15 } from "path";
3470
3472
  function makePkce() {
3471
3473
  const verifier = randomBytes2(48).toString("base64url");
3472
3474
  const challenge = createHash("sha256").update(verifier).digest("base64url");
@@ -3550,13 +3552,13 @@ function toStore(clientId, t, now) {
3550
3552
  }
3551
3553
  function loadStore() {
3552
3554
  try {
3553
- return JSON.parse(readFileSync11(STORE_PATH, "utf8"));
3555
+ return JSON.parse(readFileSync13(STORE_PATH, "utf8"));
3554
3556
  } catch {
3555
3557
  return null;
3556
3558
  }
3557
3559
  }
3558
3560
  function saveStore(s) {
3559
- writeFileSync8(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
3561
+ writeFileSync9(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
3560
3562
  }
3561
3563
  function clearStore() {
3562
3564
  try {
@@ -3566,7 +3568,7 @@ function clearStore() {
3566
3568
  }
3567
3569
  function loadClientId() {
3568
3570
  try {
3569
- return JSON.parse(readFileSync11(CLIENT_PATH, "utf8")).clientId;
3571
+ return JSON.parse(readFileSync13(CLIENT_PATH, "utf8")).clientId;
3570
3572
  } catch {
3571
3573
  return null;
3572
3574
  }
@@ -3575,7 +3577,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
3575
3577
  const existing = loadClientId();
3576
3578
  if (existing) return existing;
3577
3579
  const clientId = await registerClient({ api, redirectUri }, fetchFn);
3578
- writeFileSync8(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
3580
+ writeFileSync9(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
3579
3581
  return clientId;
3580
3582
  }
3581
3583
  async function ensureAccess(store, opts = {}) {
@@ -3601,18 +3603,18 @@ var init_oauth = __esm({
3601
3603
  AUTHORIZE = "/api/oauth/authorize";
3602
3604
  TOKEN = "/api/oauth/token";
3603
3605
  SCOPE = "openid profile email taskless:full";
3604
- STORE_PATH = join13(SWARM_HOME, "oauth.json");
3605
- CLIENT_PATH = join13(SWARM_HOME, "oauth-client.json");
3606
+ STORE_PATH = join15(SWARM_HOME, "oauth.json");
3607
+ CLIENT_PATH = join15(SWARM_HOME, "oauth-client.json");
3606
3608
  base = (api) => api.replace(/\/$/, "");
3607
3609
  }
3608
3610
  });
3609
3611
 
3610
3612
  // src/agentToken.ts
3611
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3 } from "fs";
3612
- import { join as join14 } from "path";
3613
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
3614
+ import { join as join16 } from "path";
3613
3615
  function loadAgentToken() {
3614
3616
  try {
3615
- const s = JSON.parse(readFileSync12(STORE_PATH2, "utf8"));
3617
+ const s = JSON.parse(readFileSync14(STORE_PATH2, "utf8"));
3616
3618
  return s.token ? s : null;
3617
3619
  } catch {
3618
3620
  return null;
@@ -3649,17 +3651,17 @@ var init_agentToken = __esm({
3649
3651
  "src/agentToken.ts"() {
3650
3652
  "use strict";
3651
3653
  init_config();
3652
- STORE_PATH2 = join14(SWARM_HOME, "agent.json");
3654
+ STORE_PATH2 = join16(SWARM_HOME, "agent.json");
3653
3655
  base2 = (api) => api.replace(/\/$/, "");
3654
3656
  }
3655
3657
  });
3656
3658
 
3657
3659
  // src/workspace.ts
3658
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
3659
- import { join as join15 } from "path";
3660
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, unlinkSync as unlinkSync4 } from "fs";
3661
+ import { join as join17 } from "path";
3660
3662
  function loadWorkspace() {
3661
3663
  try {
3662
- const w = JSON.parse(readFileSync13(WORKSPACE_PATH, "utf8"));
3664
+ const w = JSON.parse(readFileSync15(WORKSPACE_PATH, "utf8"));
3663
3665
  return w && typeof w.id === "string" && w.id ? w : null;
3664
3666
  } catch {
3665
3667
  return null;
@@ -3673,7 +3675,7 @@ var init_workspace = __esm({
3673
3675
  "src/workspace.ts"() {
3674
3676
  "use strict";
3675
3677
  init_config();
3676
- WORKSPACE_PATH = join15(SWARM_HOME, "workspace.json");
3678
+ WORKSPACE_PATH = join17(SWARM_HOME, "workspace.json");
3677
3679
  }
3678
3680
  });
3679
3681
 
@@ -4137,8 +4139,9 @@ var init_droneStore = __esm({
4137
4139
 
4138
4140
  // src/overmind.ts
4139
4141
  init_db();
4140
- import { existsSync as existsSync12, readFileSync as readFileSync15, readdirSync as readdirSync9, renameSync as renameSync7 } from "fs";
4141
- import { basename as basename4, dirname as dirname5, join as join19 } from "path";
4142
+ import { homedir as homedir3 } from "os";
4143
+ import { existsSync as existsSync12, readFileSync as readFileSync17, readdirSync as readdirSync10, renameSync as renameSync7 } from "fs";
4144
+ import { basename as basename4, dirname as dirname5, join as join21 } from "path";
4142
4145
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4143
4146
  import { spawn as spawn3 } from "child_process";
4144
4147
 
@@ -6035,6 +6038,18 @@ init_heartbeat();
6035
6038
  // src/repoRegistry.ts
6036
6039
  init_config();
6037
6040
  import { $ } from "execa";
6041
+ function effectiveRepoPaths(flagRepo, envPaths) {
6042
+ const seen = /* @__PURE__ */ new Set();
6043
+ const out = [];
6044
+ for (const raw of [flagRepo ?? "", ...envPaths]) {
6045
+ if (!raw.trim()) continue;
6046
+ const key = normalizeRepo(raw);
6047
+ if (!key || seen.has(key)) continue;
6048
+ seen.add(key);
6049
+ out.push(key);
6050
+ }
6051
+ return out;
6052
+ }
6038
6053
  function parseRepoFullName(remoteUrl) {
6039
6054
  const m = remoteUrl.trim().match(/^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:)([^/]+)\/([^/]+?)(\.git)?\/?$/i);
6040
6055
  if (!m) return null;
@@ -6057,19 +6072,137 @@ async function buildRepoRegistry(paths) {
6057
6072
  return resolved.filter((e) => e !== null);
6058
6073
  }
6059
6074
 
6075
+ // src/repoMemory.ts
6076
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
6077
+ import { join as join4 } from "path";
6078
+ var FILE = "repos.json";
6079
+ function loadRememberedRepos(home) {
6080
+ let raw;
6081
+ try {
6082
+ raw = readFileSync6(join4(home, FILE), "utf8");
6083
+ } catch {
6084
+ return [];
6085
+ }
6086
+ try {
6087
+ const parsed = JSON.parse(raw);
6088
+ if (!Array.isArray(parsed.paths)) return [];
6089
+ return parsed.paths.filter((p) => typeof p === "string" && p.trim().length > 0);
6090
+ } catch {
6091
+ return [];
6092
+ }
6093
+ }
6094
+ function rememberRepos(home, paths, now = () => /* @__PURE__ */ new Date()) {
6095
+ const clean = paths.filter((p) => p.trim().length > 0);
6096
+ if (clean.length === 0) return;
6097
+ writeFileSync5(join4(home, FILE), `${JSON.stringify({ paths: clean, at: now().toISOString() }, null, 2)}
6098
+ `);
6099
+ }
6100
+ function pickRepoPaths(args) {
6101
+ if (args.flag.length > 0) return { paths: [...args.flag], source: "flag" };
6102
+ if (args.env.length > 0) return { paths: [...args.env], source: "env" };
6103
+ if (args.remembered.length > 0) return { paths: [...args.remembered], source: "memory" };
6104
+ return { paths: [], source: "none" };
6105
+ }
6106
+
6107
+ // src/repoScan.ts
6108
+ import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
6109
+ import { join as join5 } from "path";
6110
+ var SKIP = /* @__PURE__ */ new Set([
6111
+ "node_modules",
6112
+ "vendor",
6113
+ "dist",
6114
+ "build",
6115
+ "target",
6116
+ "Library",
6117
+ "Applications",
6118
+ ".Trash",
6119
+ "venv",
6120
+ ".venv",
6121
+ "__pycache__"
6122
+ ]);
6123
+ function parseOriginFromGitConfig(text) {
6124
+ let inOrigin = false;
6125
+ for (const line of text.split("\n")) {
6126
+ const t = line.trim();
6127
+ if (t.startsWith("[")) {
6128
+ inOrigin = /^\[remote\s+"origin"\]$/.test(t);
6129
+ continue;
6130
+ }
6131
+ if (!inOrigin) continue;
6132
+ const m = /^url\s*=\s*(.+)$/.exec(t);
6133
+ if (m?.[1]) return m[1].trim();
6134
+ }
6135
+ return null;
6136
+ }
6137
+ function originOf(dir) {
6138
+ try {
6139
+ return parseOriginFromGitConfig(readFileSync7(join5(dir, ".git", "config"), "utf8"));
6140
+ } catch {
6141
+ return null;
6142
+ }
6143
+ }
6144
+ function scanRoots(roots, opts = {}) {
6145
+ const maxDepth = opts.maxDepth ?? 3;
6146
+ const found = /* @__PURE__ */ new Map();
6147
+ for (const root of roots) walk(root, 0, maxDepth, found);
6148
+ return found;
6149
+ }
6150
+ function walk(dir, depth, maxDepth, found) {
6151
+ if (depth > maxDepth) return;
6152
+ const origin = originOf(dir);
6153
+ if (origin) {
6154
+ const full = parseRepoFullName(origin);
6155
+ if (full && !found.has(full)) found.set(full, dir);
6156
+ return;
6157
+ }
6158
+ let entries;
6159
+ try {
6160
+ entries = readdirSync2(dir);
6161
+ } catch {
6162
+ return;
6163
+ }
6164
+ for (const name of entries) {
6165
+ if (name.startsWith(".") || SKIP.has(name)) continue;
6166
+ const child = join5(dir, name);
6167
+ try {
6168
+ if (!statSync2(child).isDirectory()) continue;
6169
+ } catch {
6170
+ continue;
6171
+ }
6172
+ walk(child, depth + 1, maxDepth, found);
6173
+ }
6174
+ }
6175
+ function defaultScanRoots(home, knownPaths = []) {
6176
+ const out = [];
6177
+ const seen = /* @__PURE__ */ new Set();
6178
+ const add = (p) => {
6179
+ if (!p || seen.has(p)) return;
6180
+ seen.add(p);
6181
+ out.push(p);
6182
+ };
6183
+ for (const p of knownPaths) {
6184
+ const parent = p.replace(/\/+$/, "").replace(/\/[^/]+$/, "");
6185
+ if (parent && parent !== p) add(parent);
6186
+ }
6187
+ for (const name of ["projects", "code", "src", "dev", "work", "repos", "git", "Projects", "Code", "Developer", "Documents/GitHub", "go/src"]) {
6188
+ add(join5(home, name));
6189
+ }
6190
+ return out;
6191
+ }
6192
+
6060
6193
  // src/hostIdentity.ts
6061
6194
  import { hostname } from "os";
6062
- import { existsSync as existsSync5, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
6063
- import { join as join4 } from "path";
6195
+ import { existsSync as existsSync5, readFileSync as readFileSync8, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "fs";
6196
+ import { join as join6 } from "path";
6064
6197
  import { randomUUID as randomUUID2 } from "crypto";
6065
6198
  function hostIdentityPath(home) {
6066
- return join4(home, "host.json");
6199
+ return join6(home, "host.json");
6067
6200
  }
6068
6201
  function loadHostIdentity(deps) {
6069
6202
  const exists = deps.exists ?? existsSync5;
6070
- const readFile2 = deps.readFile ?? ((p) => readFileSync6(p, "utf8"));
6203
+ const readFile2 = deps.readFile ?? ((p) => readFileSync8(p, "utf8"));
6071
6204
  const writeFile2 = deps.writeFile ?? ((p, data) => {
6072
- writeFileSync5(`${p}.tmp`, data);
6205
+ writeFileSync6(`${p}.tmp`, data);
6073
6206
  renameSync4(`${p}.tmp`, p);
6074
6207
  });
6075
6208
  const host = deps.hostname ?? hostname;
@@ -6252,14 +6385,14 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
6252
6385
 
6253
6386
  // src/launcher.ts
6254
6387
  import { spawn } from "child_process";
6255
- import { closeSync as closeSync2, existsSync as existsSync7, mkdirSync as mkdirSync4, openSync as openSync2, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
6388
+ import { closeSync as closeSync2, existsSync as existsSync7, mkdirSync as mkdirSync4, openSync as openSync2, readdirSync as readdirSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
6256
6389
  import { createRequire as createRequire2 } from "module";
6257
6390
  import { homedir as homedir2 } from "os";
6258
- import { dirname as dirname2, join as join6 } from "path";
6391
+ import { dirname as dirname2, join as join8 } from "path";
6259
6392
  import { fileURLToPath } from "url";
6260
6393
 
6261
6394
  // src/claudeBin.ts
6262
- import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
6395
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
6263
6396
  import { win32 as winPath } from "path";
6264
6397
  var CLAUDE_FALLBACK = "claude";
6265
6398
  function compareVersionDirs(a, b) {
@@ -6274,7 +6407,7 @@ function compareVersionDirs(a, b) {
6274
6407
  }
6275
6408
  function realListDir(dir) {
6276
6409
  try {
6277
- return readdirSync2(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6410
+ return readdirSync3(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6278
6411
  } catch {
6279
6412
  return [];
6280
6413
  }
@@ -6331,8 +6464,8 @@ function resolveClaudeBin(opts = {}) {
6331
6464
  }
6332
6465
 
6333
6466
  // src/droneHome.ts
6334
- import { lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync3, rmSync, statSync as statSync2, symlinkSync } from "fs";
6335
- import { join as join5, relative, sep } from "path";
6467
+ import { lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, rmSync, statSync as statSync3, symlinkSync } from "fs";
6468
+ import { join as join7, relative, sep } from "path";
6336
6469
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
6337
6470
  function droneHomeDeny(env = process.env) {
6338
6471
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -6351,28 +6484,28 @@ function buildDroneHome(opts) {
6351
6484
  const deny = opts.deny ?? droneHomeDeny();
6352
6485
  const platform = opts.platform ?? process.platform;
6353
6486
  mkdirSync3(droneHome, { recursive: true, mode: 448 });
6354
- const wanted = new Set(mirroredEntries(readdirSync3(realHome), deny, platform));
6487
+ const wanted = new Set(mirroredEntries(readdirSync4(realHome), deny, platform));
6355
6488
  const rel = relative(realHome, droneHome);
6356
6489
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
6357
- for (const existing of readdirSync3(droneHome)) {
6490
+ for (const existing of readdirSync4(droneHome)) {
6358
6491
  if (wanted.has(existing)) continue;
6359
- const p = join5(droneHome, existing);
6492
+ const p = join7(droneHome, existing);
6360
6493
  try {
6361
6494
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
6362
6495
  } catch {
6363
6496
  }
6364
6497
  }
6365
6498
  for (const name of wanted) {
6366
- const link = join5(droneHome, name);
6499
+ const link = join7(droneHome, name);
6367
6500
  try {
6368
6501
  lstatSync(link);
6369
6502
  continue;
6370
6503
  } catch {
6371
6504
  }
6372
- const target = join5(realHome, name);
6505
+ const target = join7(realHome, name);
6373
6506
  let isDir;
6374
6507
  try {
6375
- isDir = statSync2(target).isDirectory();
6508
+ isDir = statSync3(target).isDirectory();
6376
6509
  } catch {
6377
6510
  continue;
6378
6511
  }
@@ -6390,7 +6523,7 @@ var REAL_PATH_ENV_PINS = [
6390
6523
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
6391
6524
  ];
6392
6525
  function realPathEnvPins(realHome) {
6393
- return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join5(realHome, ...segments)]));
6526
+ return Object.fromEntries(REAL_PATH_ENV_PINS.map(({ env, segments }) => [env, join7(realHome, ...segments)]));
6394
6527
  }
6395
6528
  function droneDenyRules(realHome, swarmHome) {
6396
6529
  const paths = [
@@ -6482,7 +6615,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
6482
6615
  const env = { ...base3 };
6483
6616
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
6484
6617
  if (isolate && !base3.TURBO_CACHE_DIR) {
6485
- env.TURBO_CACHE_DIR = join6(worktree, ".turbo", "cache");
6618
+ env.TURBO_CACHE_DIR = join8(worktree, ".turbo", "cache");
6486
6619
  }
6487
6620
  Object.assign(env, droneTestEnv(droneCap, base3));
6488
6621
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -6510,29 +6643,29 @@ var DAEMON_STATE_ENV = [
6510
6643
  "TASKLESS_TOKEN"
6511
6644
  ];
6512
6645
  function bundledRoleDir(kind) {
6513
- return join6(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
6646
+ return join8(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
6514
6647
  }
6515
6648
  function roleDir(worktree, kind) {
6516
- return join6(worktree, ".claude", "roles", kind);
6649
+ return join8(worktree, ".claude", "roles", kind);
6517
6650
  }
6518
6651
  function roleDirs(worktree, kind) {
6519
6652
  return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
6520
6653
  }
6521
6654
  function transcriptDir(worktree, projectsDir) {
6522
- return join6(projectsDir, worktree.replace(/[/.]/g, "-"));
6655
+ return join8(projectsDir, worktree.replace(/[/.]/g, "-"));
6523
6656
  }
6524
- function sessionTranscriptExists(worktree, projectsDir = join6(homedir2(), ".claude", "projects")) {
6657
+ function sessionTranscriptExists(worktree, projectsDir = join8(homedir2(), ".claude", "projects")) {
6525
6658
  try {
6526
- return readdirSync4(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
6659
+ return readdirSync5(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
6527
6660
  } catch {
6528
6661
  return false;
6529
6662
  }
6530
6663
  }
6531
- function transcriptMtimeMs(worktree, projectsDir = join6(homedir2(), ".claude", "projects")) {
6664
+ function transcriptMtimeMs(worktree, projectsDir = join8(homedir2(), ".claude", "projects")) {
6532
6665
  const dir = transcriptDir(worktree, projectsDir);
6533
6666
  let files;
6534
6667
  try {
6535
- files = readdirSync4(dir);
6668
+ files = readdirSync5(dir);
6536
6669
  } catch {
6537
6670
  return null;
6538
6671
  }
@@ -6540,7 +6673,7 @@ function transcriptMtimeMs(worktree, projectsDir = join6(homedir2(), ".claude",
6540
6673
  for (const f of files) {
6541
6674
  if (!f.endsWith(".jsonl")) continue;
6542
6675
  try {
6543
- const mtime2 = statSync3(join6(dir, f)).mtimeMs;
6676
+ const mtime2 = statSync4(join8(dir, f)).mtimeMs;
6544
6677
  if (latest === null || mtime2 > latest) latest = mtime2;
6545
6678
  } catch {
6546
6679
  }
@@ -6548,11 +6681,11 @@ function transcriptMtimeMs(worktree, projectsDir = join6(homedir2(), ".claude",
6548
6681
  return latest;
6549
6682
  }
6550
6683
  function nextRunLogPath(runLogsDir, runId) {
6551
- const dir = join6(runLogsDir, String(runId));
6684
+ const dir = join8(runLogsDir, String(runId));
6552
6685
  mkdirSync4(dir, { recursive: true });
6553
6686
  let files = [];
6554
6687
  try {
6555
- files = readdirSync4(dir);
6688
+ files = readdirSync5(dir);
6556
6689
  } catch {
6557
6690
  }
6558
6691
  let next = 0;
@@ -6560,7 +6693,7 @@ function nextRunLogPath(runLogsDir, runId) {
6560
6693
  const m = /^(\d+)\.json/.exec(f);
6561
6694
  if (m) next = Math.max(next, Number(m[1]) + 1);
6562
6695
  }
6563
- return join6(dir, `${next}.json`);
6696
+ return join8(dir, `${next}.json`);
6564
6697
  }
6565
6698
  function openNextRunLog(runLogsDir, runId) {
6566
6699
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -6634,16 +6767,16 @@ function makeLauncher(cfg) {
6634
6767
  }
6635
6768
  }
6636
6769
  };
6637
- const cfgPath = join6(worktree, ".swarm-mcp.json");
6638
- writeFileSync6(cfgPath, JSON.stringify(mcpConfig, null, 2));
6770
+ const cfgPath = join8(worktree, ".swarm-mcp.json");
6771
+ writeFileSync7(cfgPath, JSON.stringify(mcpConfig, null, 2));
6639
6772
  const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
6640
6773
  const pluginDirs = roleDirs(worktree, run.kind);
6641
6774
  const realHome = cfg.droneIsolation?.realHome ?? homedir2();
6642
6775
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
6643
6776
  const settings = cfg.droneIsolation ? droneSettingsJson(realHome, cfg.droneIsolation.swarmHome) : void 0;
6644
- if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join6(cfg.droneIsolation.swarmHome, "token"))) {
6777
+ if (cfg.droneIsolation && process.env.TASKLESS_TOKEN && !existsSync7(join8(cfg.droneIsolation.swarmHome, "token"))) {
6645
6778
  console.error(
6646
- `[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 ${join6(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.`
6779
+ `[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 ${join8(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.`
6647
6780
  );
6648
6781
  }
6649
6782
  let stdio = "ignore";
@@ -6677,8 +6810,8 @@ function makeLauncher(cfg) {
6677
6810
  init_procCleanup();
6678
6811
 
6679
6812
  // src/cost.ts
6680
- import { readFileSync as readFileSync7, readdirSync as readdirSync5, renameSync as renameSync5 } from "fs";
6681
- import { join as join7 } from "path";
6813
+ import { readFileSync as readFileSync9, readdirSync as readdirSync6, renameSync as renameSync5 } from "fs";
6814
+ import { join as join9 } from "path";
6682
6815
 
6683
6816
  // src/pricing.ts
6684
6817
  var MODEL_PRICING = {
@@ -6729,23 +6862,23 @@ function launchCostUsd(result, model) {
6729
6862
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
6730
6863
  function unprocessedLogFiles(dir) {
6731
6864
  try {
6732
- return readdirSync5(dir).filter((f) => UNPROCESSED_LOG_RE.test(f));
6865
+ return readdirSync6(dir).filter((f) => UNPROCESSED_LOG_RE.test(f));
6733
6866
  } catch {
6734
6867
  return [];
6735
6868
  }
6736
6869
  }
6737
6870
  function accountRunCost(deps, run) {
6738
- const dir = join7(deps.runLogsDir, String(run.id));
6871
+ const dir = join9(deps.runLogsDir, String(run.id));
6739
6872
  const files = unprocessedLogFiles(dir);
6740
6873
  if (files.length === 0) return;
6741
6874
  const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
6742
6875
  let added = 0;
6743
6876
  const processed = [];
6744
6877
  for (const f of files) {
6745
- const full = join7(dir, f);
6878
+ const full = join9(dir, f);
6746
6879
  let raw = null;
6747
6880
  try {
6748
- raw = readFileSync7(full, "utf8");
6881
+ raw = readFileSync9(full, "utf8");
6749
6882
  } catch {
6750
6883
  continue;
6751
6884
  }
@@ -6772,14 +6905,14 @@ function makeCostAccountant(deps) {
6772
6905
  }
6773
6906
 
6774
6907
  // src/runOutcome.ts
6775
- import { readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
6776
- import { join as join8 } from "path";
6908
+ import { readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
6909
+ import { join as join10 } from "path";
6777
6910
  var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
6778
6911
  function classifyExit(runLogsDir, runId) {
6779
- const dir = join8(runLogsDir, String(runId));
6912
+ const dir = join10(runLogsDir, String(runId));
6780
6913
  let files;
6781
6914
  try {
6782
- files = readdirSync6(dir);
6915
+ files = readdirSync7(dir);
6783
6916
  } catch {
6784
6917
  return "unknown";
6785
6918
  }
@@ -6793,7 +6926,7 @@ function classifyExit(runLogsDir, runId) {
6793
6926
  if (!latest) return "unknown";
6794
6927
  let raw;
6795
6928
  try {
6796
- raw = readFileSync8(join8(dir, latest.name), "utf8");
6929
+ raw = readFileSync10(join10(dir, latest.name), "utf8");
6797
6930
  } catch {
6798
6931
  return "unknown";
6799
6932
  }
@@ -6807,8 +6940,8 @@ function makeExitClassifier(runLogsDir) {
6807
6940
  init_git();
6808
6941
 
6809
6942
  // src/deps.ts
6810
- import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync9 } from "fs";
6811
- import { join as join10 } from "path";
6943
+ import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync11 } from "fs";
6944
+ import { join as join12 } from "path";
6812
6945
  import { execa } from "execa";
6813
6946
  var PREPARE_TIMEOUT_MS = 15 * 6e4;
6814
6947
  var MAX_PLAN_COMMANDS = 12;
@@ -6824,21 +6957,21 @@ async function runCommand(cmd, worktree) {
6824
6957
  }
6825
6958
  function safeReadText(p) {
6826
6959
  try {
6827
- return readFileSync9(p, "utf8");
6960
+ return readFileSync11(p, "utf8");
6828
6961
  } catch {
6829
6962
  return null;
6830
6963
  }
6831
6964
  }
6832
6965
  function safeListDirs(p) {
6833
6966
  try {
6834
- return readdirSync8(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
6967
+ return readdirSync9(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
6835
6968
  } catch {
6836
6969
  return [];
6837
6970
  }
6838
6971
  }
6839
6972
  function safeListFiles(p) {
6840
6973
  try {
6841
- return readdirSync8(p, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
6974
+ return readdirSync9(p, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
6842
6975
  } catch {
6843
6976
  return [];
6844
6977
  }
@@ -6866,19 +6999,19 @@ function overridePlan(override) {
6866
6999
  function detectPackageManager(worktree, io = {}) {
6867
7000
  const exists = io.exists ?? existsSync9;
6868
7001
  const readText = io.readText ?? safeReadText;
6869
- const hasPkgJson = exists(join10(worktree, "package.json"));
7002
+ const hasPkgJson = exists(join12(worktree, "package.json"));
6870
7003
  if (!hasPkgJson) {
6871
- if (exists(join10(worktree, "deno.json")) || exists(join10(worktree, "deno.jsonc")) || exists(join10(worktree, "deno.lock"))) return "deno";
7004
+ if (exists(join12(worktree, "deno.json")) || exists(join12(worktree, "deno.jsonc")) || exists(join12(worktree, "deno.lock"))) return "deno";
6872
7005
  return null;
6873
7006
  }
6874
- const pkg = readJson(readText, join10(worktree, "package.json"));
7007
+ const pkg = readJson(readText, join12(worktree, "package.json"));
6875
7008
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
6876
7009
  if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
6877
- if (exists(join10(worktree, "bun.lock")) || exists(join10(worktree, "bun.lockb"))) return "bun";
6878
- if (exists(join10(worktree, "pnpm-lock.yaml"))) return "pnpm";
6879
- if (exists(join10(worktree, "yarn.lock"))) return "yarn";
6880
- if (exists(join10(worktree, "package-lock.json"))) return "npm";
6881
- if (exists(join10(worktree, "deno.lock"))) return "deno";
7010
+ if (exists(join12(worktree, "bun.lock")) || exists(join12(worktree, "bun.lockb"))) return "bun";
7011
+ if (exists(join12(worktree, "pnpm-lock.yaml"))) return "pnpm";
7012
+ if (exists(join12(worktree, "yarn.lock"))) return "yarn";
7013
+ if (exists(join12(worktree, "package-lock.json"))) return "npm";
7014
+ if (exists(join12(worktree, "deno.lock"))) return "deno";
6882
7015
  return "npm";
6883
7016
  }
6884
7017
  function jsInstallCommand(pm) {
@@ -6939,7 +7072,7 @@ function detectPlan(worktree, io) {
6939
7072
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
6940
7073
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
6941
7074
  // заново, но он дешёвый: тянет из того же общего кэша.
6942
- { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join10(worktree, "node_modules"), source: "detect" }
7075
+ { label: `${pm} install`, run: jsInstallCommand(pm), skipIfExists: join12(worktree, "node_modules"), source: "detect" }
6943
7076
  ];
6944
7077
  for (const target of workspaceBuildTargets(worktree, io)) {
6945
7078
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -6947,12 +7080,12 @@ function detectPlan(worktree, io) {
6947
7080
  return { source: "detect", ecosystem: `node/${pm}`, commands };
6948
7081
  }
6949
7082
  for (const marker of ECOSYSTEM_MARKERS) {
6950
- const hit = marker.files.some((f) => exists(join10(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
7083
+ const hit = marker.files.some((f) => exists(join12(worktree, f))) || marker.ext !== void 0 && listFiles(worktree).some((n) => n.endsWith(marker.ext));
6951
7084
  if (!hit) continue;
6952
7085
  let ecosystem = marker.ecosystem;
6953
7086
  let command = marker.command;
6954
7087
  for (const refine of marker.refine ?? []) {
6955
- if (!exists(join10(worktree, refine.file))) continue;
7088
+ if (!exists(join12(worktree, refine.file))) continue;
6956
7089
  ecosystem = refine.ecosystem;
6957
7090
  command = refine.command;
6958
7091
  break;
@@ -7009,12 +7142,12 @@ var CI_INSTALL_PATTERNS = [
7009
7142
  function ciWorkflowPlan(worktree, io) {
7010
7143
  const readText = io.readText ?? safeReadText;
7011
7144
  const listFiles = io.listFiles ?? safeListFiles;
7012
- const dir = join10(worktree, ".github", "workflows");
7145
+ const dir = join12(worktree, ".github", "workflows");
7013
7146
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
7014
7147
  const seen = /* @__PURE__ */ new Set();
7015
7148
  const commands = [];
7016
7149
  for (const name of files.sort()) {
7017
- const yaml = readText(join10(dir, name));
7150
+ const yaml = readText(join12(dir, name));
7018
7151
  if (yaml === null) continue;
7019
7152
  for (const raw of workflowRunLines(yaml)) {
7020
7153
  const line = raw.replace(/\s+/g, " ").trim();
@@ -7029,7 +7162,7 @@ function ciWorkflowPlan(worktree, io) {
7029
7162
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
7030
7163
  }
7031
7164
  function workspaceGlobs(worktree, readText) {
7032
- const yaml = readText(join10(worktree, "pnpm-workspace.yaml"));
7165
+ const yaml = readText(join12(worktree, "pnpm-workspace.yaml"));
7033
7166
  if (yaml !== null) {
7034
7167
  const globs = [];
7035
7168
  let inPackages = false;
@@ -7049,7 +7182,7 @@ function workspaceGlobs(worktree, readText) {
7049
7182
  }
7050
7183
  if (globs.length) return globs;
7051
7184
  }
7052
- const pkg = readJson(readText, join10(worktree, "package.json"));
7185
+ const pkg = readJson(readText, join12(worktree, "package.json"));
7053
7186
  const ws = pkg?.workspaces;
7054
7187
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
7055
7188
  return list.filter((g) => typeof g === "string");
@@ -7060,14 +7193,14 @@ function workspaceDirs(worktree, globs, io) {
7060
7193
  if (glob.startsWith("!")) continue;
7061
7194
  const star = glob.indexOf("*");
7062
7195
  if (star === -1) {
7063
- const dir = join10(worktree, glob);
7064
- if (io.exists(join10(dir, "package.json"))) dirs.add(dir);
7196
+ const dir = join12(worktree, glob);
7197
+ if (io.exists(join12(dir, "package.json"))) dirs.add(dir);
7065
7198
  continue;
7066
7199
  }
7067
- const base3 = join10(worktree, glob.slice(0, star).replace(/\/$/, ""));
7200
+ const base3 = join12(worktree, glob.slice(0, star).replace(/\/$/, ""));
7068
7201
  for (const name of io.listDirs(base3)) {
7069
- const dir = join10(base3, name);
7070
- if (io.exists(join10(dir, "package.json"))) dirs.add(dir);
7202
+ const dir = join12(base3, name);
7203
+ if (io.exists(join12(dir, "package.json"))) dirs.add(dir);
7071
7204
  }
7072
7205
  }
7073
7206
  return [...dirs];
@@ -7095,11 +7228,11 @@ function workspaceBuildTargets(worktree, io = {}) {
7095
7228
  if (!globs.length) return [];
7096
7229
  const pkgs = [];
7097
7230
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
7098
- const pkg = readJson(readText, join10(dir, "package.json"));
7231
+ const pkg = readJson(readText, join12(dir, "package.json"));
7099
7232
  if (!pkg || typeof pkg.name !== "string") continue;
7100
7233
  const scripts = pkg.scripts ?? {};
7101
7234
  const entries = entryPaths(pkg);
7102
- const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join10(dir, e)));
7235
+ const needsBuild = typeof scripts.build === "string" && entries.length > 0 && entries.some((e) => !exists(join12(dir, e)));
7103
7236
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
7104
7237
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
7105
7238
  }
@@ -8673,7 +8806,7 @@ function makeExclusiveReader(opts) {
8673
8806
 
8674
8807
  // src/envGate.ts
8675
8808
  import { existsSync as existsSync10 } from "fs";
8676
- import { join as join16, delimiter } from "path";
8809
+ import { join as join18, delimiter } from "path";
8677
8810
  import { createConnection } from "net";
8678
8811
  import { execa as execa2 } from "execa";
8679
8812
  var PROBE_TIMEOUT_MS = 2500;
@@ -8725,7 +8858,7 @@ var COMPOSE_MARKERS = [
8725
8858
  ];
8726
8859
  function detectRequirements(repo, io = {}) {
8727
8860
  const exists = io.exists ?? existsSync10;
8728
- const marker = COMPOSE_MARKERS.find((f) => exists(join16(repo, f)));
8861
+ const marker = COMPOSE_MARKERS.find((f) => exists(join18(repo, f)));
8729
8862
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
8730
8863
  }
8731
8864
  function resolveRequirements(repo, declared, io = {}) {
@@ -8747,7 +8880,7 @@ async function hasCommand(name, env) {
8747
8880
  if (name.includes("/")) return existsSync10(name);
8748
8881
  const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
8749
8882
  const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
8750
- return dirs.some((dir) => exts.some((ext) => existsSync10(join16(dir, name + ext))));
8883
+ return dirs.some((dir) => exts.some((ext) => existsSync10(join18(dir, name + ext))));
8751
8884
  }
8752
8885
  async function portOpen(host, port) {
8753
8886
  return new Promise((resolve3) => {
@@ -8864,8 +8997,8 @@ import { basename as basename3 } from "path";
8864
8997
  import { serve as serve2 } from "@hono/node-server";
8865
8998
 
8866
8999
  // src/diff.ts
8867
- import { statSync as statSync5 } from "fs";
8868
- import { join as join17 } from "path";
9000
+ import { statSync as statSync6 } from "fs";
9001
+ import { join as join19 } from "path";
8869
9002
  import { $ as $4 } from "execa";
8870
9003
  var MAX_FILES = 200;
8871
9004
  var MAX_LINES_PER_FILE = 1500;
@@ -9008,7 +9141,7 @@ async function resolveBase(wt, baseBranch) {
9008
9141
  }
9009
9142
  function mtime(path) {
9010
9143
  try {
9011
- return statSync5(path).mtimeMs;
9144
+ return statSync6(path).mtimeMs;
9012
9145
  } catch {
9013
9146
  return 0;
9014
9147
  }
@@ -9033,7 +9166,7 @@ async function collectRunDiff(worktree, opts = {}) {
9033
9166
  files.push(pf);
9034
9167
  }
9035
9168
  }
9036
- files.sort((a, b) => mtime(join17(worktree, b.path)) - mtime(join17(worktree, a.path)));
9169
+ files.sort((a, b) => mtime(join19(worktree, b.path)) - mtime(join19(worktree, a.path)));
9037
9170
  const truncatedFiles = files.length > MAX_FILES;
9038
9171
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
9039
9172
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -9052,7 +9185,7 @@ async function collectRunDiff(worktree, opts = {}) {
9052
9185
  // package.json
9053
9186
  var package_default = {
9054
9187
  name: "@taskless-app/swarm",
9055
- version: "0.4.4",
9188
+ version: "0.4.7",
9056
9189
  type: "module",
9057
9190
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
9058
9191
  keywords: [
@@ -10784,16 +10917,16 @@ async function fetchExternalStorm(url, opts = {}) {
10784
10917
  }
10785
10918
 
10786
10919
  // src/anomalyState.ts
10787
- import { existsSync as existsSync11, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
10788
- import { join as join18 } from "path";
10920
+ import { existsSync as existsSync11, readFileSync as readFileSync16, renameSync as renameSync6, writeFileSync as writeFileSync12 } from "fs";
10921
+ import { join as join20 } from "path";
10789
10922
  var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
10790
10923
  var EMPTY = { since: {}, lastFiredAt: {}, fires: {}, stopQuiet: null };
10791
10924
  function anomalyStatePath(home) {
10792
- return join18(home, "anomaly-state.json");
10925
+ return join20(home, "anomaly-state.json");
10793
10926
  }
10794
10927
  function loadAnomalyState(home, now = Date.now(), io = {}) {
10795
10928
  const exists = io.exists ?? existsSync11;
10796
- const readFile2 = io.readFile ?? ((p) => readFileSync14(p, "utf8"));
10929
+ const readFile2 = io.readFile ?? ((p) => readFileSync16(p, "utf8"));
10797
10930
  const path = anomalyStatePath(home);
10798
10931
  if (!exists(path)) return { ...EMPTY };
10799
10932
  try {
@@ -10819,7 +10952,7 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
10819
10952
  function saveAnomalyState(home, state, io = {}) {
10820
10953
  const path = anomalyStatePath(home);
10821
10954
  const writeFile2 = io.writeFile ?? ((p, data) => {
10822
- writeFileSync11(`${p}.tmp`, data);
10955
+ writeFileSync12(`${p}.tmp`, data);
10823
10956
  renameSync6(`${p}.tmp`, p);
10824
10957
  });
10825
10958
  writeFile2(path, JSON.stringify(state, null, 2));
@@ -11462,10 +11595,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
11462
11595
  }
11463
11596
  return parts.join("\n");
11464
11597
  }
11465
- var HERO_CHARTER_PATH = join19(dirname5(THIS_FILE), "..", "docs", "HERO.md");
11598
+ var HERO_CHARTER_PATH = join21(dirname5(THIS_FILE), "..", "docs", "HERO.md");
11466
11599
  function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
11467
11600
  const name = run.name || `#${run.id}`;
11468
- const charter = readFileSync15(HERO_CHARTER_PATH, "utf8");
11601
+ const charter = readFileSync17(HERO_CHARTER_PATH, "utf8");
11469
11602
  const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
11470
11603
 
11471
11604
  ${task.description}` : `\u042D\u041F\u0418\u041A ${run.task_slug} (\u0442\u0435\u043B\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043F\u043E slug \u0438 \u0437\u0434\u0440\u0430\u0432\u043E\u043C\u0443 \u0441\u043C\u044B\u0441\u043B\u0443).`;
@@ -11801,9 +11934,9 @@ function repoHasCi(deps, run) {
11801
11934
  function repoHasCiWorkflows(worktreePath, repo) {
11802
11935
  for (const base3 of [worktreePath, repo]) {
11803
11936
  if (!base3) continue;
11804
- const dir = join19(base3, ".github", "workflows");
11937
+ const dir = join21(base3, ".github", "workflows");
11805
11938
  try {
11806
- if (readdirSync9(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
11939
+ if (readdirSync10(dir).some((f) => f.endsWith(".yml") || f.endsWith(".yaml"))) return true;
11807
11940
  } catch {
11808
11941
  }
11809
11942
  }
@@ -13092,7 +13225,7 @@ async function resolveConflict(deps, reviewRun) {
13092
13225
  workspace_id: reviewRun.workspace_id
13093
13226
  // та же задача, тот же воркспейс (TASWM-288)
13094
13227
  });
13095
- const wt = join19(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
13228
+ const wt = join21(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
13096
13229
  try {
13097
13230
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
13098
13231
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -13144,7 +13277,7 @@ async function spawnReviewer(deps, reviewRun, files) {
13144
13277
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
13145
13278
  workspace_id: reviewRun.workspace_id
13146
13279
  });
13147
- const wt = join19(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
13280
+ const wt = join21(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
13148
13281
  try {
13149
13282
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
13150
13283
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
@@ -13239,22 +13372,59 @@ async function syncCrashedOnStartup(deps) {
13239
13372
  }
13240
13373
  return { orphaned, merged, requeued };
13241
13374
  }
13242
- async function makeStore(repo, worktreesDir) {
13375
+ async function makeStore(repo, worktreesDir, repoFlag) {
13243
13376
  const host = loadHostIdentity({
13244
13377
  home: SWARM_HOME,
13245
13378
  envHostId: process.env.SWARM_HOST_ID,
13246
13379
  envHostName: process.env.SWARM_HOST_NAME
13247
13380
  });
13248
13381
  console.log(`[overmind] \u043C\u0430\u0448\u0438\u043D\u0430: ${host.hostName} (${host.hostId})`);
13382
+ const picked = pickRepoPaths({
13383
+ flag: effectiveRepoPaths(repoFlag ?? "", []),
13384
+ env: effectiveRepoPaths("", SWARM_REPO_PATHS),
13385
+ remembered: effectiveRepoPaths("", loadRememberedRepos(SWARM_HOME))
13386
+ });
13387
+ const repoPaths = picked.paths;
13388
+ if (picked.source === "memory") {
13389
+ console.log(`[overmind] \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0432\u0437\u044F\u0442\u044B \u0438\u0437 \u043F\u0430\u043C\u044F\u0442\u0438 \u043F\u0440\u043E\u0448\u043B\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430: ${repoPaths.join(", ")} (\u0441\u043C\u0435\u043D\u0438\u0442\u044C: swarm start --repo <\u043F\u0443\u0442\u044C>)`);
13390
+ }
13391
+ if (picked.source === "flag" || picked.source === "env") {
13392
+ try {
13393
+ rememberRepos(SWARM_HOME, repoPaths);
13394
+ } catch (e) {
13395
+ console.warn(`[overmind] \u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u044C \u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044E (${e instanceof Error ? e.message : e}) \u2014 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u0441\u043D\u043E\u0432\u0430 \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0442 --repo`);
13396
+ }
13397
+ }
13249
13398
  if (SWARM_BACKEND !== "client") return { store: new SwarmDb(DB_PATH, repo), reconciled: true, host };
13250
13399
  const call = resilientSwarmMcp({
13251
13400
  onDown: (ms) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u0440\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u0441\u0434\u0430\u0447\u0438 \u0438 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u044B \u043D\u0435 \u0434\u043E\u0435\u0437\u0436\u0430\u044E\u0442.`),
13252
13401
  onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
13253
13402
  });
13254
- const repoRegistry = await buildRepoRegistry(SWARM_REPO_PATHS);
13255
- if (SWARM_REPO_PATHS.length > 0 && repoRegistry.length < SWARM_REPO_PATHS.length) {
13403
+ const explicitRegistry = await buildRepoRegistry(repoPaths);
13404
+ const byName = new Map(explicitRegistry.map((e) => [e.repoFullName, e.path]));
13405
+ const scanRootsList = defaultScanRoots(homedir3(), repoPaths);
13406
+ const scanStartedAt = Date.now();
13407
+ const scanned = scanRoots(scanRootsList, { maxDepth: SWARM_SCAN_DEPTH });
13408
+ let addedByScan = 0;
13409
+ for (const [full, path] of scanned) {
13410
+ if (byName.has(full)) continue;
13411
+ byName.set(full, path);
13412
+ addedByScan++;
13413
+ }
13414
+ if (addedByScan > 0) {
13415
+ console.log(
13416
+ `[overmind] \u043D\u0430\u0448\u0451\u043B \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435 ${addedByScan} \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439(\u0435\u0432) \u0441\u043A\u0430\u043D\u043E\u043C \u0437\u0430 ${Date.now() - scanStartedAt}\u043C\u0441: ${[...scanned.keys()].slice(0, 8).join(", ")}${scanned.size > 8 ? ` \u0438 \u0435\u0449\u0451 ${scanned.size - 8}` : ""}`
13417
+ );
13418
+ }
13419
+ const repoRegistry = [...byName].map(([repoFullName, path]) => ({ repoFullName, path }));
13420
+ if (repoPaths.length > 0 && repoRegistry.length < repoPaths.length) {
13256
13421
  console.warn(
13257
- `[overmind] \u0440\u0435\u043F\u043E-\u0440\u0435\u0435\u0441\u0442\u0440: ${repoRegistry.length}/${SWARM_REPO_PATHS.length} \u043F\u0443\u0442\u0435\u0439 \u0440\u0435\u0437\u043E\u043B\u0432\u043D\u0443\u043B\u0438\u0441\u044C \u0432 repoFullName (\u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u2014 \u043D\u0435 git-\u0440\u0435\u043F\u043E \u0438\u043B\u0438 \u0431\u0435\u0437 remote origin)`
13422
+ `[overmind] \u0440\u0435\u043F\u043E-\u0440\u0435\u0435\u0441\u0442\u0440: ${repoRegistry.length}/${repoPaths.length} \u043F\u0443\u0442\u0435\u0439 \u0440\u0435\u0437\u043E\u043B\u0432\u043D\u0443\u043B\u0438\u0441\u044C \u0432 repoFullName (\u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u2014 \u043D\u0435 git-\u0440\u0435\u043F\u043E \u0438\u043B\u0438 \u0431\u0435\u0437 remote origin)`
13423
+ );
13424
+ }
13425
+ if (repoRegistry.length === 0) {
13426
+ console.warn(
13427
+ `[overmind] \u041D\u0415 \u041D\u0410\u0428\u0401\u041B \u041D\u0418 \u041E\u0414\u041D\u041E\u0413\u041E \u0420\u0415\u041F\u041E\u0417\u0418\u0422\u041E\u0420\u0418\u042F \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u2014 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0431\u0440\u0430\u0442\u044C\u0441\u044F \u043D\u0435 \u0431\u0443\u0434\u0443\u0442: \u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0438\u043C\u0435\u043D\u0435\u043C owner/repo, \u0430 \u0441\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435 \u0441 \u0447\u0435\u043C. \u0418\u0441\u043A\u0430\u043B \u0432: ${scanRootsList.slice(0, 6).join(", ")}. \u0415\u0441\u043B\u0438 \u043A\u043B\u043E\u043D\u044B \u043B\u0435\u0436\u0430\u0442 \u0432 \u0434\u0440\u0443\u0433\u043E\u043C \u043C\u0435\u0441\u0442\u0435 \u2014 \u0443\u043A\u0430\u0436\u0438: swarm start --repo <\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E> (\u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u0441\u044F).`
13258
13428
  );
13259
13429
  }
13260
13430
  const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry, host });
@@ -13291,7 +13461,7 @@ async function migrateWorktreesAtStartup() {
13291
13461
  exists: (d) => existsSync12(d),
13292
13462
  isNonEmptyDir: (d) => {
13293
13463
  try {
13294
- return readdirSync9(d).length > 0;
13464
+ return readdirSync10(d).length > 0;
13295
13465
  } catch {
13296
13466
  return false;
13297
13467
  }
@@ -13322,7 +13492,11 @@ async function runDaemon(opts) {
13322
13492
  installCrashGuards();
13323
13493
  const daemonStart = await captureDaemonStart(THIS_FILE);
13324
13494
  const worktreesDir = await migrateWorktreesAtStartup();
13325
- const { store: db, reconciled, host } = await makeStore(opts.repo, worktreesDir);
13495
+ if (!opts.repo) {
13496
+ const remembered = effectiveRepoPaths("", loadRememberedRepos(SWARM_HOME))[0];
13497
+ if (remembered) opts.repo = remembered;
13498
+ }
13499
+ const { store: db, reconciled, host } = await makeStore(opts.repo, worktreesDir, opts.repoFlag);
13326
13500
  await preflightAuth();
13327
13501
  const tl = await connectTaskless();
13328
13502
  const tlFor = (workspaceId) => tl ? tl.forWorkspace(workspaceId) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.4.4",
3
+ "version": "0.4.7",
4
4
  "type": "module",
5
5
  "description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6
6
  "keywords": [