@taskless-app/swarm 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { basename as basename4 } from "path";
5
- import { existsSync as existsSync8, readdirSync as readdirSync6 } from "fs";
5
+ import { existsSync as existsSync9, readdirSync as readdirSync6, statSync as statSync7, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
6
6
  import { createInterface } from "readline/promises";
7
7
  import { setTimeout as sleep2 } from "timers/promises";
8
8
 
@@ -448,6 +448,8 @@ function groupByHost(items, ref, opts = {}) {
448
448
  unknown,
449
449
  lastSeenAt: unknown ? null : info?.lastSeenAt ?? null,
450
450
  offline: false,
451
+ paused: unknown ? false : info?.paused === true,
452
+ pauseReason: unknown ? null : info?.pauseReason ?? null,
451
453
  runs: []
452
454
  };
453
455
  byKey.set(key, g);
@@ -477,7 +479,9 @@ function hostStateText(host, nowMs = Date.now(), staleMs = HOST_HEARTBEAT_STALE_
477
479
  if (!host.lastSeenAt) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
478
480
  const at = Date.parse(host.lastSeenAt);
479
481
  if (Number.isNaN(at)) return "\u043F\u0443\u043B\u044C\u0441\u0430 \u043D\u0435 \u0431\u044B\u043B\u043E";
480
- if (!isHostOffline(host.lastSeenAt, nowMs, staleMs)) return "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
482
+ if (!isHostOffline(host.lastSeenAt, nowMs, staleMs)) {
483
+ return host.paused ? host.pauseReason ? `\u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 ${host.pauseReason}` : "\u043D\u0430 \u043F\u0430\u0443\u0437\u0435" : "\u043D\u0430 \u0441\u0432\u044F\u0437\u0438";
484
+ }
481
485
  const min = Math.max(1, Math.round((nowMs - at) / 6e4));
482
486
  return `\u043C\u043E\u043B\u0447\u0438\u0442 ${min} \u043C\u0438\u043D`;
483
487
  }
@@ -581,6 +585,8 @@ var SWARM_STORM_URL = process.env.SWARM_STORM_URL || "";
581
585
  var WEB_PORT = Number(process.env.SWARM_WEB_PORT) || 7420;
582
586
  var WEB_REDIRECT_URI = process.env.SWARM_REDIRECT_URI || `http://127.0.0.1:${WEB_PORT}/callback`;
583
587
  var SWARM_ANOMALY_RENOTIFY_MS = envNum("SWARM_ANOMALY_RENOTIFY_MS", 60 * 6e4);
588
+ var SWARM_ANOMALY_MAX_REPEATS = Math.trunc(envNum("SWARM_ANOMALY_MAX_REPEATS", 3));
589
+ var SWARM_ANOMALY_RUN_DOWN_WINDOW_MS = envNum("SWARM_ANOMALY_RUN_DOWN_WINDOW_MS", 24 * 60 * 6e4);
584
590
  var SWARM_ANOMALY_FAILED_SPIKE_COUNT = Math.trunc(envNum("SWARM_ANOMALY_FAILED_SPIKE_COUNT", 3));
585
591
  var SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS = envNum("SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS", 30 * 6e4);
586
592
  var SWARM_ANOMALY_QUEUE_STALLED_MS = envNum("SWARM_ANOMALY_QUEUE_STALLED_MS", 10 * 6e4);
@@ -1670,6 +1676,10 @@ var SwarmClient = class _SwarmClient {
1670
1676
  depsDoneInProd = /* @__PURE__ */ new Set();
1671
1677
  depsAskedAt = /* @__PURE__ */ new Map();
1672
1678
  static DEPS_TTL_MS = 6e4;
1679
+ // TASWM-327: прод старее демона и не знает аргумент `level` у swarm_notify. Выясняется
1680
+ // ровно один раз (первой отвергнутой нотификацией) и держится всю жизнь процесса — иначе
1681
+ // каждый сигнал платил бы за это лишним отвергнутым вызовом. См. notifyAnomaly.
1682
+ notifyLevelUnsupported = false;
1673
1683
  // Корреляция с продом.
1674
1684
  keyByLocal = /* @__PURE__ */ new Map();
1675
1685
  // local run id → runKey
@@ -2731,9 +2741,32 @@ var SwarmClient = class _SwarmClient {
2731
2741
  lastSeenAt: typeof h.lastSeenAt === "string" ? h.lastSeenAt : null,
2732
2742
  // TASWM-298: репозитории машины — «на связи» ещё не значит «возьмёт эту задачу».
2733
2743
  // Прод старой версии поля не пришлёт → пустой список, строка просто без хвоста.
2734
- reposServed: Array.isArray(h.reposServed) ? h.reposServed.filter((r) => typeof r === "string") : []
2744
+ reposServed: Array.isArray(h.reposServed) ? h.reposServed.filter((r) => typeof r === "string") : [],
2745
+ // TASWM-326: машина на паузе — «на связи, но работу не берёт». Прод старой
2746
+ // версии поля не пришлёт → undefined, строка выглядит как раньше.
2747
+ ...typeof h.paused === "boolean" ? { paused: h.paused } : {},
2748
+ ...typeof h.pausedAt === "string" ? { pausedAt: h.pausedAt } : {},
2749
+ ...typeof h.pauseReason === "string" ? { pauseReason: h.pauseReason } : {}
2735
2750
  }));
2736
2751
  }
2752
+ /**
2753
+ * TASWM-326: убрать ЭТУ машину из парка (`swarm remove`). Прод удаляет строку
2754
+ * SwarmHost и освобождает её незавершённые прогоны — ровно то же, что делает свипер
2755
+ * с молчащей машиной, только сразу и по явной команде. Без этого выведенная из роя
2756
+ * машина висела в списке вечно с подписью «молчит N минут», неотличимо от упавшей.
2757
+ *
2758
+ * ПРЯМОЙ вызов, не outbox: это не durable-факт роя, который надо досылать после
2759
+ * восстановления сети, — команда имеет смысл только сейчас. Бросает при сбое:
2760
+ * вызывающий обязан сказать человеку, что из парка машину убрать не удалось, а не
2761
+ * молча стереть локальные токены (тогда её вообще нечем будет убрать).
2762
+ */
2763
+ async forgetHost() {
2764
+ if (!this.host) throw new Error("\u043C\u0430\u0448\u0438\u043D\u0430 \u043D\u0435 \u043F\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043B\u0430\u0441\u044C (\u043D\u0435\u0442 hostId) \u2014 \u043D\u0435\u0447\u0435\u0433\u043E \u0443\u0431\u0438\u0440\u0430\u0442\u044C \u0438\u0437 \u043F\u0430\u0440\u043A\u0430");
2765
+ const res = await this.call("swarm_host_forget", { hostId: this.host.hostId });
2766
+ if (res?.isError) throw new Error("swarm_host_forget \u043E\u0442\u0432\u0435\u0440\u0433 \u0437\u0430\u043F\u0440\u043E\u0441 \u2014 \u043C\u0430\u0448\u0438\u043D\u0430 \u043E\u0441\u0442\u0430\u043B\u0430\u0441\u044C \u0432 \u043F\u0430\u0440\u043A\u0435");
2767
+ const sc = res.structuredContent ?? {};
2768
+ return { removed: sc.removed !== false, released: typeof sc.released === "number" ? sc.released : 0 };
2769
+ }
2737
2770
  /**
2738
2771
  * Пульс overmind в прод (TASWM-129): swarm_heartbeat обновляет User.swarmHeartbeatAt —
2739
2772
  * дашборд /swarm сравнивает свежесть со своим порогом устаревания («нет связи с
@@ -2761,10 +2794,21 @@ var SwarmClient = class _SwarmClient {
2761
2794
  * «пришли хвост лога» (logRequests). Возвращаем их вызывающему; пульс недоступен или
2762
2795
  * прод старый (поля нет) → пустой список, всё как раньше.
2763
2796
  */
