@taskless-app/swarm 0.4.5 → 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);
@@ -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.5",
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);
@@ -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
 
@@ -6069,19 +6072,137 @@ async function buildRepoRegistry(paths) {
6069
6072
  return resolved.filter((e) => e !== null);
6070
6073
  }
6071
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
+
6072
6193
  // src/hostIdentity.ts
6073
6194
  import { hostname } from "os";
6074
- import { existsSync as existsSync5, readFileSync as readFileSync6, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
6075
- 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";
6076
6197
  import { randomUUID as randomUUID2 } from "crypto";
6077
6198
  function hostIdentityPath(home) {
6078
- return join4(home, "host.json");
6199
+ return join6(home, "host.json");
6079
6200
  }
6080
6201
  function loadHostIdentity(deps) {
6081
6202
  const exists = deps.exists ?? existsSync5;
6082
- const readFile2 = deps.readFile ?? ((p) => readFileSync6(p, "utf8"));
6203
+ const readFile2 = deps.readFile ?? ((p) => readFileSync8(p, "utf8"));
6083
6204
  const writeFile2 = deps.writeFile ?? ((p, data) => {
6084
- writeFileSync5(`${p}.tmp`, data);
6205
+ writeFileSync6(`${p}.tmp`, data);
6085
6206
  renameSync4(`${p}.tmp`, p);
6086
6207
  });
6087
6208
  const host = deps.hostname ?? hostname;
@@ -6264,14 +6385,14 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
6264
6385
 
6265
6386
  // src/launcher.ts
6266
6387
  import { spawn } from "child_process";
6267
- 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";
6268
6389
  import { createRequire as createRequire2 } from "module";
6269
6390
  import { homedir as homedir2 } from "os";
6270
- import { dirname as dirname2, join as join6 } from "path";
6391
+ import { dirname as dirname2, join as join8 } from "path";
6271
6392
  import { fileURLToPath } from "url";
6272
6393
 
6273
6394
  // src/claudeBin.ts
6274
- import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
6395
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
6275
6396
  import { win32 as winPath } from "path";
6276
6397
  var CLAUDE_FALLBACK = "claude";
6277
6398
  function compareVersionDirs(a, b) {
@@ -6286,7 +6407,7 @@ function compareVersionDirs(a, b) {
6286
6407
  }
6287
6408
  function realListDir(dir) {
6288
6409
  try {
6289
- 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);
6290
6411
  } catch {
6291
6412
  return [];
6292
6413
  }
@@ -6343,8 +6464,8 @@ function resolveClaudeBin(opts = {}) {
6343
6464
  }
6344
6465
 
6345
6466
  // src/droneHome.ts
6346
- import { lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync3, rmSync, statSync as statSync2, symlinkSync } from "fs";
6347
- 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";
6348
6469
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
6349
6470
  function droneHomeDeny(env = process.env) {
6350
6471
  const extra = (env.SWARM_DRONE_HOME_DENY || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -6363,28 +6484,28 @@ function buildDroneHome(opts) {
6363
6484
  const deny = opts.deny ?? droneHomeDeny();
6364
6485
  const platform = opts.platform ?? process.platform;
6365
6486
  mkdirSync3(droneHome, { recursive: true, mode: 448 });
6366
- const wanted = new Set(mirroredEntries(readdirSync3(realHome), deny, platform));
6487
+ const wanted = new Set(mirroredEntries(readdirSync4(realHome), deny, platform));
6367
6488
  const rel = relative(realHome, droneHome);
6368
6489
  if (rel && !rel.startsWith("..") && !rel.includes(sep)) wanted.delete(rel);
6369
- for (const existing of readdirSync3(droneHome)) {
6490
+ for (const existing of readdirSync4(droneHome)) {
6370
6491
  if (wanted.has(existing)) continue;
6371
- const p = join5(droneHome, existing);
6492
+ const p = join7(droneHome, existing);
6372
6493
  try {
6373
6494
  if (lstatSync(p).isSymbolicLink()) rmSync(p);
6374
6495
  } catch {
6375
6496
  }
6376
6497
  }
6377
6498
  for (const name of wanted) {
6378
- const link = join5(droneHome, name);
6499
+ const link = join7(droneHome, name);
6379
6500
  try {
6380
6501
  lstatSync(link);
6381
6502
  continue;
6382
6503
  } catch {
6383
6504
  }
6384
- const target = join5(realHome, name);
6505
+ const target = join7(realHome, name);
6385
6506
  let isDir;
6386
6507
  try {
6387
- isDir = statSync2(target).isDirectory();
6508
+ isDir = statSync3(target).isDirectory();
6388
6509
  } catch {
6389
6510
  continue;
6390
6511
  }
@@ -6402,7 +6523,7 @@ var REAL_PATH_ENV_PINS = [
6402
6523
  { env: "YARN_GLOBAL_FOLDER", segments: [".yarn", "berry"] }
6403
6524
  ];
6404
6525
  function realPathEnvPins(realHome) {
6405
- 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)]));
6406
6527
  }
6407
6528
  function droneDenyRules(realHome, swarmHome) {
6408
6529
  const paths = [
@@ -6494,7 +6615,7 @@ function droneEnv(worktree, base3 = process.env, isolation, droneCap = null) {
6494
6615
  const env = { ...base3 };
6495
6616
  const isolate = base3.SWARM_TURBO_ISOLATE !== "0";
6496
6617
  if (isolate && !base3.TURBO_CACHE_DIR) {
6497
- env.TURBO_CACHE_DIR = join6(worktree, ".turbo", "cache");
6618
+ env.TURBO_CACHE_DIR = join8(worktree, ".turbo", "cache");
6498
6619
  }
6499
6620
  Object.assign(env, droneTestEnv(droneCap, base3));
6500
6621
  for (const key of DAEMON_STATE_ENV) delete env[key];
@@ -6522,29 +6643,29 @@ var DAEMON_STATE_ENV = [
6522
6643
  "TASKLESS_TOKEN"
6523
6644
  ];
6524
6645
  function bundledRoleDir(kind) {
6525
- return join6(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
6646
+ return join8(dirname2(fileURLToPath(import.meta.url)), "..", "roles", kind);
6526
6647
  }
6527
6648
  function roleDir(worktree, kind) {
6528
- return join6(worktree, ".claude", "roles", kind);
6649
+ return join8(worktree, ".claude", "roles", kind);
6529
6650
  }
6530
6651
  function roleDirs(worktree, kind) {
6531
6652
  return [bundledRoleDir(kind), roleDir(worktree, kind)].filter((dir) => existsSync7(dir));
6532
6653
  }
6533
6654
  function transcriptDir(worktree, projectsDir) {
6534
- return join6(projectsDir, worktree.replace(/[/.]/g, "-"));
6655
+ return join8(projectsDir, worktree.replace(/[/.]/g, "-"));
6535
6656
  }
6536
- function sessionTranscriptExists(worktree, projectsDir = join6(homedir2(), ".claude", "projects")) {
6657
+ function sessionTranscriptExists(worktree, projectsDir = join8(homedir2(), ".claude", "projects")) {
6537
6658
  try {
6538
- return readdirSync4(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
6659
+ return readdirSync5(transcriptDir(worktree, projectsDir)).some((f) => f.endsWith(".jsonl"));
6539
6660
  } catch {
6540
6661
  return false;
6541
6662
  }
6542
6663
  }
6543
- function transcriptMtimeMs(worktree, projectsDir = join6(homedir2(), ".claude", "projects")) {
6664
+ function transcriptMtimeMs(worktree, projectsDir = join8(homedir2(), ".claude", "projects")) {
6544
6665
  const dir = transcriptDir(worktree, projectsDir);
6545
6666
  let files;
6546
6667
  try {
6547
- files = readdirSync4(dir);
6668
+ files = readdirSync5(dir);
6548
6669
  } catch {
6549
6670
  return null;
6550
6671
  }
@@ -6552,7 +6673,7 @@ function transcriptMtimeMs(worktree, projectsDir = join6(homedir2(), ".claude",
6552
6673
  for (const f of files) {
6553
6674
  if (!f.endsWith(".jsonl")) continue;
6554
6675
  try {
6555
- const mtime2 = statSync3(join6(dir, f)).mtimeMs;
6676
+ const mtime2 = statSync4(join8(dir, f)).mtimeMs;
6556
6677
  if (latest === null || mtime2 > latest) latest = mtime2;
6557
6678
  } catch {
6558
6679
  }
@@ -6560,11 +6681,11 @@ function transcriptMtimeMs(worktree, projectsDir = join6(homedir2(), ".claude",
6560
6681
  return latest;
6561
6682
  }
6562
6683
  function nextRunLogPath(runLogsDir, runId) {
6563
- const dir = join6(runLogsDir, String(runId));
6684
+ const dir = join8(runLogsDir, String(runId));
6564
6685
  mkdirSync4(dir, { recursive: true });
6565
6686
  let files = [];
6566
6687
  try {
6567
- files = readdirSync4(dir);
6688
+ files = readdirSync5(dir);
6568
6689
  } catch {
6569
6690
  }
6570
6691
  let next = 0;
@@ -6572,7 +6693,7 @@ function nextRunLogPath(runLogsDir, runId) {
6572
6693
  const m = /^(\d+)\.json/.exec(f);
6573
6694
  if (m) next = Math.max(next, Number(m[1]) + 1);
6574
6695
  }
6575
- return join6(dir, `${next}.json`);
6696
+ return join8(dir, `${next}.json`);
6576
6697
  }
6577
6698
  function openNextRunLog(runLogsDir, runId) {
6578
6699
  for (let attempt = 0; attempt < 50; attempt++) {
@@ -6646,16 +6767,16 @@ function makeLauncher(cfg) {
6646
6767
  }
6647
6768
  }
6648
6769
  };
6649
- const cfgPath = join6(worktree, ".swarm-mcp.json");
6650
- writeFileSync6(cfgPath, JSON.stringify(mcpConfig, null, 2));
6770
+ const cfgPath = join8(worktree, ".swarm-mcp.json");
6771
+ writeFileSync7(cfgPath, JSON.stringify(mcpConfig, null, 2));
6651
6772
  const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
6652
6773
  const pluginDirs = roleDirs(worktree, run.kind);
6653
6774
  const realHome = cfg.droneIsolation?.realHome ?? homedir2();
6654
6775
  const home = cfg.droneIsolation ? buildDroneHome({ realHome, droneHome: cfg.droneIsolation.home }) : void 0;
6655
6776
  const settings = cfg.droneIsolation ? droneSettingsJson(realHome, cfg.droneIsolation.swarmHome) : void 0;
6656
- 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"))) {
6657
6778
  console.error(
6658
- `[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.`
6659
6780
  );
6660
6781
  }
6661
6782
  let stdio = "ignore";
@@ -6689,8 +6810,8 @@ function makeLauncher(cfg) {
6689
6810
  init_procCleanup();
6690
6811
 
6691
6812
  // src/cost.ts
6692
- import { readFileSync as readFileSync7, readdirSync as readdirSync5, renameSync as renameSync5 } from "fs";
6693
- 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";
6694
6815
 
6695
6816
  // src/pricing.ts
6696
6817
  var MODEL_PRICING = {
@@ -6741,23 +6862,23 @@ function launchCostUsd(result, model) {
6741
6862
  var UNPROCESSED_LOG_RE = /^\d+\.json$/;
6742
6863
  function unprocessedLogFiles(dir) {
6743
6864
  try {
6744
- return readdirSync5(dir).filter((f) => UNPROCESSED_LOG_RE.test(f));
6865
+ return readdirSync6(dir).filter((f) => UNPROCESSED_LOG_RE.test(f));
6745
6866
  } catch {
6746
6867
  return [];
6747
6868
  }
6748
6869
  }
6749
6870
  function accountRunCost(deps, run) {
6750
- const dir = join7(deps.runLogsDir, String(run.id));
6871
+ const dir = join9(deps.runLogsDir, String(run.id));
6751
6872
  const files = unprocessedLogFiles(dir);
6752
6873
  if (files.length === 0) return;
6753
6874
  const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? null;
6754
6875
  let added = 0;
6755
6876
  const processed = [];
6756
6877
  for (const f of files) {
6757
- const full = join7(dir, f);
6878
+ const full = join9(dir, f);
6758
6879
  let raw = null;
6759
6880
  try {
6760
- raw = readFileSync7(full, "utf8");
6881
+ raw = readFileSync9(full, "utf8");
6761
6882
  } catch {
6762
6883
  continue;
6763
6884
  }
@@ -6784,14 +6905,14 @@ function makeCostAccountant(deps) {
6784
6905
  }
6785
6906
 
6786
6907
  // src/runOutcome.ts
6787
- import { readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
6788
- import { join as join8 } from "path";
6908
+ import { readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
6909
+ import { join as join10 } from "path";
6789
6910
  var RESULT_FILE_RE = /^(\d+)\.json(\.done)?$/;
6790
6911
  function classifyExit(runLogsDir, runId) {
6791
- const dir = join8(runLogsDir, String(runId));
6912
+ const dir = join10(runLogsDir, String(runId));
6792
6913
  let files;
6793
6914
  try {
6794
- files = readdirSync6(dir);
6915
+ files = readdirSync7(dir);
6795
6916
  } catch {
6796
6917
  return "unknown";
6797
6918
  }
@@ -6805,7 +6926,7 @@ function classifyExit(runLogsDir, runId) {
6805
6926
  if (!latest) return "unknown";
6806
6927
  let raw;
6807
6928
  try {
6808
- raw = readFileSync8(join8(dir, latest.name), "utf8");
6929
+ raw = readFileSync10(join10(dir, latest.name), "utf8");
6809
6930
  } catch {
6810
6931
  return "unknown";
6811
6932
  }
@@ -6819,8 +6940,8 @@ function makeExitClassifier(runLogsDir) {
6819
6940
  init_git();
6820
6941
 
6821
6942
  // src/deps.ts
6822
- import { existsSync as existsSync9, readdirSync as readdirSync8, readFileSync as readFileSync9 } from "fs";
6823
- 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";
6824
6945
  import { execa } from "execa";
6825
6946
  var PREPARE_TIMEOUT_MS = 15 * 6e4;
6826
6947
  var MAX_PLAN_COMMANDS = 12;
@@ -6836,21 +6957,21 @@ async function runCommand(cmd, worktree) {
6836
6957
  }
6837
6958
  function safeReadText(p) {
6838
6959
  try {
6839
- return readFileSync9(p, "utf8");
6960
+ return readFileSync11(p, "utf8");
6840
6961
  } catch {
6841
6962
  return null;
6842
6963
  }
6843
6964
  }
6844
6965
  function safeListDirs(p) {
6845
6966
  try {
6846
- 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);
6847
6968
  } catch {
6848
6969
  return [];
6849
6970
  }
6850
6971
  }
6851
6972
  function safeListFiles(p) {
6852
6973
  try {
6853
- 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);
6854
6975
  } catch {
6855
6976
  return [];
6856
6977
  }
@@ -6878,19 +6999,19 @@ function overridePlan(override) {
6878
6999
  function detectPackageManager(worktree, io = {}) {
6879
7000
  const exists = io.exists ?? existsSync9;
6880
7001
  const readText = io.readText ?? safeReadText;
6881
- const hasPkgJson = exists(join10(worktree, "package.json"));
7002
+ const hasPkgJson = exists(join12(worktree, "package.json"));
6882
7003
  if (!hasPkgJson) {
6883
- 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";
6884
7005
  return null;
6885
7006
  }
6886
- const pkg = readJson(readText, join10(worktree, "package.json"));
7007
+ const pkg = readJson(readText, join12(worktree, "package.json"));
6887
7008
  const declared = typeof pkg?.packageManager === "string" ? pkg.packageManager.split("@")[0] : void 0;
6888
7009
  if (declared === "pnpm" || declared === "bun" || declared === "npm" || declared === "yarn") return declared;
6889
- if (exists(join10(worktree, "bun.lock")) || exists(join10(worktree, "bun.lockb"))) return "bun";
6890
- if (exists(join10(worktree, "pnpm-lock.yaml"))) return "pnpm";
6891
- if (exists(join10(worktree, "yarn.lock"))) return "yarn";
6892
- if (exists(join10(worktree, "package-lock.json"))) return "npm";
6893
- 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";
6894
7015
  return "npm";
6895
7016
  }
6896
7017
  function jsInstallCommand(pm) {
@@ -6951,7 +7072,7 @@ function detectPlan(worktree, io) {
6951
7072
  // node_modules на месте — установку пропускаем (быстрый путь переиспользованного worktree).
6952
7073
  // У deno каталога может не быть вовсе (кэш в DENO_DIR) — тогда `deno install` отработает
6953
7074
  // заново, но он дешёвый: тянет из того же общего кэша.
6954
- { 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" }
6955
7076
  ];
6956
7077
  for (const target of workspaceBuildTargets(worktree, io)) {
6957
7078
  commands.push({ label: `\u0441\u0431\u043E\u0440\u043A\u0430 ${target.name}`, run: jsRunScript(pm, "build"), cwd: target.dir, source: "detect" });
@@ -6959,12 +7080,12 @@ function detectPlan(worktree, io) {
6959
7080
  return { source: "detect", ecosystem: `node/${pm}`, commands };
6960
7081
  }
6961
7082
  for (const marker of ECOSYSTEM_MARKERS) {
6962
- 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));
6963
7084
  if (!hit) continue;
6964
7085
  let ecosystem = marker.ecosystem;
6965
7086
  let command = marker.command;
6966
7087
  for (const refine of marker.refine ?? []) {
6967
- if (!exists(join10(worktree, refine.file))) continue;
7088
+ if (!exists(join12(worktree, refine.file))) continue;
6968
7089
  ecosystem = refine.ecosystem;
6969
7090
  command = refine.command;
6970
7091
  break;
@@ -7021,12 +7142,12 @@ var CI_INSTALL_PATTERNS = [
7021
7142
  function ciWorkflowPlan(worktree, io) {
7022
7143
  const readText = io.readText ?? safeReadText;
7023
7144
  const listFiles = io.listFiles ?? safeListFiles;
7024
- const dir = join10(worktree, ".github", "workflows");
7145
+ const dir = join12(worktree, ".github", "workflows");
7025
7146
  const files = listFiles(dir).filter((n) => n.endsWith(".yml") || n.endsWith(".yaml"));
7026
7147
  const seen = /* @__PURE__ */ new Set();
7027
7148
  const commands = [];
7028
7149
  for (const name of files.sort()) {
7029
- const yaml = readText(join10(dir, name));
7150
+ const yaml = readText(join12(dir, name));
7030
7151
  if (yaml === null) continue;
7031
7152
  for (const raw of workflowRunLines(yaml)) {
7032
7153
  const line = raw.replace(/\s+/g, " ").trim();
@@ -7041,7 +7162,7 @@ function ciWorkflowPlan(worktree, io) {
7041
7162
  return commands.length ? { source: "ci-workflow", ecosystem: "ci-\u043A\u043E\u043D\u0444\u0438\u0433", commands } : null;
7042
7163
  }
7043
7164
  function workspaceGlobs(worktree, readText) {
7044
- const yaml = readText(join10(worktree, "pnpm-workspace.yaml"));
7165
+ const yaml = readText(join12(worktree, "pnpm-workspace.yaml"));
7045
7166
  if (yaml !== null) {
7046
7167
  const globs = [];
7047
7168
  let inPackages = false;
@@ -7061,7 +7182,7 @@ function workspaceGlobs(worktree, readText) {
7061
7182
  }
7062
7183
  if (globs.length) return globs;
7063
7184
  }
7064
- const pkg = readJson(readText, join10(worktree, "package.json"));
7185
+ const pkg = readJson(readText, join12(worktree, "package.json"));
7065
7186
  const ws = pkg?.workspaces;
7066
7187
  const list = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
7067
7188
  return list.filter((g) => typeof g === "string");
@@ -7072,14 +7193,14 @@ function workspaceDirs(worktree, globs, io) {
7072
7193
  if (glob.startsWith("!")) continue;
7073
7194
  const star = glob.indexOf("*");
7074
7195
  if (star === -1) {
7075
- const dir = join10(worktree, glob);
7076
- 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);
7077
7198
  continue;
7078
7199
  }
7079
- const base3 = join10(worktree, glob.slice(0, star).replace(/\/$/, ""));
7200
+ const base3 = join12(worktree, glob.slice(0, star).replace(/\/$/, ""));
7080
7201
  for (const name of io.listDirs(base3)) {
7081
- const dir = join10(base3, name);
7082
- 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);
7083
7204
  }
7084
7205
  }
7085
7206
  return [...dirs];
@@ -7107,11 +7228,11 @@ function workspaceBuildTargets(worktree, io = {}) {
7107
7228
  if (!globs.length) return [];
7108
7229
  const pkgs = [];
7109
7230
  for (const dir of workspaceDirs(worktree, globs, { exists, listDirs })) {
7110
- const pkg = readJson(readText, join10(dir, "package.json"));
7231
+ const pkg = readJson(readText, join12(dir, "package.json"));
7111
7232
  if (!pkg || typeof pkg.name !== "string") continue;
7112
7233
  const scripts = pkg.scripts ?? {};
7113
7234
  const entries = entryPaths(pkg);
7114
- 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)));
7115
7236
  const deps = Object.keys({ ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} });
7116
7237
  pkgs.push({ name: pkg.name, dir, deps, needsBuild });
7117
7238
  }
@@ -8685,7 +8806,7 @@ function makeExclusiveReader(opts) {
8685
8806
 
8686
8807
  // src/envGate.ts
8687
8808
  import { existsSync as existsSync10 } from "fs";
8688
- import { join as join16, delimiter } from "path";
8809
+ import { join as join18, delimiter } from "path";
8689
8810
  import { createConnection } from "net";
8690
8811
  import { execa as execa2 } from "execa";
8691
8812
  var PROBE_TIMEOUT_MS = 2500;
@@ -8737,7 +8858,7 @@ var COMPOSE_MARKERS = [
8737
8858
  ];
8738
8859
  function detectRequirements(repo, io = {}) {
8739
8860
  const exists = io.exists ?? existsSync10;
8740
- const marker = COMPOSE_MARKERS.find((f) => exists(join16(repo, f)));
8861
+ const marker = COMPOSE_MARKERS.find((f) => exists(join18(repo, f)));
8741
8862
  return marker ? [{ kind: "docker", raw: `docker (\u0434\u0435\u0442\u0435\u043A\u0442: ${marker})` }] : [];
8742
8863
  }
8743
8864
  function resolveRequirements(repo, declared, io = {}) {
@@ -8759,7 +8880,7 @@ async function hasCommand(name, env) {
8759
8880
  if (name.includes("/")) return existsSync10(name);
8760
8881
  const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
8761
8882
  const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
8762
- 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))));
8763
8884
  }
8764
8885
  async function portOpen(host, port) {
8765
8886
  return new Promise((resolve3) => {
@@ -8876,8 +8997,8 @@ import { basename as basename3 } from "path";
8876
8997
  import { serve as serve2 } from "@hono/node-server";
8877
8998
 
8878
8999
  // src/diff.ts
8879
- import { statSync as statSync5 } from "fs";
8880
- import { join as join17 } from "path";
9000
+ import { statSync as statSync6 } from "fs";
9001
+ import { join as join19 } from "path";
8881
9002
  import { $ as $4 } from "execa";
8882
9003
  var MAX_FILES = 200;
8883
9004
  var MAX_LINES_PER_FILE = 1500;
@@ -9020,7 +9141,7 @@ async function resolveBase(wt, baseBranch) {
9020
9141
  }
9021
9142
  function mtime(path) {
9022
9143
  try {
9023
- return statSync5(path).mtimeMs;
9144
+ return statSync6(path).mtimeMs;
9024
9145
  } catch {
9025
9146
  return 0;
9026
9147
  }
@@ -9045,7 +9166,7 @@ async function collectRunDiff(worktree, opts = {}) {
9045
9166
  files.push(pf);
9046
9167
  }
9047
9168
  }
9048
- 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)));
9049
9170
  const truncatedFiles = files.length > MAX_FILES;
9050
9171
  if (truncatedFiles) files = files.slice(0, MAX_FILES);
9051
9172
  const added = files.reduce((s, f) => s + f.added, 0);
@@ -9064,7 +9185,7 @@ async function collectRunDiff(worktree, opts = {}) {
9064
9185
  // package.json
9065
9186
  var package_default = {
9066
9187
  name: "@taskless-app/swarm",
9067
- version: "0.4.5",
9188
+ version: "0.4.7",
9068
9189
  type: "module",
9069
9190
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
9070
9191
  keywords: [
@@ -10796,16 +10917,16 @@ async function fetchExternalStorm(url, opts = {}) {
10796
10917
  }
10797
10918
 
10798
10919
  // src/anomalyState.ts
10799
- import { existsSync as existsSync11, readFileSync as readFileSync14, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "fs";
10800
- 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";
10801
10922
  var KEY_TTL_MS = 7 * 24 * 60 * 6e4;
10802
10923
  var EMPTY = { since: {}, lastFiredAt: {}, fires: {}, stopQuiet: null };
10803
10924
  function anomalyStatePath(home) {
10804
- return join18(home, "anomaly-state.json");
10925
+ return join20(home, "anomaly-state.json");
10805
10926
  }
10806
10927
  function loadAnomalyState(home, now = Date.now(), io = {}) {
10807
10928
  const exists = io.exists ?? existsSync11;
10808
- const readFile2 = io.readFile ?? ((p) => readFileSync14(p, "utf8"));
10929
+ const readFile2 = io.readFile ?? ((p) => readFileSync16(p, "utf8"));
10809
10930
  const path = anomalyStatePath(home);
10810
10931
  if (!exists(path)) return { ...EMPTY };
10811
10932
  try {
@@ -10831,7 +10952,7 @@ function loadAnomalyState(home, now = Date.now(), io = {}) {
10831
10952
  function saveAnomalyState(home, state, io = {}) {
10832
10953
  const path = anomalyStatePath(home);
10833
10954
  const writeFile2 = io.writeFile ?? ((p, data) => {
10834
- writeFileSync11(`${p}.tmp`, data);
10955
+ writeFileSync12(`${p}.tmp`, data);
10835
10956
  renameSync6(`${p}.tmp`, p);
10836
10957
  });
10837
10958
  writeFile2(path, JSON.stringify(state, null, 2));
@@ -11474,10 +11595,10 @@ ${task.description}` : `\u0417\u0430\u0434\u0430\u0447\u0430 ${run.task_slug} (\
11474
11595
  }
11475
11596
  return parts.join("\n");
11476
11597
  }
11477
- var HERO_CHARTER_PATH = join19(dirname5(THIS_FILE), "..", "docs", "HERO.md");
11598
+ var HERO_CHARTER_PATH = join21(dirname5(THIS_FILE), "..", "docs", "HERO.md");
11478
11599
  function buildHeroPrompt(task, run, taskContext, attachmentsBlock) {
11479
11600
  const name = run.name || `#${run.id}`;
11480
- const charter = readFileSync15(HERO_CHARTER_PATH, "utf8");
11601
+ const charter = readFileSync17(HERO_CHARTER_PATH, "utf8");
11481
11602
  const head = task ? `\u042D\u041F\u0418\u041A ${run.task_slug}: ${task.title}
11482
11603
 
11483
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).`;
@@ -11813,9 +11934,9 @@ function repoHasCi(deps, run) {
11813
11934
  function repoHasCiWorkflows(worktreePath, repo) {
11814
11935
  for (const base3 of [worktreePath, repo]) {
11815
11936
  if (!base3) continue;
11816
- const dir = join19(base3, ".github", "workflows");
11937
+ const dir = join21(base3, ".github", "workflows");
11817
11938
  try {
11818
- 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;
11819
11940
  } catch {
11820
11941
  }
11821
11942
  }
@@ -13104,7 +13225,7 @@ async function resolveConflict(deps, reviewRun) {
13104
13225
  workspace_id: reviewRun.workspace_id
13105
13226
  // та же задача, тот же воркспейс (TASWM-288)
13106
13227
  });
13107
- 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}`);
13108
13229
  try {
13109
13230
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
13110
13231
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -13156,7 +13277,7 @@ async function spawnReviewer(deps, reviewRun, files) {
13156
13277
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
13157
13278
  workspace_id: reviewRun.workspace_id
13158
13279
  });
13159
- 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}`);
13160
13281
  try {
13161
13282
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
13162
13283
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
@@ -13251,20 +13372,51 @@ async function syncCrashedOnStartup(deps) {
13251
13372
  }
13252
13373
  return { orphaned, merged, requeued };
13253
13374
  }
13254
- async function makeStore(repo, worktreesDir) {
13375
+ async function makeStore(repo, worktreesDir, repoFlag) {
13255
13376
  const host = loadHostIdentity({
13256
13377
  home: SWARM_HOME,
13257
13378
  envHostId: process.env.SWARM_HOST_ID,
13258
13379
  envHostName: process.env.SWARM_HOST_NAME
13259
13380
  });
13260
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
+ }
13261
13398
  if (SWARM_BACKEND !== "client") return { store: new SwarmDb(DB_PATH, repo), reconciled: true, host };
13262
13399
  const call = resilientSwarmMcp({
13263
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.`),
13264
13401
  onUp: () => console.warn("[overmind] \u043F\u0440\u043E\u0434-MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
13265
13402
  });
13266
- const repoPaths = effectiveRepoPaths(repo, SWARM_REPO_PATHS);
13267
- const repoRegistry = await buildRepoRegistry(repoPaths);
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 }));
13268
13420
  if (repoPaths.length > 0 && repoRegistry.length < repoPaths.length) {
13269
13421
  console.warn(
13270
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)`
@@ -13272,7 +13424,7 @@ async function makeStore(repo, worktreesDir) {
13272
13424
  }
13273
13425
  if (repoRegistry.length === 0) {
13274
13426
  console.warn(
13275
- "[overmind] \u0420\u0415\u041F\u041E-\u0420\u0415\u0415\u0421\u0422\u0420 \u041F\u0423\u0421\u0422: \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0435 \u043F\u0440\u043E\u0433\u043E\u043D\u044B (\u0442\u0435, \u0447\u0442\u043E \u043D\u0435 \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u044B \u0437\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u043E\u0439) \u0431\u0440\u0430\u0442\u044C\u0441\u044F \u041D\u0415 \u0431\u0443\u0434\u0443\u0442 \u2014 \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 \u0441 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u043C \u043F\u0443\u0442\u0451\u043C \u043D\u0435\u0447\u0435\u043C. \u0423\u043A\u0430\u0436\u0438 \u043F\u0443\u0442\u044C: swarm start --repo <\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E> (\u0438\u043B\u0438 SWARM_REPO/SWARM_REPOS \u0432 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0438)."
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).`
13276
13428
  );
13277
13429
  }
13278
13430
  const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry, host });
@@ -13309,7 +13461,7 @@ async function migrateWorktreesAtStartup() {
13309
13461
  exists: (d) => existsSync12(d),
13310
13462
  isNonEmptyDir: (d) => {
13311
13463
  try {
13312
- return readdirSync9(d).length > 0;
13464
+ return readdirSync10(d).length > 0;
13313
13465
  } catch {
13314
13466
  return false;
13315
13467
  }
@@ -13340,7 +13492,11 @@ async function runDaemon(opts) {
13340
13492
  installCrashGuards();
13341
13493
  const daemonStart = await captureDaemonStart(THIS_FILE);
13342
13494
  const worktreesDir = await migrateWorktreesAtStartup();
13343
- 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);
13344
13500
  await preflightAuth();
13345
13501
  const tl = await connectTaskless();
13346
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.5",
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": [