2764
- async heartbeat(daemonVersion) {
2797
+ /**
2798
+ * TASWM-326: тем же пульсом уходит ПАУЗА этой машины (`pause` — durable-запись из
2799
+ * pause.json либо null). В парке машин «на паузе» обязано отличаться от «нет работы»
2800
+ * и от «молчит», а единственный канал «демон → прод» с нужной кадансностью — пульс.
2801
+ * Шлём ВСЕГДА (в т.ч. `paused:false`): это состояние, а не команда, и молчание тут
2802
+ * означало бы «прод помнит вчерашнюю паузу», когда рой давно работает.
2803
+ */
2804
+ async heartbeat(daemonVersion, pause) {
2765
2805
  try {
2766
2806
  const args = this.repoFullNames.length ? { repoFullNames: this.repoFullNames } : {};
2767
2807
  if (daemonVersion) args.daemonVersion = daemonVersion;
2808
+ if (pause !== void 0) {
2809
+ args.paused = !!pause?.paused;
2810
+ if (pause?.reason) args.pauseReason = pause.reason;
2811
+ }
2768
2812
  if (this.host) {
2769
2813
  args.hostId = this.host.hostId;
2770
2814
  args.hostName = this.host.hostName;
@@ -2820,13 +2864,26 @@ var SwarmClient = class _SwarmClient {
2820
2864
  */
2821
2865
  async notifyAnomaly(signal) {
2822
2866
  const prodRunId = signal.runId != null ? this.prodIdByKey.get(this.runKeyFor(signal.runId) ?? "") : void 0;
2823
- const res = await this.call("swarm_notify", {
2867
+ const base3 = {
2824
2868
  tag: signal.key,
2825
2869
  title: signal.title,
2826
2870
  body: signal.body,
2827
2871
  ...signal.href ? { href: signal.href } : {},
2828
2872
  ...prodRunId ? { runId: prodRunId } : {}
2829
- });
2873
+ };
2874
+ if (!this.notifyLevelUnsupported) {
2875
+ const res2 = await this.call("swarm_notify", { ...base3, level: signal.level ?? "decision" });
2876
+ if (!res2?.isError) return;
2877
+ const text = JSON.stringify(res2);
2878
+ if (!(text.includes("\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D") && text.includes("\u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442") && text.includes("level"))) {
2879
+ throw new Error(`swarm_notify \u043E\u0442\u0432\u0435\u0440\u0433 \xAB${signal.key}\xBB`);
2880
+ }
2881
+ this.notifyLevelUnsupported = true;
2882
+ console.warn(
2883
+ "[swarm-client] \u043F\u0440\u043E\u0434 \u0441\u0442\u0430\u0440\u0435\u0435 \u0434\u0435\u043C\u043E\u043D\u0430 (swarm_notify \u0431\u0435\u0437 level) \u2014 \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F \u0440\u043E\u044F \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u0438\u0434\u0443\u0442 \u043E\u0434\u043D\u0438\u043C \u0443\u0440\u043E\u0432\u043D\u0435\u043C, \u043A\u0430\u043A \u0434\u043E TASWM-327. \u041E\u0431\u043D\u043E\u0432\u0438 \u043F\u0440\u043E\u0434 \u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0434\u0435\u043C\u043E\u043D."
2884
+ );
2885
+ }
2886
+ const res = await this.call("swarm_notify", base3);
2830
2887
  if (res?.isError) throw new Error(`swarm_notify \u043E\u0442\u0432\u0435\u0440\u0433 \xAB${signal.key}\xBB`);
2831
2888
  }
2832
2889
  /**
@@ -3228,6 +3285,85 @@ function parseDecision(v) {
3228
3285
  }
3229
3286
  }
3230
3287
 
3288
+ // src/daemonLaunch.ts
3289
+ import { openSync as openSync2, closeSync as closeSync2, renameSync as renameSync2, statSync as statSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
3290
+ import { spawn } from "child_process";
3291
+ var FOREGROUND_FLAGS = /* @__PURE__ */ new Set(["--foreground", "-f", "--attach"]);
3292
+ function parseStartMode(argv) {
3293
+ const rest = argv.filter((a) => !FOREGROUND_FLAGS.has(a));
3294
+ return { foreground: rest.length !== argv.length, rest };
3295
+ }
3296
+ function childStartArgv(rest) {
3297
+ return ["start", ...rest.filter((a) => !FOREGROUND_FLAGS.has(a)), "--foreground"];
3298
+ }
3299
+ function rotateDaemonLog(path, maxBytes) {
3300
+ let size;
3301
+ try {
3302
+ size = statSync2(path).size;
3303
+ } catch {
3304
+ return "missing";
3305
+ }
3306
+ if (size <= maxBytes) return "kept";
3307
+ renameSync2(path, `${path}.1`);
3308
+ writeFileSync3(path, "");
3309
+ return "rotated";
3310
+ }
3311
+ function tailText(text, lines) {
3312
+ const rows = text.split("\n");
3313
+ if (rows[rows.length - 1] === "") rows.pop();
3314
+ return rows.slice(-lines).join("\n");
3315
+ }
3316
+ async function waitForReady(opts) {
3317
+ const now = opts.now ?? Date.now;
3318
+ const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
3319
+ const started = now();
3320
+ for (; ; ) {
3321
+ if (await opts.probe()) return "ready";
3322
+ if (!opts.alive()) return "died";
3323
+ if (now() - started >= opts.timeoutMs) return "timeout";
3324
+ await sleep3(opts.pollMs);
3325
+ }
3326
+ }
3327
+ function launchDetached(opts) {
3328
+ const fd = openSync2(opts.logPath, "a");
3329
+ try {
3330
+ const child = spawn(opts.execPath, [...opts.execArgv, opts.entry, ...opts.args], {
3331
+ detached: true,
3332
+ stdio: ["ignore", fd, fd],
3333
+ env: opts.env
3334
+ });
3335
+ child.unref();
3336
+ let exited = false;
3337
+ child.once("exit", () => {
3338
+ exited = true;
3339
+ });
3340
+ return {
3341
+ pid: child.pid ?? -1,
3342
+ // Не `child.killed` (он про посланный сигнал) и не exit-код: после unref событие
3343
+ // может не прийти вовсе — спрашиваем ядро сигналом 0, это и есть «процесс есть».
3344
+ alive: () => {
3345
+ if (exited) return false;
3346
+ if (!child.pid) return false;
3347
+ try {
3348
+ process.kill(child.pid, 0);
3349
+ return true;
3350
+ } catch {
3351
+ return false;
3352
+ }
3353
+ }
3354
+ };
3355
+ } finally {
3356
+ closeSync2(fd);
3357
+ }
3358
+ }
3359
+ function tailFile(path, lines) {
3360
+ try {
3361
+ return tailText(readFileSync4(path, "utf8"), lines);
3362
+ } catch {
3363
+ return "";
3364
+ }
3365
+ }
3366
+
3231
3367
  // src/procCleanup.ts
3232
3368
  import { execFileSync as execFileSync3 } from "child_process";
3233
3369
 
@@ -3489,7 +3625,7 @@ async function buildRepoRegistry(paths) {
3489
3625
 
3490
3626
  // src/hostIdentity.ts
3491
3627
  import { hostname } from "os";
3492
- import { existsSync as existsSync4, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
3628
+ import { existsSync as existsSync4, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
3493
3629
  import { join as join3 } from "path";
3494
3630
  import { randomUUID as randomUUID2 } from "crypto";
3495
3631
  function hostIdentityPath(home) {
@@ -3497,10 +3633,10 @@ function hostIdentityPath(home) {
3497
3633
  }
3498
3634
  function loadHostIdentity(deps) {
3499
3635
  const exists = deps.exists ?? existsSync4;
3500
- const readFile2 = deps.readFile ?? ((p) => readFileSync4(p, "utf8"));
3636
+ const readFile2 = deps.readFile ?? ((p) => readFileSync5(p, "utf8"));
3501
3637
  const writeFile2 = deps.writeFile ?? ((p, data) => {
3502
- writeFileSync3(`${p}.tmp`, data);
3503
- renameSync2(`${p}.tmp`, p);
3638
+ writeFileSync4(`${p}.tmp`, data);
3639
+ renameSync3(`${p}.tmp`, p);
3504
3640
  });
3505
3641
  const host = deps.hostname ?? hostname;
3506
3642
  const newId = deps.newId ?? randomUUID2;
@@ -3608,7 +3744,7 @@ import { basename as basename2 } from "path";
3608
3744
  import { serve as serve2 } from "@hono/node-server";
3609
3745
 
3610
3746
  // src/diff.ts
3611
- import { statSync as statSync2 } from "fs";
3747
+ import { statSync as statSync3 } from "fs";
3612
3748
  import { join as join4 } from "path";
3613
3749
  import { $ as $2 } from "execa";
3614
3750
  var MAX_FILES = 200;
@@ -3752,7 +3888,7 @@ async function resolveBase(wt, baseBranch) {
3752
3888
  }
3753
3889
  function mtime(path) {
3754
3890
  try {
3755
- return statSync2(path).mtimeMs;
3891
+ return statSync3(path).mtimeMs;
3756
3892
  } catch {
3757
3893
  return 0;
3758
3894
  }
@@ -3793,6 +3929,64 @@ async function collectRunDiff(worktree, opts = {}) {
3793
3929
  };
3794
3930
  }
3795
3931
 
3932
+ // package.json
3933
+ var package_default = {
3934
+ name: "@taskless-app/swarm",
3935
+ version: "0.4.3",
3936
+ type: "module",
3937
+ description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3938
+ keywords: [
3939
+ "ai-agents",
3940
+ "coding-agents",
3941
+ "swarm",
3942
+ "orchestration",
3943
+ "autonomous-agents",
3944
+ "mcp",
3945
+ "taskless",
3946
+ "cli"
3947
+ ],
3948
+ license: "SEE LICENSE IN LICENSE",
3949
+ author: "Ilya Smurov <ilya@smurov.com>",
3950
+ homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
3951
+ repository: {
3952
+ type: "git",
3953
+ url: "git+https://github.com/ilyasmurov/taskless.git",
3954
+ directory: "packages/swarm-daemon"
3955
+ },
3956
+ engines: {
3957
+ node: ">=22"
3958
+ },
3959
+ bin: {
3960
+ swarm: "dist/cli.js"
3961
+ },
3962
+ files: [
3963
+ "dist",
3964
+ "docs/HERO.md",
3965
+ "roles"
3966
+ ],
3967
+ scripts: {
3968
+ overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
3969
+ swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
3970
+ build: "tsup",
3971
+ start: "node dist/overmind.js",
3972
+ prepublishOnly: "pnpm run build",
3973
+ test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
3974
+ "test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
3975
+ "test:watch": "vitest",
3976
+ typecheck: "tsc -p tsconfig.json --noEmit",
3977
+ lint: "echo 'no lint in swarm-daemon yet'"
3978
+ },
3979
+ dependencies: {
3980
+ "@hono/node-server": "^2.0.12",
3981
+ "@modelcontextprotocol/sdk": "^1.29.0",
3982
+ execa: "^10.0.0",
3983
+ zod: "3.25.76"
3984
+ },
3985
+ optionalDependencies: {
3986
+ "better-sqlite3": "^12.11.1"
3987
+ }
3988
+ };
3989
+
3796
3990
  // src/gh.ts
3797
3991
  import { $ as $3 } from "execa";
3798
3992
  var NO_CHECKS_STDERR = /no checks reported/i;
@@ -3841,18 +4035,18 @@ async function ghChecks(pr, run = realChecksRunner) {
3841
4035
 
3842
4036
  // src/droneChannel.ts
3843
4037
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3844
- import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
4038
+ import { chmodSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3845
4039
  import { dirname as dirname2 } from "path";
3846
4040
  import { serve } from "@hono/node-server";
3847
4041
  function loadChannelSecret(path) {
3848
4042
  try {
3849
- const s = readFileSync5(path, "utf8").trim();
4043
+ const s = readFileSync6(path, "utf8").trim();
3850
4044
  if (s.length >= 32) return s;
3851
4045
  } catch {
3852
4046
  }
3853
4047
  const secret = randomBytes(32).toString("hex");
3854
4048
  mkdirSync3(dirname2(path), { recursive: true });
3855
- writeFileSync4(path, secret, { mode: 384 });
4049
+ writeFileSync5(path, secret, { mode: 384 });
3856
4050
  try {
3857
4051
  chmodSync(path, 384);
3858
4052
  } catch {
@@ -3886,7 +4080,7 @@ var EVENT_KIND_SET = new Set(EVENT_KINDS);
3886
4080
 
3887
4081
  // src/oauth.ts
3888
4082
  import { createHash, randomBytes as randomBytes2 } from "crypto";
3889
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync } from "fs";
4083
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync } from "fs";
3890
4084
  import { join as join5 } from "path";
3891
4085
  var REGISTER = "/api/oauth/register";
3892
4086
  var AUTHORIZE = "/api/oauth/authorize";
@@ -3978,13 +4172,13 @@ function toStore(clientId, t, now) {
3978
4172
  }
3979
4173
  function loadStore() {
3980
4174
  try {
3981
- return JSON.parse(readFileSync6(STORE_PATH, "utf8"));
4175
+ return JSON.parse(readFileSync7(STORE_PATH, "utf8"));
3982
4176
  } catch {
3983
4177
  return null;
3984
4178
  }
3985
4179
  }
3986
4180
  function saveStore(s) {
3987
- writeFileSync5(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
4181
+ writeFileSync6(STORE_PATH, JSON.stringify(s, null, 2), { mode: 384 });
3988
4182
  }
3989
4183
  function clearStore() {
3990
4184
  try {
@@ -3994,7 +4188,7 @@ function clearStore() {
3994
4188
  }
3995
4189
  function loadClientId() {
3996
4190
  try {
3997
- return JSON.parse(readFileSync6(CLIENT_PATH, "utf8")).clientId;
4191
+ return JSON.parse(readFileSync7(CLIENT_PATH, "utf8")).clientId;
3998
4192
  } catch {
3999
4193
  return null;
4000
4194
  }
@@ -4003,7 +4197,7 @@ async function ensureClientId(api, redirectUri, fetchFn = fetch) {
4003
4197
  const existing = loadClientId();
4004
4198
  if (existing) return existing;
4005
4199
  const clientId = await registerClient({ api, redirectUri }, fetchFn);
4006
- writeFileSync5(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4200
+ writeFileSync6(CLIENT_PATH, JSON.stringify({ clientId }, null, 2), { mode: 384 });
4007
4201
  return clientId;
4008
4202
  }
4009
4203
  async function ensureAccess(store, opts = {}) {
@@ -4022,6 +4216,7 @@ async function ensureAccess(store, opts = {}) {
4022
4216
  }
4023
4217
 
4024
4218
  // src/web.ts
4219
+ var PACKAGE_VERSION = package_default.version;
4025
4220
  var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
4026
4221
  var jsonForScript = (v) => JSON.stringify(v).replace(/</g, "\\u003c");
4027
4222
  var TASKLESS_MARK = `<svg viewBox="0 0 60 54" width="19" height="19" fill="none" aria-hidden="true" style="display:block"><g transform="matrix(1.0512,0,0,1.0512,0,-0.021)"><path fill="currentColor" fill-rule="nonzero" d="M46.92,0 L10.24,0 C4.59,0 0,4.59 0,10.24 L0,10.4 C0,16.05 4.59,20.64 10.24,20.64 L16.84,20.64 C19.56,20.64 22.12,19.59 24.05,17.67 L36.06,5.77 C37.25,4.6 38.82,3.95 40.49,3.95 L46.93,3.95 C50.4,3.95 53.22,6.77 53.22,10.24 L53.22,10.4 C53.22,13.87 50.4,16.69 46.93,16.69 L39.82,16.69 C37.05,16.69 34.36,17.83 32.44,19.83 L21.13,31.58 C19.28,33.5 18.27,36.02 18.27,38.68 L18.27,40.94 C18.27,46.59 22.86,51.18 28.51,51.18 C34.16,51.18 38.75,46.59 38.75,40.94 L38.75,20.74 C39.1,20.68 39.46,20.64 39.82,20.64 L46.93,20.64 C52.58,20.64 57.17,16.05 57.17,10.4 L57.17,10.24 C57.17,4.59 52.58,0 46.93,0 L46.92,0 Z M21.26,14.87 C20.07,16.04 18.5,16.69 16.83,16.69 L10.23,16.69 C6.76,16.69 3.94,13.87 3.94,10.4 L3.94,10.24 C3.94,6.77 6.76,3.95 10.23,3.95 L32.27,3.95 L21.25,14.87 L21.26,14.87 Z M34.79,40.94 C34.79,44.41 31.97,47.23 28.5,47.23 C25.03,47.23 22.21,44.41 22.21,40.94 L22.21,38.68 C22.21,37.05 22.83,35.5 23.97,34.32 L34.79,23.07 L34.79,40.94 L34.79,40.94 Z"/></g></svg>`;
@@ -5302,6 +5497,36 @@ async function runDiff(db2, id, now = Date.now()) {
5302
5497
  }
5303
5498
  var readNarratorLog = (log) => log?.() ?? [];
5304
5499
  var readRestartSnap = (restart, liveSessionIds) => restart ? { phase: restart.phase, sessionIds: liveSessionIds?.() ?? [] } : null;
5500
+ function pauseSourceOf(raw) {
5501
+ return raw === "cli" || raw === "remote" ? raw : "web";
5502
+ }
5503
+ function setPausedVia(deps, paused, source, reason) {
5504
+ const control = deps.control;
5505
+ if (control.set) {
5506
+ control.set(paused, { source, reason });
5507
+ return;
5508
+ }
5509
+ control.paused = paused;
5510
+ deps.narrate?.(
5511
+ paused ? "\u23F8 \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0443 \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u043C, \u0436\u0438\u0432\u044B\u0435 \u0434\u0440\u043E\u043D\u044B \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442." : "\u25B6\uFE0F \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0441\u043D\u044F\u043B \u0440\u043E\u0439 \u0441 \u043F\u0430\u0443\u0437\u044B \u2014 \u0441\u043D\u043E\u0432\u0430 \u0431\u0435\u0440\u0451\u043C \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438 \u0438\u043D\u0442\u0435\u0439\u043A\u0430."
5512
+ );
5513
+ }
5514
+ function daemonStatus(deps) {
5515
+ const counts = {};
5516
+ for (const r of deps.db.listRuns()) counts[r.status] = (counts[r.status] ?? 0) + 1;
5517
+ const live = deps.liveSessionIds?.() ?? [];
5518
+ return {
5519
+ hostId: deps.selfHost?.hostId ?? null,
5520
+ hostName: deps.selfHost?.hostName ?? null,
5521
+ backend: SWARM_BACKEND,
5522
+ paused: deps.control?.paused ?? false,
5523
+ pause: deps.control?.state ?? null,
5524
+ restart: deps.restart ? { phase: deps.restart.phase, intent: deps.restart.intent, sessions: live } : null,
5525
+ counts,
5526
+ live,
5527
+ version: PACKAGE_VERSION
5528
+ };
5529
+ }
5305
5530
  function eventsStream(db2, narratorLog, control, daemonVersion, restart, liveSessionIds, selfHost) {
5306
5531
  let iv;
5307
5532
  let last = "";
@@ -5470,18 +5695,25 @@ function makeHandler(deps) {
5470
5695
  return redirect("/");
5471
5696
  }
5472
5697
  if (p === "/pause" && req.method === "POST") {
5473
- if (!auth()) return unauthorized();
5698
+ if (!authed(req)) return unauthorized();
5474
5699
  if (!deps.control) return new Response("\u043F\u0430\u0443\u0437\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: \u043C\u043E\u0440\u0434\u0430 \u0431\u0435\u0437 \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430", { status: 503 });
5475
- deps.control.paused = true;
5476
- deps.narrate?.("\u23F8 \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0443 \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u043C, \u0436\u0438\u0432\u044B\u0435 \u0434\u0440\u043E\u043D\u044B \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442.");
5477
- return redirect("/");
5700
+ const f = await formValue(req);
5701
+ const reason = (f.get("reason") ?? "").trim() || null;
5702
+ setPausedVia(deps, true, pauseSourceOf(f.get("source")), reason);
5703
+ return Response.json({ ok: true, paused: true, why: "\u0440\u043E\u0439 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435 \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043D\u0435 \u0431\u0435\u0440\u0451\u043C, \u0436\u0438\u0432\u044B\u0435 \u0434\u0440\u043E\u043D\u044B \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442" });
5478
5704
  }
5479
5705
  if (p === "/resume" && req.method === "POST") {
5480
- if (!auth()) return unauthorized();
5706
+ if (!authed(req)) return unauthorized();
5481
5707
  if (!deps.control) return new Response("\u043F\u0430\u0443\u0437\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: \u043C\u043E\u0440\u0434\u0430 \u0431\u0435\u0437 \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430", { status: 503 });
5482
- deps.control.paused = false;
5483
- deps.narrate?.("\u25B6\uFE0F \u041E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u0441\u043D\u044F\u043B \u0440\u043E\u0439 \u0441 \u043F\u0430\u0443\u0437\u044B \u2014 \u0441\u043D\u043E\u0432\u0430 \u0431\u0435\u0440\u0451\u043C \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438 \u0438\u043D\u0442\u0435\u0439\u043A\u0430.");
5484
- return redirect("/");
5708
+ setPausedVia(deps, false, pauseSourceOf((await formValue(req)).get("source")), null);
5709
+ return Response.json({ ok: true, paused: false, why: "\u0440\u043E\u0439 \u0441\u043D\u044F\u0442 \u0441 \u043F\u0430\u0443\u0437\u044B \u2014 \u0441\u043D\u043E\u0432\u0430 \u0431\u0435\u0440\u0451\u043C \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438 \u0438\u043D\u0442\u0435\u0439\u043A\u0430" });
5710
+ }
5711
+ if (p === "/status") return Response.json(daemonStatus(deps));
5712
+ if (p === "/shutdown" && req.method === "POST") {
5713
+ if (!authed(req)) return unauthorized();
5714
+ if (!deps.requestStop) return new Response("\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: \u043C\u043E\u0440\u0434\u0430 \u0431\u0435\u0437 \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430", { status: 503 });
5715
+ const f = await formValue(req);
5716
+ return Response.json(deps.requestStop({ force: f.get("force") === "1", wait: f.get("wait") === "1" }));
5485
5717
  }
5486
5718
  if (p === "/revive" && req.method === "POST") {
5487
5719
  if (!auth()) return unauthorized();
@@ -5544,8 +5776,8 @@ function startWeb(deps, port = WEB_PORT) {
5544
5776
  import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDroneProcesses } from "./overmind.js";
5545
5777
 
5546
5778
  // src/launcher.ts
5547
- import { spawn } from "child_process";
5548
- import { closeSync as closeSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, openSync as openSync2, readdirSync as readdirSync4, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
5779
+ import { spawn as spawn2 } from "child_process";
5780
+ import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync5, openSync as openSync3, readdirSync as readdirSync4, statSync as statSync5, writeFileSync as writeFileSync7 } from "fs";
5549
5781
  import { createRequire as createRequire2 } from "module";
5550
5782
  import { homedir as homedir2 } from "os";
5551
5783
  import { dirname as dirname3, join as join7 } from "path";
@@ -5624,7 +5856,7 @@ function resolveClaudeBin(opts = {}) {
5624
5856
  }
5625
5857
 
5626
5858
  // src/droneHome.ts
5627
- import { lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync3, rmSync, statSync as statSync3, symlinkSync } from "fs";
5859
+ import { lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync3, rmSync, statSync as statSync4, symlinkSync } from "fs";
5628
5860
  import { join as join6, relative, sep } from "path";
5629
5861
  var DRONE_HOME_DENY_DEFAULT = [".taskless-swarm", ".bash_history", ".zsh_history"];
5630
5862
  function droneHomeDeny(env = process.env) {
@@ -5665,7 +5897,7 @@ function buildDroneHome(opts) {
5665
5897
  const target = join6(realHome, name);
5666
5898
  let isDir;
5667
5899
  try {
5668
- isDir = statSync3(target).isDirectory();
5900
+ isDir = statSync4(target).isDirectory();
5669
5901
  } catch {
5670
5902
  continue;
5671
5903
  }
@@ -5831,14 +6063,14 @@ function openNextRunLog(runLogsDir, runId) {
5831
6063
  const path = nextRunLogPath(runLogsDir, runId);
5832
6064
  let out;
5833
6065
  try {
5834
- out = openSync2(path, "wx");
6066
+ out = openSync3(path, "wx");
5835
6067
  } catch (e) {
5836
6068
  if (e.code !== "EEXIST") throw e;
5837
6069
  continue;
5838
6070
  }
5839
6071
  let err = null;
5840
6072
  try {
5841
- err = openSync2(`${path.slice(0, -".json".length)}.err.log`, "w");
6073
+ err = openSync3(`${path.slice(0, -".json".length)}.err.log`, "w");
5842
6074
  } catch {
5843
6075
  err = null;
5844
6076
  }
@@ -5899,7 +6131,7 @@ function makeLauncher(cfg) {
5899
6131
  }
5900
6132
  };
5901
6133
  const cfgPath = join7(worktree, ".swarm-mcp.json");
5902
- writeFileSync6(cfgPath, JSON.stringify(mcpConfig, null, 2));
6134
+ writeFileSync7(cfgPath, JSON.stringify(mcpConfig, null, 2));
5903
6135
  const model = run.model ?? process.env.SWARM_DRONE_MODEL ?? void 0;
5904
6136
  const pluginDirs = roleDirs(worktree, run.kind);
5905
6137
  const realHome = cfg.droneIsolation?.realHome ?? homedir2();
@@ -5919,7 +6151,7 @@ function makeLauncher(cfg) {
5919
6151
  errFd = fds.err ?? void 0;
5920
6152
  stdio = ["ignore", outFd, errFd ?? "ignore"];
5921
6153
  }
5922
- const child = spawn(cfg.claudeBin ?? resolveClaudeBin(), claudeArgs(cfgPath, prompt, continueSession, model, pluginDirs, settings), {
6154
+ const child = spawn2(cfg.claudeBin ?? resolveClaudeBin(), claudeArgs(cfgPath, prompt, continueSession, model, pluginDirs, settings), {
5923
6155
  cwd: worktree,
5924
6156
  detached: true,
5925
6157
  stdio,
@@ -5929,8 +6161,8 @@ function makeLauncher(cfg) {
5929
6161
  // Остальной env демона наследуется, как раньше.
5930
6162
  env: droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null)
5931
6163
  });
5932
- if (outFd !== void 0) closeSync2(outFd);
5933
- if (errFd !== void 0) closeSync2(errFd);
6164
+ if (outFd !== void 0) closeSync3(outFd);
6165
+ if (errFd !== void 0) closeSync3(errFd);
5934
6166
  child.unref();
5935
6167
  if (child.pid == null) throw new Error("\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430 (\u043D\u0435\u0442 pid)");
5936
6168
  return child.pid;
@@ -5938,13 +6170,13 @@ function makeLauncher(cfg) {
5938
6170
  }
5939
6171
 
5940
6172
  // src/git.ts
5941
- import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
6173
+ import { existsSync as existsSync7, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
5942
6174
  import { appendFile, mkdir, readFile, rm } from "fs/promises";
5943
6175
  import { isAbsolute, join as join8 } from "path";
5944
6176
  import { $ as $4 } from "execa";
5945
6177
  function isLinkedWorktree(dir) {
5946
6178
  try {
5947
- return statSync5(join8(dir, ".git")).isFile();
6179
+ return statSync6(join8(dir, ".git")).isFile();
5948
6180
  } catch {
5949
6181
  return false;
5950
6182
  }
@@ -5984,20 +6216,20 @@ async function removeWorktree(repo, path) {
5984
6216
  }
5985
6217
 
5986
6218
  // src/agentToken.ts
5987
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
6219
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync2 } from "fs";
5988
6220
  import { join as join9 } from "path";
5989
6221
  var STORE_PATH2 = join9(SWARM_HOME, "agent.json");
5990
6222
  var base2 = (api) => api.replace(/\/$/, "");
5991
6223
  function loadAgentToken() {
5992
6224
  try {
5993
- const s = JSON.parse(readFileSync7(STORE_PATH2, "utf8"));
6225
+ const s = JSON.parse(readFileSync8(STORE_PATH2, "utf8"));
5994
6226
  return s.token ? s : null;
5995
6227
  } catch {
5996
6228
  return null;
5997
6229
  }
5998
6230
  }
5999
6231
  function saveAgentToken(s) {
6000
- writeFileSync7(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
6232
+ writeFileSync8(STORE_PATH2, JSON.stringify(s, null, 2), { mode: 384 });
6001
6233
  }
6002
6234
  function clearAgentToken() {
6003
6235
  try {
@@ -6049,19 +6281,19 @@ import { basename as basename3, isAbsolute as isAbsolute2, join as join11 } from
6049
6281
  var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
6050
6282
 
6051
6283
  // src/workspace.ts
6052
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
6284
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3 } from "fs";
6053
6285
  import { join as join12 } from "path";
6054
6286
  var WORKSPACE_PATH = join12(SWARM_HOME, "workspace.json");
6055
6287
  function loadWorkspace() {
6056
6288
  try {
6057
- const w = JSON.parse(readFileSync8(WORKSPACE_PATH, "utf8"));
6289
+ const w = JSON.parse(readFileSync9(WORKSPACE_PATH, "utf8"));
6058
6290
  return w && typeof w.id === "string" && w.id ? w : null;
6059
6291
  } catch {
6060
6292
  return null;
6061
6293
  }
6062
6294
  }
6063
6295
  function saveWorkspace(w) {
6064
- writeFileSync8(WORKSPACE_PATH, JSON.stringify(w, null, 2), { mode: 384 });
6296
+ writeFileSync9(WORKSPACE_PATH, JSON.stringify(w, null, 2), { mode: 384 });
6065
6297
  }
6066
6298
  function clearWorkspace() {
6067
6299
  try {
@@ -6516,68 +6748,106 @@ function daemonCancel(slug, reason) {
6516
6748
  function daemonWake(runId) {
6517
6749
  return post("/wake", { runId: String(runId) });
6518
6750
  }
6751
+ function daemonPause(reason) {
6752
+ return post("/pause", { source: "cli", ...reason ? { reason } : {} });
6753
+ }
6754
+ function daemonResume() {
6755
+ return post("/resume", { source: "cli" });
6756
+ }
6757
+ async function daemonStop(opts) {
6758
+ const res = await post("/shutdown", { force: opts.force ? "1" : "0", wait: opts.wait ? "1" : "0" });
6759
+ if (!res) return null;
6760
+ const raw = res;
6761
+ if (raw.stopping === void 0 && raw.waiting === void 0) return null;
6762
+ return { stopping: !!raw.stopping, waiting: !!raw.waiting, sessions: Array.isArray(raw.sessions) ? raw.sessions : [] };
6763
+ }
6764
+ async function daemonStatus2() {
6765
+ try {
6766
+ const res = await fetch(`${BASE}/status`, { headers: headers(), signal: AbortSignal.timeout(TIMEOUT_MS) });
6767
+ if (!res.ok) return null;
6768
+ const body = await res.json();
6769
+ return typeof body?.paused === "boolean" ? body : null;
6770
+ } catch {
6771
+ return null;
6772
+ }
6773
+ }
6519
6774
  var NO_DAEMON_NOTE = `\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT}) \u2014 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E \u041B\u041E\u041A\u0410\u041B\u042C\u041D\u042B\u0419 \u0441\u0442\u043E\u0440 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u044B, \u043E\u043D \u043C\u043E\u0436\u0435\u0442 \u0440\u0430\u0441\u0445\u043E\u0434\u0438\u0442\u044C\u0441\u044F \u0441 \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u043E\u043C. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0434\u0435\u043C\u043E\u043D\u0430: swarm start`;
6520
6775
 
6521
- // package.json
6522
- var package_default = {
6523
- name: "@taskless-app/swarm",
6524
- version: "0.4.1",
6525
- type: "module",
6526
- description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6527
- keywords: [
6528
- "ai-agents",
6529
- "coding-agents",
6530
- "swarm",
6531
- "orchestration",
6532
- "autonomous-agents",
6533
- "mcp",
6534
- "taskless",
6535
- "cli"
6536
- ],
6537
- license: "SEE LICENSE IN LICENSE",
6538
- author: "Ilya Smurov <ilya@smurov.com>",
6539
- homepage: "https://github.com/ilyasmurov/taskless/tree/main/packages/swarm-daemon#readme",
6540
- repository: {
6541
- type: "git",
6542
- url: "git+https://github.com/ilyasmurov/taskless.git",
6543
- directory: "packages/swarm-daemon"
6544
- },
6545
- engines: {
6546
- node: ">=22"
6547
- },
6548
- bin: {
6549
- swarm: "dist/cli.js"
6550
- },
6551
- files: [
6552
- "dist",
6553
- "docs/HERO.md",
6554
- "roles"
6555
- ],
6556
- scripts: {
6557
- overmind: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/overmind.ts',
6558
- swarm: 'NODE_OPTIONS="${NODE_OPTIONS:-} --conditions=development" tsx src/cli.ts',
6559
- build: "tsup",
6560
- start: "node dist/overmind.js",
6561
- prepublishOnly: "pnpm run build",
6562
- test: "vitest run && SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
6563
- "test:client": "SWARM_TEST_BACKEND=client vitest run src/overmind.test.ts src/overmind.storm.test.ts src/db.storm.test.ts",
6564
- "test:watch": "vitest",
6565
- typecheck: "tsc -p tsconfig.json --noEmit",
6566
- lint: "echo 'no lint in swarm-daemon yet'"
6567
- },
6568
- dependencies: {
6569
- "@hono/node-server": "^2.0.12",
6570
- "@modelcontextprotocol/sdk": "^1.29.0",
6571
- execa: "^10.0.0",
6572
- zod: "3.25.76"
6573
- },
6574
- optionalDependencies: {
6575
- "better-sqlite3": "^12.11.1"
6776
+ // src/pauseState.ts
6777
+ import { existsSync as existsSync8, readFileSync as readFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync10 } from "fs";
6778
+ import { join as join13 } from "path";
6779
+ function pauseStatePath(home) {
6780
+ return join13(home, "pause.json");
6781
+ }
6782
+ var ACTIVE = { paused: false, at: "", source: "cli" };
6783
+ function loadPauseState(home, io = {}) {
6784
+ const exists = io.exists ?? existsSync8;
6785
+ const readFile2 = io.readFile ?? ((p) => readFileSync10(p, "utf8"));
6786
+ const path = pauseStatePath(home);
6787
+ if (!exists(path)) return ACTIVE;
6788
+ try {
6789
+ const parsed = JSON.parse(readFile2(path));
6790
+ if (parsed.paused !== true) return ACTIVE;
6791
+ return {
6792
+ paused: true,
6793
+ at: typeof parsed.at === "string" ? parsed.at : "",
6794
+ source: parsed.source === "web" || parsed.source === "remote" ? parsed.source : "cli",
6795
+ ...typeof parsed.reason === "string" && parsed.reason.trim() ? { reason: parsed.reason.trim() } : {}
6796
+ };
6797
+ } catch {
6798
+ return ACTIVE;
6576
6799
  }
6577
- };
6800
+ }
6801
+ function savePauseState(home, state, io = {}) {
6802
+ const path = pauseStatePath(home);
6803
+ if (!state.paused) {
6804
+ const remove = io.remove ?? ((p) => existsSync8(p) && unlinkSync4(p));
6805
+ remove(path);
6806
+ return;
6807
+ }
6808
+ const writeFile2 = io.writeFile ?? ((p, data) => {
6809
+ writeFileSync10(`${p}.tmp`, data);
6810
+ renameSync4(`${p}.tmp`, p);
6811
+ });
6812
+ writeFile2(path, JSON.stringify(state, null, 2));
6813
+ }
6814
+ function makePauseState(source, reason, now = Date.now()) {
6815
+ return {
6816
+ paused: true,
6817
+ at: new Date(now).toISOString(),
6818
+ source,
6819
+ ...reason && reason.trim() ? { reason: reason.trim() } : {}
6820
+ };
6821
+ }
6822
+ function pauseText(state, now = Date.now()) {
6823
+ if (!state?.paused) return "\u0440\u043E\u0439 \u0430\u043A\u0442\u0438\u0432\u0435\u043D \u2014 \u0431\u0435\u0440\u0451\u0442 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443";
6824
+ const since = state.at ? sinceText(state.at, now) : "";
6825
+ const who = { cli: "\u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u043D\u0430 \u043C\u0430\u0448\u0438\u043D\u0435", web: "\u0441 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0439 \u043C\u043E\u0440\u0434\u044B", remote: "\u0438\u0437 Taskless" }[state.source];
6826
+ const why = state.reason ? ` \u2014 ${state.reason}` : "";
6827
+ return `\u0440\u043E\u0439 \u041D\u0410 \u041F\u0410\u0423\u0417\u0415${since} (${who})${why}`;
6828
+ }
6829
+ function sinceText(at, now) {
6830
+ const ms = Date.parse(at);
6831
+ if (Number.isNaN(ms)) return "";
6832
+ const min = Math.max(0, Math.round((now - ms) / 6e4));
6833
+ if (min < 1) return " (\u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E)";
6834
+ if (min < 60) return ` (${min} \u043C\u0438\u043D)`;
6835
+ return ` (${Math.round(min / 60)} \u0447)`;
6836
+ }
6837
+
6838
+ // src/heartbeat.ts
6839
+ import { readFileSync as readFileSync11, renameSync as renameSync5, writeFileSync as writeFileSync11 } from "fs";
6840
+ function readHeartbeat(path) {
6841
+ try {
6842
+ const ms = Number(readFileSync11(path, "utf8").trim());
6843
+ return Number.isFinite(ms) ? ms : null;
6844
+ } catch {
6845
+ return null;
6846
+ }
6847
+ }
6578
6848
 
6579
6849
  // src/cli.ts
6580
- var PACKAGE_VERSION = package_default.version;
6850
+ var PACKAGE_VERSION2 = package_default.version;
6581
6851
  function takeRepo(argv) {
6582
6852
  const i = argv.indexOf("--repo");
6583
6853
  if (i === -1) return { repo: null, rest: argv };
@@ -6733,6 +7003,215 @@ async function runLogin(db2, opts) {
6733
7003
  web.server.close();
6734
7004
  }
6735
7005
  }
7006
+ async function liveSessionNames(ids) {
7007
+ if (ids.length === 0) return "";
7008
+ const snap = await daemonRuns();
7009
+ const byId = new Map((snap?.runs ?? []).map((r) => [r.id, r]));
7010
+ return ids.map((id) => {
7011
+ const r = byId.get(id);
7012
+ return r ? `${r.name} (${r.task_slug})` : `#${id}`;
7013
+ }).join(", ");
7014
+ }
7015
+ async function waitDaemonDown(opts) {
7016
+ const pollMs = opts.pollMs ?? 3e3;
7017
+ const until = Date.now() + opts.timeoutMs;
7018
+ let lastLive = -1;
7019
+ for (; ; ) {
7020
+ const st = await daemonStatus2();
7021
+ if (!st) return true;
7022
+ if (st.live.length !== lastLive) {
7023
+ lastLive = st.live.length;
7024
+ console.log(lastLive ? `\u0436\u0434\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439: ${lastLive}\u2026` : "\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C \u2014 \u0433\u0430\u0448\u0443\u2026");
7025
+ }
7026
+ if (Date.now() >= until) return false;
7027
+ await sleep2(pollMs);
7028
+ }
7029
+ }
7030
+ var MAX_DAEMON_LOG_BYTES = 16 * 1024 * 1024;
7031
+ var START_READY_TIMEOUT_MS = 45e3;
7032
+ async function runStartDetached(args) {
7033
+ const already = await daemonStatus2();
7034
+ if (already) {
7035
+ console.log(`\u0434\u0435\u043C\u043E\u043D \u0443\u0436\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 (v${already.version})${already.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${already.hostName}\xBB` : ""} \u2014 \u0432\u0442\u043E\u0440\u043E\u0433\u043E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E.`);
7036
+ console.log("\u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435: swarm status \xB7 \u043B\u043E\u0433: swarm logs -f \xB7 \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C: swarm stop");
7037
+ return 0;
7038
+ }
7039
+ const rotated = rotateDaemonLog(DAEMON_LOG_PATH, MAX_DAEMON_LOG_BYTES);
7040
+ const entry = process.argv[1];
7041
+ if (!entry) {
7042
+ console.error("\u043D\u0435\u0447\u0435\u043C \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0441\u0435\u0431\u044F \u0444\u043E\u043D\u043E\u043C (\u043F\u0443\u0441\u0442\u043E\u0439 argv[1]) \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435: swarm start --foreground");
7043
+ return 1;
7044
+ }
7045
+ let launch;
7046
+ try {
7047
+ launch = launchDetached({
7048
+ execPath: process.execPath,
7049
+ // execArgv тащим за собой намеренно: в монорепе CLI запускают через tsx-loader, и
7050
+ // без него ребёнок не прочитает .ts-исходник, с которого сам же и стартовал.
7051
+ execArgv: process.execArgv,
7052
+ entry,
7053
+ args: childStartArgv(args),
7054
+ logPath: DAEMON_LOG_PATH,
7055
+ env: process.env
7056
+ });
7057
+ } catch (e) {
7058
+ console.error(`\u043D\u0435 \u0441\u043C\u043E\u0433 \u043E\u0442\u0434\u0435\u043B\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430 \u043E\u0442 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430: ${e instanceof Error ? e.message : e}`);
7059
+ console.error("\u043F\u043E\u0434\u043D\u0438\u043C\u0438 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435: swarm start --foreground");
7060
+ return 1;
7061
+ }
7062
+ console.log(`\u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0440\u043E\u0439 \u0444\u043E\u043D\u043E\u043C (pid ${launch.pid}), \u043B\u043E\u0433: ${DAEMON_LOG_PATH}${rotated === "rotated" ? " (\u043F\u0440\u043E\u0448\u043B\u044B\u0439 \u0443\u0435\u0445\u0430\u043B \u0432 .1)" : ""}`);
7063
+ const verdict = await waitForReady({
7064
+ probe: async () => await daemonStatus2() != null,
7065
+ alive: launch.alive,
7066
+ timeoutMs: START_READY_TIMEOUT_MS,
7067
+ pollMs: 500
7068
+ });
7069
+ if (verdict === "ready") {
7070
+ console.log("\u0440\u043E\u0439 \u043F\u043E\u0434\u043D\u044F\u0442, \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D. \u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435: swarm status \xB7 \u043B\u043E\u0433: swarm logs -f \xB7 \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C: swarm stop");
7071
+ return 0;
7072
+ }
7073
+ console.error(
7074
+ verdict === "died" ? "\u0434\u0435\u043C\u043E\u043D \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F \u2014 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0432\u044B\u0448\u0435\u043B \u0441\u0440\u0430\u0437\u0443. \u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432 \u0445\u0432\u043E\u0441\u0442\u0435 \u043B\u043E\u0433\u0430:" : `\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u043E\u0437\u0432\u0430\u043B\u0441\u044F \u0437\u0430 ${Math.round(START_READY_TIMEOUT_MS / 1e3)}\u0441 (\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0435\u0449\u0451 \u0436\u0438\u0432, pid ${launch.pid}). \u0425\u0432\u043E\u0441\u0442 \u043B\u043E\u0433\u0430:`
7075
+ );
7076
+ console.error(tailFile(DAEMON_LOG_PATH, 20) || `(\u043B\u043E\u0433 \u043F\u0443\u0441\u0442: ${DAEMON_LOG_PATH})`);
7077
+ return 1;
7078
+ }
7079
+ async function runDaemonLogs(argv) {
7080
+ const follow = argv.includes("-f") || argv.includes("--follow");
7081
+ const nAt = argv.findIndex((a) => a === "-n" || a === "--lines");
7082
+ const lines = nAt >= 0 ? Number(argv[nAt + 1]) : 200;
7083
+ if (!Number.isFinite(lines) || lines <= 0) {
7084
+ console.error("\u043D\u0443\u0436\u043D\u043E \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A: swarm logs -n 200");
7085
+ return 1;
7086
+ }
7087
+ const stray = argv.find((a, i) => /^\d+$/.test(a) && i !== nAt + 1);
7088
+ if (stray) {
7089
+ console.error(`\u043B\u043E\u0433 \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u044D\u0442\u043E \xABswarm log ${stray}\xBB (\u0431\u0435\u0437 s). \xABswarm logs\xBB \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u043B\u043E\u0433 \u0434\u0435\u043C\u043E\u043D\u0430; \u0447\u0438\u0441\u043B\u043E \u0441\u0442\u0440\u043E\u043A \u2014 \xABswarm logs -n 200\xBB.`);
7090
+ return 1;
7091
+ }
7092
+ if (!existsSync9(DAEMON_LOG_PATH)) {
7093
+ console.error(`\u043B\u043E\u0433\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435\u0442: ${DAEMON_LOG_PATH}`);
7094
+ console.error("\u043E\u043D \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0444\u043E\u043D\u043E\u0432\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 (swarm start). \u0414\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u044F\u0442 \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 (--foreground) \u2014 \u0432\u044B\u0432\u043E\u0434 \u0438\u0434\u0451\u0442 \u0442\u0443\u0434\u0430.");
7095
+ return 1;
7096
+ }
7097
+ const head = tailFile(DAEMON_LOG_PATH, lines);
7098
+ if (head) console.log(head);
7099
+ if (!follow) return 0;
7100
+ let pos = statSync7(DAEMON_LOG_PATH).size;
7101
+ for (; ; ) {
7102
+ await sleep2(500);
7103
+ let size;
7104
+ try {
7105
+ size = statSync7(DAEMON_LOG_PATH).size;
7106
+ } catch {
7107
+ continue;
7108
+ }
7109
+ if (size < pos) pos = 0;
7110
+ if (size === pos) continue;
7111
+ const fd = openSync4(DAEMON_LOG_PATH, "r");
7112
+ try {
7113
+ const buf = Buffer.alloc(size - pos);
7114
+ const read = readSync2(fd, buf, 0, buf.length, pos);
7115
+ process.stdout.write(buf.subarray(0, read).toString("utf8"));
7116
+ pos += read;
7117
+ } finally {
7118
+ closeSync4(fd);
7119
+ }
7120
+ }
7121
+ }
7122
+ async function runStop(argv) {
7123
+ const force = argv.includes("--force");
7124
+ const wait = argv.includes("--wait");
7125
+ if (force && wait) {
7126
+ console.error("--wait \u0438 --force \u0432\u043C\u0435\u0441\u0442\u0435 \u043D\u0435 \u0438\u043C\u0435\u044E\u0442 \u0441\u043C\u044B\u0441\u043B\u0430: \u043B\u0438\u0431\u043E \u0436\u0434\u0451\u043C \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0434\u0440\u043E\u043D\u043E\u0432, \u043B\u0438\u0431\u043E \u043E\u0431\u0440\u044B\u0432\u0430\u0435\u043C \u0438\u0445 \u0441\u0435\u0439\u0447\u0430\u0441");
7127
+ return 1;
7128
+ }
7129
+ const res = await daemonStop({ force, wait });
7130
+ if (!res) {
7131
+ const beat = readHeartbeat(HEARTBEAT_PATH);
7132
+ const freshBeat = beat != null && Date.now() - beat < 6e4;
7133
+ if (freshBeat) {
7134
+ console.error(
7135
+ `\u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u0430\u0451\u0442 \u043F\u0443\u043B\u044C\u0441 (${HEARTBEAT_PATH}), \u043D\u043E \u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT} \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u2014 \u0441\u0430\u043C \u0441\u0435\u0431\u044F \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C \u043E\u043D \u043D\u0435 \u043C\u043E\u0436\u0435\u0442. \u041F\u043E\u0445\u043E\u0436\u0435, \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u0437\u0430\u0432\u0438\u0441: \u043D\u0430\u0439\u0434\u0438 \u0435\u0433\u043E (ps) \u0438 \u043F\u043E\u0433\u0430\u0441\u0438 \u0441\u0438\u0433\u043D\u0430\u043B\u043E\u043C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.`
7136
+ );
7137
+ return 1;
7138
+ }
7139
+ console.log(`\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT} \u043C\u043E\u043B\u0447\u0438\u0442) \u2014 \u0433\u0430\u0441\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.`);
7140
+ return 0;
7141
+ }
7142
+ if (res.stopping) {
7143
+ if (res.sessions.length) {
7144
+ console.log(`\u0433\u0430\u0448\u0443 \u0434\u0435\u043C\u043E\u043D\u0430 \u041F\u0420\u0418\u041D\u0423\u0414\u0418\u0422\u0415\u041B\u042C\u041D\u041E \u2014 \u043E\u0431\u043E\u0440\u0432\u0430\u043D\u044B \u0436\u0438\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 (${res.sessions.length}): ${await liveSessionNames(res.sessions)}`);
7145
+ console.log("\u0438\u0445 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430; \u043F\u043E\u0434\u043D\u0438\u043C\u0443\u0442\u0441\u044F \u0437\u0430\u043D\u043E\u0432\u043E \u043F\u043E\u0441\u043B\u0435 swarm start (--continue \u043F\u043E \u0441\u0432\u043E\u0435\u043C\u0443 worktree)");
7146
+ } else {
7147
+ console.log("\u0433\u0430\u0448\u0443 \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u0431\u044B\u043B\u043E");
7148
+ }
7149
+ const down = await waitDaemonDown({ timeoutMs: 3e4, pollMs: 500 });
7150
+ console.log(down ? "\u0434\u0435\u043C\u043E\u043D \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D. \u041F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u0431\u0440\u0430\u0442\u043D\u043E: swarm start" : "\u0434\u0435\u043C\u043E\u043D \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u043E\u043C\u0430\u043D\u0434\u0443, \u043D\u043E \u0435\u0449\u0451 \u043D\u0435 \u0432\u044B\u0448\u0435\u043B \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C `swarm status`");
7151
+ return down ? 0 : 1;
7152
+ }
7153
+ if (res.waiting) {
7154
+ console.log(`\u0434\u0435\u043C\u043E\u043D \u0433\u0430\u0441\u043D\u0435\u0442 \u041F\u041E\u0421\u041B\u0415 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (${res.sessions.length}): ${await liveSessionNames(res.sessions)}`);
7155
+ console.log("\u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u043E\u043D \u0443\u0436\u0435 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442. \u041F\u0440\u0435\u0440\u0432\u0430\u0442\u044C \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u2014 \xAB\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435\xBB \u043D\u0430 \u043C\u043E\u0440\u0434\u0435 \u0434\u0435\u043C\u043E\u043D\u0430; \u043E\u0431\u043E\u0440\u0432\u0430\u0442\u044C \u0441\u0435\u0439\u0447\u0430\u0441 \u2014 swarm stop --force");
7156
+ const down = await waitDaemonDown({ timeoutMs: 6 * 60 * 6e4 });
7157
+ console.log(down ? "\u0434\u0435\u043C\u043E\u043D \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D. \u041F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u0431\u0440\u0430\u0442\u043D\u043E: swarm start" : "\u0436\u0434\u0443 \u0431\u043E\u043B\u044C\u0448\u0435 6 \u0447\u0430\u0441\u043E\u0432 \u2014 \u0431\u0440\u043E\u0441\u0430\u044E \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0435, \u0434\u0435\u043C\u043E\u043D \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u0433\u0430\u0441\u043D\u0443\u0442\u044C \u0441\u0430\u043C (swarm status \u043F\u043E\u043A\u0430\u0436\u0435\u0442)");
7158
+ return down ? 0 : 1;
7159
+ }
7160
+ console.error(`\u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 ${res.sessions.length} \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439: ${await liveSessionNames(res.sessions)}`);
7161
+ console.error(
7162
+ "\u043E\u043D\u0438 \u043F\u0438\u0448\u0443\u0442 \u043A\u043E\u0434 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441, \u0438 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435 \u0443\u043C\u0440\u0451\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0438\u043C\u0438 \u2014 \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u043D\u0435 \u0433\u0430\u0448\u0443.\n swarm stop --wait \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F, \u043F\u043E\u043A\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442 (\u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u0440\u043E\u0439 \u0443\u0436\u0435 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442)\n swarm stop --force \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C \u0441\u0435\u0439\u0447\u0430\u0441, \u043E\u0431\u043E\u0440\u0432\u0430\u0432 \u0438\u0445\n swarm pause \u043F\u0440\u043E\u0441\u0442\u043E \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0442\u044C \u0431\u0440\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443, \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0435 \u0433\u0430\u0441\u0438\u0442\u044C"
7163
+ );
7164
+ return 1;
7165
+ }
7166
+ async function runRemove(argv) {
7167
+ const yes = argv.includes("--yes") || argv.includes("-y");
7168
+ if (!yes) {
7169
+ if (!process.stdin.isTTY) {
7170
+ console.error("swarm remove \u043E\u0442\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u043C\u0430\u0448\u0438\u043D\u0443 \u043E\u0442 \u0440\u043E\u044F (\u0433\u0430\u0441\u0438\u0442 \u0434\u0435\u043C\u043E\u043D\u0430, \u0443\u0431\u0438\u0440\u0430\u0435\u0442 \u0435\u0451 \u0438\u0437 \u043F\u0430\u0440\u043A\u0430, \u0441\u0442\u0438\u0440\u0430\u0435\u0442 \u0442\u043E\u043A\u0435\u043D\u044B). \u041D\u0435\u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u2014 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438 \u044F\u0432\u043D\u043E: swarm remove --yes");
7171
+ return 1;
7172
+ }
7173
+ const answer = (await askTty(`\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \xAB${thisHost.hostName}\xBB \u043E\u0442 \u0440\u043E\u044F? \u0414\u0435\u043C\u043E\u043D \u0431\u0443\u0434\u0435\u0442 \u043F\u043E\u0433\u0430\u0448\u0435\u043D, \u043C\u0430\u0448\u0438\u043D\u0430 \u0438\u0441\u0447\u0435\u0437\u043D\u0435\u0442 \u0438\u0437 \u043F\u0430\u0440\u043A\u0430, \u0442\u043E\u043A\u0435\u043D\u044B \u0441\u0442\u0451\u0440\u0442\u044B [y/N]: `)).trim().toLowerCase();
7174
+ if (answer !== "y" && answer !== "yes" && answer !== "\u0434" && answer !== "\u0434\u0430") {
7175
+ console.log("\u043E\u0442\u043C\u0435\u043D\u0435\u043D\u043E \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442\u043E");
7176
+ return 1;
7177
+ }
7178
+ }
7179
+ const stopCode = await runStop(argv.filter((a) => a !== "--yes" && a !== "-y"));
7180
+ if (stopCode !== 0) {
7181
+ console.error("\u043C\u0430\u0448\u0438\u043D\u0430 \u041D\u0415 \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u0430: \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043D\u0443\u0436\u043D\u043E \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430 (\u0441\u043C. \u0432\u044B\u0448\u0435) \u2014 \u0442\u043E\u043A\u0435\u043D\u044B \u0438 \u043F\u0430\u0440\u043A \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442\u044B");
7182
+ return stopCode;
7183
+ }
7184
+ if (SWARM_BACKEND !== "client") {
7185
+ console.log("\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 sqlite-\u0431\u044D\u043A\u0435\u043D\u0434: \u043F\u0430\u0440\u043A\u0430 \u043C\u0430\u0448\u0438\u043D \u043D\u0435\u0442 \u2014 \u0443\u0431\u0438\u0440\u0430\u0442\u044C \u0438\u0437 \u043D\u0435\u0433\u043E \u043D\u0435\u0447\u0435\u0433\u043E, \u0447\u0438\u0449\u0443 \u0442\u043E\u043B\u044C\u043A\u043E \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435");
7186
+ } else {
7187
+ const db2 = await makeCliStore(SWARM_REPO);
7188
+ try {
7189
+ if (db2 instanceof SwarmClient) {
7190
+ const res = await db2.forgetHost();
7191
+ console.log(
7192
+ res.removed ? `\u043C\u0430\u0448\u0438\u043D\u0430 \u0443\u0431\u0440\u0430\u043D\u0430 \u0438\u0437 \u043F\u0430\u0440\u043A\u0430${res.released ? ` \u2014 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0435\u043D\u043E \u0435\u0451 \u043D\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0445 \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432: ${res.released}` : ""}` : "\u043C\u0430\u0448\u0438\u043D\u044B \u0432 \u043F\u0430\u0440\u043A\u0435 \u0438 \u043D\u0435 \u0431\u044B\u043B\u043E (\u043F\u0443\u043B\u044C\u0441 \u043D\u0438 \u0440\u0430\u0437\u0443 \u043D\u0435 \u0434\u043E\u0445\u043E\u0434\u0438\u043B) \u2014 \u0443\u0431\u0438\u0440\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E"
7193
+ );
7194
+ }
7195
+ } catch (e) {
7196
+ console.error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0431\u0440\u0430\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \u0438\u0437 \u043F\u0430\u0440\u043A\u0430: ${e instanceof Error ? e.message : e}`);
7197
+ console.error("\u0442\u043E\u043A\u0435\u043D\u044B \u041D\u0415 \u0441\u0442\u0451\u0440\u0442\u044B \u2014 \u0438\u043D\u0430\u0447\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C \u0431\u044B\u043B\u043E \u0431\u044B \u043D\u0435\u0447\u0435\u043C. \u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438: swarm remove --yes");
7198
+ db2.close();
7199
+ return 1;
7200
+ }
7201
+ db2.close();
7202
+ }
7203
+ clearAgentToken();
7204
+ clearStore();
7205
+ clearWorkspace();
7206
+ try {
7207
+ savePauseState(SWARM_HOME, { paused: false, at: "", source: "cli" });
7208
+ } catch {
7209
+ }
7210
+ console.log("\u043C\u0430\u0448\u0438\u043D\u0430 \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u0430 \u043E\u0442 \u0440\u043E\u044F: \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0433\u0430\u0448\u0435\u043D, \u0442\u043E\u043A\u0435\u043D\u044B (agent.json/oauth.json/workspace.json) \u0441\u0442\u0451\u0440\u0442\u044B.");
7211
+ console.log("\u043D\u0430\u043F\u043E\u043C\u0438\u043D\u0430\u043D\u0438\u0435: \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0435 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435 \u043E\u0442\u0437\u044B\u0432\u0430\u0435\u0442 \u0435\u0433\u043E \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u2014 \u043E\u0442\u0437\u044B\u0432 \u0432 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430, \u041F\u0440\u043E\u0444\u0438\u043B\u044C \u2192 \u0420\u043E\u0439.");
7212
+ console.log("\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \u0432 \u0440\u043E\u0439: swarm login && swarm start");
7213
+ return 0;
7214
+ }
6736
7215
  var STATUS_MARK = {
6737
7216
  queued: "\xB7",
6738
7217
  running: "\u25B6",
@@ -6784,27 +7263,62 @@ function printRuns(runs, view) {
6784
7263
  }).forEach((l) => console.log(l));
6785
7264
  }
6786
7265
  function usage() {
6787
- console.log(`swarm \u2014 \u0440\u043E\u0439 \u0434\u0440\u043E\u043D\u043E\u0432 (v${PACKAGE_VERSION})
7266
+ console.log(`swarm \u2014 \u0440\u043E\u0439 AI-\u0434\u0440\u043E\u043D\u043E\u0432 (v${PACKAGE_VERSION2})
6788
7267
 
6789
- swarm login [--workspace <slug|id>] [--no-open] \u0432\u0445\u043E\u0434 \u0432 \u043F\u0440\u043E\u0434 + \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430
6790
- swarm start [--repo <path>] \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430: \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u0447\u0435\u0440\u0435\u0434\u044C, \u0441\u043F\u0430\u0432\u043D\u0438\u0442/\u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u043E\u0432
6791
- swarm ls [status] \u0441\u043F\u0438\u0441\u043E\u043A \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0416\u0418\u0412\u041E\u0413\u041E \u0414\u0415\u041C\u041E\u041D\u0410 \u2014 \u0440\u043E\u0432\u043D\u043E \u0442\u0435, \u0447\u0442\u043E \u0432 \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435 (\u043E\u043F\u0446. \u0444\u0438\u043B\u044C\u0442\u0440 \u043F\u043E \u0441\u0442\u0430\u0442\u0443\u0441\u0443)
6792
- swarm spawn <slug> [area] [--repo <path>] [--after <s1,s2>] [--priority <n>] [--base <\u0432\u0435\u0442\u043A\u0430>] [--host <\u043C\u0430\u0448\u0438\u043D\u0430|any>] \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u044C (--after \u2192 \u0436\u0434\u0430\u0442\u044C done \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439, --priority \u2192 \u0432\u044B\u0448\u0435 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0440\u0430\u043D\u044C\u0448\u0435, --base \u2192 PR \u0432 \u044D\u0442\u0443 \u0432\u0435\u0442\u043A\u0443, \u0430 \u043D\u0435 \u0432 \u0434\u0435\u0444\u043E\u043B\u0442\u043D\u0443\u044E, --host \u2192 \u043C\u0430\u0448\u0438\u043D\u0430 \u043F\u0440\u043E\u0433\u043E\u043D\u0430: \u041F\u041E \u0423\u041C\u041E\u041B\u0427\u0410\u041D\u0418\u042E \u044D\u0442\u0430, \xABany\xBB \u2192 \u043B\u044E\u0431\u0430\u044F \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u0430\u044F)
6793
- swarm hosts \u043C\u0430\u0448\u0438\u043D\u044B \u0440\u043E\u044F: \u0438\u043C\u044F, \u043F\u0443\u043B\u044C\u0441, \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, id (\u0438\u043C\u044F \u0434\u043B\u044F --host)
6794
- swarm spawn <slug> --hero [--name HYDRA] [--repo <path>] [--after <s1,s2>] [--priority <n>] \u0413\u0435\u0440\u043E\u0439-\u043B\u0435\u0439\u0442\u0435\u043D\u0430\u043D\u0442 \u044D\u043F\u0438\u043A\u0430
6795
- swarm wake <runId> \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (id \u0438\u0437 swarm ls) \u2014 \u0447\u0435\u0440\u0435\u0437 \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430; \u043D\u0435\u0447\u0435\u0433\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u2192 \u0441\u0442\u0430\u0440\u0442 \u0441 \u043D\u0443\u043B\u044F
7268
+ \u041D\u0410\u0427\u0410\u0422\u042C
7269
+ swarm login [--workspace <slug|id>] [--no-open]
7270
+ \u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441
7271
+ swarm start [--repo <path>] [--foreground]
7272
+ \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
7273
+ 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
7274
+
7275
+ \u0414\u0410\u0422\u042C \u0420\u041E\u042E \u0420\u0410\u0411\u041E\u0422\u0423
7276
+ swarm spawn <slug> [area] [\u0444\u043B\u0430\u0433\u0438 \u043D\u0438\u0436\u0435]
7277
+ \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u044C
7278
+ swarm ls [\u0441\u0442\u0430\u0442\u0443\u0441] \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 \u2014 \u0440\u043E\u0432\u043D\u043E \u0442\u0435, \u0447\u0442\u043E \u0432 \u0434\u0430\u0448\u0431\u043E\u0440\u0434\u0435
7279
+ swarm wake <runId> \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u043E\u043D (id \u0438\u0437 swarm ls); \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E \u2192 \u043D\u0430\u0447\u043D\u0451\u0442 \u0441 \u043D\u0443\u043B\u044F
7280
+ swarm cancel <slug> <\u043F\u0440\u0438\u0447\u0438\u043D\u0430\u2026>
7281
+ \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430: \u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\xBB \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439, \u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F
6796
7282
  swarm questions \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0434\u0440\u043E\u043D\u043E\u0432
6797
- swarm answer <qid> <...> \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 (\u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442 \u0434\u0440\u043E\u043D\u0430)
6798
- swarm cancel <slug> <\u043F\u0440\u0438\u0447\u0438\u043D\u0430\u2026> \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430 \u0447\u0435\u0440\u0435\u0437 \u0436\u0438\u0432\u043E\u0433\u043E \u0434\u0435\u043C\u043E\u043D\u0430 (\u0441\u0442\u0430\u0442\u0443\u0441 \xAB\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\xBB + \u043F\u0440\u0438\u0447\u0438\u043D\u0430; \u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F)
6799
- swarm import-db <path> --repo <path> \u0438\u043C\u043F\u043E\u0440\u0442 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u043E\u0439 \u0411\u0414 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E)
6800
- swarm log <runId> \u043B\u043E\u0433 \u043F\u0440\u043E\u0433\u043E\u043D\u0430
6801
- swarm logout \u0437\u0430\u0431\u044B\u0442\u044C \u0442\u043E\u043A\u0435\u043D\u044B \u0438 \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430
7283
+ swarm answer <qid> <\u0442\u0435\u043A\u0441\u0442\u2026>
7284
+ \u043E\u0442\u0432\u0435\u0442\u0438\u0442\u044C \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 \u0438 \u0440\u0430\u0437\u0431\u0443\u0434\u0438\u0442\u044C \u0434\u0440\u043E\u043D\u0430
7285
+
7286
+ \u0423\u041F\u0420\u0410\u0412\u041B\u042F\u0422\u042C \u041C\u0410\u0428\u0418\u041D\u041E\u0419
7287
+ swarm pause [\u043F\u0440\u0438\u0447\u0438\u043D\u0430\u2026] \u043D\u0435 \u0431\u0440\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443; \u0436\u0438\u0432\u044B\u0435 \u0434\u0440\u043E\u043D\u044B \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442. \u041F\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0435\u0441\u0442\u0430\u0440\u0442
7288
+ swarm resume \u0441\u043D\u043E\u0432\u0430 \u0431\u0440\u0430\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443
7289
+ swarm stop [--wait|--force]
7290
+ \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430 \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435
7291
+ swarm remove [--yes] [--wait|--force]
7292
+ \u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043C\u0430\u0448\u0438\u043D\u0443 \u043E\u0442 \u0440\u043E\u044F: \u043F\u043E\u0433\u0430\u0441\u0438\u0442\u044C, \u0443\u0431\u0440\u0430\u0442\u044C \u0438\u0437 \u043F\u0430\u0440\u043A\u0430, \u0441\u0442\u0435\u0440\u0435\u0442\u044C \u0442\u043E\u043A\u0435\u043D\u044B
7293
+ swarm hosts \u043C\u0430\u0448\u0438\u043D\u044B \u0440\u043E\u044F: \u0438\u043C\u044F, \u043F\u0443\u043B\u044C\u0441, \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, id (\u0438\u043C\u044F \u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0434\u043B\u044F --host)
7294
+
7295
+ \u0421\u041C\u041E\u0422\u0420\u0415\u0422\u042C, \u0427\u0422\u041E \u041F\u0420\u041E\u0418\u0421\u0425\u041E\u0414\u0418\u0422
7296
+ swarm logs [-n 200] [-f] \u043B\u043E\u0433 \u0414\u0415\u041C\u041E\u041D\u0410 (~/.taskless-swarm/overmind.log)
7297
+ swarm log <runId> \u043B\u043E\u0433 \u043E\u0434\u043D\u043E\u0433\u043E \u041F\u0420\u041E\u0413\u041E\u041D\u0410 \u2014 \u043D\u0435 \u043F\u0443\u0442\u0430\u0442\u044C \u0441 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0439
7298
+ swarm web \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430 \u0440\u043E\u044F \u043D\u0430 127.0.0.1:${WEB_PORT}
7299
+
7300
+ \u041F\u0420\u041E\u0427\u0415\u0415
6802
7301
  swarm whoami \u043A\u0442\u043E \u0432\u043E\u0448\u0451\u043B \u0438 \u0432 \u043A\u0430\u043A\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435
6803
- swarm web \u0432\u0435\u0431-\u043C\u043E\u0440\u0434\u0430 (\u0432\u0445\u043E\u0434 + \u0441\u0442\u0430\u0442\u0443\u0441) \u043D\u0430 127.0.0.1
7302
+ swarm logout \u0437\u0430\u0431\u044B\u0442\u044C \u0442\u043E\u043A\u0435\u043D\u044B \u0438 \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430
6804
7303
  swarm shot <url> <out-prefix> [--timeout <ms>] [--storage-state <path>]
6805
- headless-\u0441\u043A\u0440\u0438\u043D desktop+mobile \u041E\u0414\u041D\u041E\u0419 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 (TASWM-40):
7304
+ headless-\u0441\u043A\u0440\u0438\u043D\u044B desktop+mobile \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439:
6806
7305
  <out-prefix>-desktop.png, <out-prefix>-mobile.png
7306
+ swarm import-db <path> --repo <path>
7307
+ \u0438\u043C\u043F\u043E\u0440\u0442 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u0438\u0437 \u0434\u0440\u0443\u0433\u043E\u0439 \u0411\u0414 (\u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E)
6807
7308
  swarm --version \u0432\u0435\u0440\u0441\u0438\u044F \u043F\u0430\u043A\u0435\u0442\u0430
7309
+
7310
+ \u0424\u041B\u0410\u0413\u0418, \u041A\u041E\u0422\u041E\u0420\u042B\u0415 \u0421\u0422\u041E\u0418\u0422 \u0417\u041D\u0410\u0422\u042C
7311
+ start \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0434\u0435\u043C\u043E\u043D \u041E\u0422\u041A\u0420\u0415\u041F\u041B\u042F\u0415\u0422\u0421\u042F: \u0443\u0445\u043E\u0434\u0438\u0442 \u0432 \u0444\u043E\u043D, \u043F\u0438\u0448\u0435\u0442 \u0432 ~/.taskless-swarm/overmind.log,
7312
+ \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B \u0441\u0432\u043E\u0431\u043E\u0434\u0435\u043D. --foreground (\u043E\u043D \u0436\u0435 -f, --attach) \u0434\u0435\u0440\u0436\u0438\u0442 \u0435\u0433\u043E \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435 \u2014
7313
+ \u0434\u043B\u044F \u043E\u0442\u043B\u0430\u0434\u043A\u0438 \u0438 \u0442\u0430\u043C, \u0433\u0434\u0435 \u0444\u043E\u043D\u043E\u043C \u0437\u0430\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0441\u0443\u043F\u0435\u0440\u0432\u0438\u0437\u043E\u0440 (systemd, docker)
7314
+ spawn --repo <path> \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043F\u0440\u043E\u0433\u043E\u043D\u0430, \u0435\u0441\u043B\u0438 \u043D\u0435 \u0434\u0435\u0444\u043E\u043B\u0442\u043D\u044B\u0439
7315
+ --after <s1,s2> \u0436\u0434\u0430\u0442\u044C, \u043F\u043E\u043A\u0430 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u0435\u043D\u043D\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0435 \u0431\u0443\u0434\u0443\u0442 done
7316
+ --priority <n> \u0432\u044B\u0448\u0435 \u0447\u0438\u0441\u043B\u043E \u2014 \u0440\u0430\u043D\u044C\u0448\u0435 \u0432\u043E\u0437\u044C\u043C\u0443\u0442 \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438
7317
+ --base <\u0432\u0435\u0442\u043A\u0430> PR \u043D\u0435 \u0432 \u0434\u0435\u0444\u043E\u043B\u0442\u043D\u0443\u044E \u0432\u0435\u0442\u043A\u0443, \u0430 \u0432 \u044D\u0442\u0443
7318
+ --host <\u043C\u0430\u0448\u0438\u043D\u0430|any> \u043D\u0430 \u043A\u0430\u043A\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C; \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u044D\u0442\u0430, \xABany\xBB \u2014 \u043B\u044E\u0431\u0430\u044F \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u0430\u044F
7319
+ --hero [--name HYDRA] \u0413\u0435\u0440\u043E\u0439-\u043B\u0435\u0439\u0442\u0435\u043D\u0430\u043D\u0442 \u044D\u043F\u0438\u043A\u0430 \u0432\u043C\u0435\u0441\u0442\u043E \u043E\u0431\u044B\u0447\u043D\u043E\u0433\u043E \u0434\u0440\u043E\u043D\u0430
7320
+ stop \u0431\u0435\u0437 \u0444\u043B\u0430\u0433\u043E\u0432 \u043F\u0440\u0438 \u0436\u0438\u0432\u044B\u0445 \u0434\u0440\u043E\u043D\u0430\u0445 \u041D\u0415 \u0433\u0430\u0441\u0438\u0442, \u0430 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u043A\u0442\u043E \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442: \u0438\u0445 \u043D\u0435\u0437\u0430\u043F\u0443\u0448\u0435\u043D\u043D\u043E\u0435
7321
+ \u0443\u043C\u0440\u0451\u0442 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0438\u043C\u0438. --wait \u2014 \u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F, \u043F\u043E\u043A\u0430 \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442; --force \u2014 \u043E\u0431\u043E\u0440\u0432\u0430\u0442\u044C \u0441\u0435\u0439\u0447\u0430\u0441
6808
7322
  `);
6809
7323
  }
6810
7324
  var effectiveWorktreesDirMemo;
@@ -6814,7 +7328,7 @@ function effectiveWorktreesDir() {
6814
7328
  legacyDir: WORKTREES_LEGACY_DIR,
6815
7329
  targetDir: WORKTREES_DIR,
6816
7330
  envOverride: process.env.SWARM_WORKTREES || "",
6817
- exists: (d) => existsSync8(d),
7331
+ exists: (d) => existsSync9(d),
6818
7332
  isNonEmptyDir: (d) => {
6819
7333
  try {
6820
7334
  return readdirSync6(d).length > 0;
@@ -6847,7 +7361,7 @@ async function finishClient(db2) {
6847
7361
  }
6848
7362
  var [cmd, ...rawArgs] = process.argv.slice(2);
6849
7363
  if (cmd === "--version" || cmd === "-v") {
6850
- console.log(PACKAGE_VERSION);
7364
+ console.log(PACKAGE_VERSION2);
6851
7365
  process.exit(0);
6852
7366
  }
6853
7367
  if (cmd === "logout") {
@@ -6894,14 +7408,18 @@ if (cmd === "import-db") {
6894
7408
  process.exit(0);
6895
7409
  }
6896
7410
  if (cmd === "start") {
7411
+ const { foreground, rest: startArgs } = parseStartMode(rawArgs);
6897
7412
  if (SWARM_BACKEND !== "client") {
6898
7413
  console.warn("SWARM_BACKEND=client \u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u043C sqlite-\u0440\u0435\u0436\u0438\u043C\u0435, \u0411\u0415\u0417 \u0441\u0432\u044F\u0437\u0438 \u0441 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u043E\u043C Taskless (\u043D\u0435 \u0443\u0432\u0438\u0434\u0438\u0442 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0435 \u043F\u043E\u0448\u043B\u0451\u0442 \u043F\u0443\u043B\u044C\u0441).");
6899
7414
  }
6900
- const { repo: repoFlag } = takeRepo(rawArgs);
7415
+ const { repo: repoFlag } = takeRepo(startArgs);
6901
7416
  const repo = repoFlag ?? SWARM_REPO;
6902
7417
  if (!repo) {
6903
7418
  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).");
6904
7419
  }
7420
+ if (!foreground) {
7421
+ process.exit(await runStartDetached(startArgs));
7422
+ }
6905
7423
  try {
6906
7424
  await runDaemon({ repo });
6907
7425
  } catch (e) {
@@ -6910,6 +7428,9 @@ if (cmd === "start") {
6910
7428
  }
6911
7429
  process.exit(0);
6912
7430
  }
7431
+ if (cmd === "logs") {
7432
+ process.exit(await runDaemonLogs(rawArgs));
7433
+ }
6913
7434
  if (cmd === "ls") {
6914
7435
  const snap = await daemonRuns(rawArgs[0]);
6915
7436
  if (snap) {
@@ -6946,6 +7467,54 @@ if (cmd === "cancel") {
6946
7467
  }
6947
7468
  console.warn(NO_DAEMON_NOTE);
6948
7469
  }
7470
+ if (cmd === "status") {
7471
+ const st = await daemonStatus2();
7472
+ if (!st) {
7473
+ const local = loadPauseState(SWARM_HOME);
7474
+ console.log(`\u0434\u0435\u043C\u043E\u043D \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422 (\u043C\u043E\u0440\u0434\u0430 127.0.0.1:${WEB_PORT} \u043C\u043E\u043B\u0447\u0438\u0442). \u041F\u043E\u0434\u043D\u044F\u0442\u044C: swarm start`);
7475
+ console.log(local.paused ? `${pauseText(local)} \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435, \u0441\u043D\u044F\u0442\u044C: swarm resume` : "\u043F\u0430\u0443\u0437\u044B \u043D\u0435\u0442 \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u0440\u0430\u0431\u043E\u0442\u0443");
7476
+ process.exit(1);
7477
+ }
7478
+ console.log(`\u0434\u0435\u043C\u043E\u043D \u0436\u0438\u0432 (v${st.version})${st.hostName ? `, \u043C\u0430\u0448\u0438\u043D\u0430 \xAB${st.hostName}\xBB` : ""}, \u0431\u044D\u043A\u0435\u043D\u0434 ${st.backend}`);
7479
+ console.log(pauseText(st.pause ?? (st.paused ? { paused: true, at: "", source: "web" } : null)));
7480
+ if (st.restart?.phase === "waiting") {
7481
+ console.log(
7482
+ `\u0434\u0435\u043C\u043E\u043D \u0416\u0414\u0401\u0422 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 (${st.restart.sessions.length}), \u0447\u0442\u043E\u0431\u044B ${st.restart.intent === "stop" ? "\u043F\u043E\u0433\u0430\u0441\u043D\u0443\u0442\u044C" : "\u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C\u0441\u044F"} \u2014 \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u043D\u0430 \u044D\u0442\u043E \u0432\u0440\u0435\u043C\u044F`
7483
+ );
7484
+ }
7485
+ const counts = Object.entries(st.counts).filter(([, n]) => n > 0);
7486
+ console.log(counts.length ? `\u043F\u0440\u043E\u0433\u043E\u043D\u044B: ${counts.map(([s, n]) => `${s} ${n}`).join(", ")}` : "\u043F\u0440\u043E\u0433\u043E\u043D\u043E\u0432 \u043D\u0435\u0442");
7487
+ console.log(st.live.length ? `\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u0441\u0435\u0439\u0447\u0430\u0441: ${st.live.length} (#${st.live.join(", #")})` : "\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435\u0442");
7488
+ process.exit(0);
7489
+ }
7490
+ if (cmd === "pause" || cmd === "resume") {
7491
+ const wantPause = cmd === "pause";
7492
+ const reason = wantPause ? rawArgs.join(" ").trim() || null : null;
7493
+ const verdict = wantPause ? await daemonPause(reason) : await daemonResume();
7494
+ if (verdict) {
7495
+ console.log(verdict.ok ? verdict.why : verdict.why);
7496
+ if (verdict.ok && wantPause) console.log("\u043F\u0430\u0443\u0437\u0430 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u0430 \u043D\u0430 \u0434\u0438\u0441\u043A \u2014 \u043F\u0435\u0440\u0435\u0436\u0438\u0432\u0451\u0442 \u0440\u0435\u0441\u0442\u0430\u0440\u0442 \u0434\u0435\u043C\u043E\u043D\u0430 \u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443 \u043C\u0430\u0448\u0438\u043D\u044B");
7497
+ process.exit(verdict.ok ? 0 : 1);
7498
+ }
7499
+ try {
7500
+ savePauseState(SWARM_HOME, wantPause ? makePauseState("cli", reason) : { paused: false, at: "", source: "cli" });
7501
+ } catch (e) {
7502
+ console.error(`\u043D\u0435 \u0441\u043C\u043E\u0433 \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043F\u0430\u0443\u0437\u044B: ${e instanceof Error ? e.message : e}`);
7503
+ process.exit(1);
7504
+ }
7505
+ console.log(
7506
+ wantPause ? `\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u2014 \u0437\u0430\u043F\u0438\u0441\u0430\u043B \u043F\u0430\u0443\u0437\u0443 \u043D\u0430 \u0434\u0438\u0441\u043A: \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F (swarm start) \u0423\u0416\u0415 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435. \u0421\u043D\u044F\u0442\u044C: swarm resume` : "\u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u2014 \u0441\u043D\u044F\u043B \u043F\u0430\u0443\u0437\u0443 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435: \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 swarm start \u043F\u043E\u0434\u043D\u0438\u043C\u0435\u0442\u0441\u044F \u0432 \u0440\u0430\u0431\u043E\u0442\u0443"
7507
+ );
7508
+ process.exit(0);
7509
+ }
7510
+ if (cmd === "stop") {
7511
+ const code = await runStop(rawArgs);
7512
+ process.exit(code);
7513
+ }
7514
+ if (cmd === "remove") {
7515
+ const code = await runRemove(rawArgs);
7516
+ process.exit(code);
7517
+ }
6949
7518
  if (cmd === "shot") {
6950
7519
  const code = await runShotCli(rawArgs);
6951
7520
  process.exit(code);