@taskless-app/swarm 0.6.0 → 0.6.2

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
@@ -152,6 +152,27 @@ swarm remove --yes # отключить машину от роя цел
152
152
  машин (её незавершённые прогоны освобождаются и уедут на другие машины) и стирает
153
153
  токены. Вернуть — `swarm login && swarm start`.
154
154
 
155
+ ## Перелогин — одна команда
156
+
157
+ Токен машины отзывается сам собой: вошли на второй машине, нажали «сбросить токен
158
+ роя», выключили рой тумблером. Демон при этом жив и тикает, но работу не берёт, а в
159
+ логе повторяет одну строку — «токен роя отозван … Почини одной командой: swarm login».
160
+
161
+ ```bash
162
+ swarm login # и всё: без stop, без start, без чтения логов
163
+ ```
164
+
165
+ Команда сама разруливает демона: увидела поднятого — **гасит его аккуратно** (как
166
+ `swarm stop --wait`: живые дроны доигрывают, новую работу рой в это время не берёт),
167
+ поднимает страницу входа и **поднимает демона обратно** — уже с новым токеном.
168
+ Перезапуск обязателен: токен читается один раз при старте процесса, иначе демон
169
+ продолжил бы стучаться старым.
170
+
171
+ Ждать нечего — `swarm stop --force` в другом терминале оборвёт дронов и вход пойдёт
172
+ сразу. Демон был погашен до команды — обратно она его не поднимает: было выключено,
173
+ выключенным и останется. Вход не удался или вкладку закрыли — демон возвращается в то
174
+ состояние, в котором был.
175
+
155
176
  ## Кем исполнять дрона
156
177
 
157
178
  Рою всё равно, какой агент внутри: всё, что знает про конкретный харнесс (как
package/dist/cli.js CHANGED
@@ -613,15 +613,19 @@ var init_hostPool = __esm({
613
613
  });
614
614
 
615
615
  // ../swarm-core/src/hostCommands.ts
616
- var HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, RUN_SCOPED_COMMAND_KINDS, RUN_SCOPED;
616
+ var SWARM_PACKAGE_NAME, MANUAL_UPDATE_COMMAND, HOST_COMMAND_STATUSES, TERMINAL, HOST_COMMAND_OPEN_STATUSES, HOST_COMMAND_TTL_MS, HOST_COMMAND_KEEPALIVE_MS, HOST_COMMAND_ACK_GRACE_MS, HOST_COMMAND_MUTE_REASON, RUN_SCOPED_COMMAND_KINDS, RUN_SCOPED;
617
617
  var init_hostCommands = __esm({
618
618
  "../swarm-core/src/hostCommands.ts"() {
619
619
  "use strict";
620
+ SWARM_PACKAGE_NAME = "@taskless-app/swarm";
621
+ MANUAL_UPDATE_COMMAND = `npm i -g ${SWARM_PACKAGE_NAME}@latest`;
620
622
  HOST_COMMAND_STATUSES = ["pending", "delivered", "running", "done", "failed", "rejected", "expired"];
621
623
  TERMINAL = /* @__PURE__ */ new Set(["done", "failed", "rejected", "expired"]);
622
624
  HOST_COMMAND_OPEN_STATUSES = HOST_COMMAND_STATUSES.filter((s) => !TERMINAL.has(s));
623
625
  HOST_COMMAND_TTL_MS = 30 * 6e4;
624
626
  HOST_COMMAND_KEEPALIVE_MS = 10 * 6e4;
627
+ HOST_COMMAND_ACK_GRACE_MS = 5 * 6e4;
628
+ HOST_COMMAND_MUTE_REASON = `\u043C\u0430\u0448\u0438\u043D\u0430 \u043D\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043B\u0430 \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u2014 \u0432\u0435\u0440\u043E\u044F\u0442\u043D\u043E, \u0434\u0435\u043C\u043E\u043D \u0441\u0442\u0430\u0440\u0435\u0435 \u043A\u043E\u043C\u0430\u043D\u0434\u044B. \u041E\u0431\u043D\u043E\u0432\u0438 \u0432\u0440\u0443\u0447\u043D\u0443\u044E: ${MANUAL_UPDATE_COMMAND}`;
625
629
  RUN_SCOPED_COMMAND_KINDS = ["stop_run", "wake_run"];
626
630
  RUN_SCOPED = new Set(RUN_SCOPED_COMMAND_KINDS);
627
631
  }
@@ -1833,7 +1837,7 @@ var init_package = __esm({
1833
1837
  "package.json"() {
1834
1838
  package_default = {
1835
1839
  name: "@taskless-app/swarm",
1836
- version: "0.6.0",
1840
+ version: "0.6.2",
1837
1841
  type: "module",
1838
1842
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
1839
1843
  keywords: [
@@ -1882,7 +1886,8 @@ var init_package = __esm({
1882
1886
  "@modelcontextprotocol/sdk": "^1.29.0",
1883
1887
  execa: "^10.0.0",
1884
1888
  zod: "3.25.76"
1885
- }
1889
+ },
1890
+ gitHead: "e812f35a996b00111f3f535b6b0a45b379e7afb2"
1886
1891
  };
1887
1892
  }
1888
1893
  });
@@ -1993,6 +1998,14 @@ async function probeAgentToken(args, fetchFn = fetch) {
1993
1998
  return "unreachable";
1994
1999
  }
1995
2000
  }
2001
+ function isRevokedTokenError(e) {
2002
+ if (e && typeof e === "object" && e.code === 401) return true;
2003
+ const text = e instanceof Error ? e.message : typeof e === "string" ? e : "";
2004
+ return /invalid_token|(?:http|status|code)[ =:]*401\b|\(401\)|401 unauthorized/i.test(text);
2005
+ }
2006
+ function revokedTokenLine() {
2007
+ return "[taskless] \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u043E\u0437\u0432\u0430\u043D (401 invalid_token) \u2014 \u0440\u043E\u0439 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443. \u041F\u043E\u0447\u0438\u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439: swarm login";
2008
+ }
1996
2009
  var STORE_PATH2, base2;
1997
2010
  var init_agentToken = __esm({
1998
2011
  "src/agentToken.ts"() {
@@ -3216,8 +3229,9 @@ var SWARM_PACKAGE, LATEST_TIMEOUT_MS;
3216
3229
  var init_release = __esm({
3217
3230
  "src/release.ts"() {
3218
3231
  "use strict";
3232
+ init_src();
3219
3233
  init_claudeBin();
3220
- SWARM_PACKAGE = "@taskless-app/swarm";
3234
+ SWARM_PACKAGE = SWARM_PACKAGE_NAME;
3221
3235
  LATEST_TIMEOUT_MS = 4e3;
3222
3236
  }
3223
3237
  });
@@ -5394,7 +5408,16 @@ var SwarmClient = class _SwarmClient {
5394
5408
  const data = res?.structuredContent ?? {};
5395
5409
  return {
5396
5410
  commands: Array.isArray(data.commands) ? data.commands.filter(isHostCommandShape) : [],
5397
- logRequests: parseLogRequests(data.logRequests)
5411
+ logRequests: parseLogRequests(data.logRequests),
5412
+ // Предел человека (TASWM-363). Различаем ТРИ случая, и разница не косметическая:
5413
+ // • ключа нет вовсе (старый прод, пульс без hostId) → undefined: прежнее число
5414
+ // не трогаем, иначе обновление прода молча снимало бы предел;
5415
+ // • число >0 → предел назван;
5416
+ // • всё прочее (null, ноль, мусор) → null: предела нет. Выдуманный потолок хуже
5417
+ // отсутствующего — под ним рой встанет молча.
5418
+ ...data.maxDrones === void 0 ? {} : {
5419
+ maxDrones: typeof data.maxDrones === "number" && Number.isFinite(data.maxDrones) && data.maxDrones > 0 ? Math.trunc(data.maxDrones) : null
5420
+ }
5398
5421
  };
5399
5422
  } catch {
5400
5423
  return EMPTY_HEARTBEAT_REPLY;
@@ -8908,7 +8931,9 @@ async function connectSwarmMcp(cfg) {
8908
8931
  try {
8909
8932
  await client.connect(transport);
8910
8933
  } catch (e) {
8911
- console.warn(`Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`);
8934
+ console.warn(
8935
+ isRevokedTokenError(e) ? `${revokedTokenLine()} (\u043F\u043E\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u0430 \u043D\u0435\u0442 \u2014 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u043A\u043E\u043F\u044F\u0442\u0441\u044F \u0432 outbox)` : `Swarm MCP: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (${e instanceof Error ? e.message : e}) \u2192 \u0431\u0443\u0444\u0435\u0440 \u0432 outbox.`
8936
+ );
8912
8937
  return null;
8913
8938
  }
8914
8939
  return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
@@ -8984,6 +9009,7 @@ function resilientSwarmMcp(opts = {}) {
8984
9009
 
8985
9010
  // src/login.ts
8986
9011
  import { execa as execa2 } from "execa";
9012
+ import { createServer } from "net";
8987
9013
  async function waitForLogin(d) {
8988
9014
  const deadline = d.now() + d.timeoutMs;
8989
9015
  for (; ; ) {
@@ -8993,6 +9019,53 @@ async function waitForLogin(d) {
8993
9019
  await d.sleep(d.pollMs);
8994
9020
  }
8995
9021
  }
9022
+ async function withDaemonStopped(d, body) {
9023
+ const st = await d.status();
9024
+ if (!st) {
9025
+ if (!await d.portFree()) {
9026
+ d.err(
9027
+ `\u0432\u0445\u043E\u0434 \u041D\u0415 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u0441\u044F: \u043F\u043E\u0440\u0442 ${d.port} \u0437\u0430\u043D\u044F\u0442, \u043D\u043E \u044D\u0442\u043E \u043D\u0435 \u0434\u0435\u043C\u043E\u043D \u0440\u043E\u044F (\u0435\u0433\u043E \u043C\u043E\u0440\u0434\u0430 \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442). \u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0432\u0445\u043E\u0434\u0430 \u043E\u0431\u044F\u0437\u0430\u043D\u0430 \u043F\u043E\u0434\u043D\u044F\u0442\u044C\u0441\u044F \u0438\u043C\u0435\u043D\u043D\u043E \u043D\u0430 \u043D\u0451\u043C \u2014 \u043E\u043D \u0437\u0430\u0448\u0438\u0442 \u0432 redirect_uri. \u041E\u0441\u0432\u043E\u0431\u043E\u0434\u0438 \u043F\u043E\u0440\u0442 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438: swarm login`
9028
+ );
9029
+ return 1;
9030
+ }
9031
+ const code2 = await body();
9032
+ if (code2 === 0) d.out("\u0434\u0435\u043C\u043E\u043D \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442 \u2014 \u0440\u043E\u0439 \u0436\u0434\u0451\u0442 \u0437\u0430\u043F\u0443\u0441\u043A\u0430: swarm start");
9033
+ return code2;
9034
+ }
9035
+ d.out(
9036
+ st.live.length ? `\u0434\u0435\u043C\u043E\u043D \u0440\u043E\u044F \u043F\u043E\u0434\u043D\u044F\u0442, \u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439: ${st.live.length}. \u0413\u0430\u0448\u0443 \u0435\u0433\u043E \u0441\u0430\u043C \u2014 \u0432\u0445\u043E\u0434 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 \u043F\u043E\u0440\u0442\u0443 ${d.port}.` : `\u0434\u0435\u043C\u043E\u043D \u0440\u043E\u044F \u043F\u043E\u0434\u043D\u044F\u0442 \u2014 \u0433\u0430\u0448\u0443 \u0435\u0433\u043E \u0441\u0430\u043C: \u0432\u0445\u043E\u0434 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u043D\u0430 \u0442\u043E\u043C \u0436\u0435 \u043F\u043E\u0440\u0442\u0443 ${d.port}.`
9037
+ );
9038
+ if (await d.stop() !== "stopped") {
9039
+ d.err(`\u0432\u0445\u043E\u0434 \u041D\u0415 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u0441\u044F: \u0434\u0435\u043C\u043E\u043D \u043D\u0435 \u043F\u043E\u0433\u0430\u0448\u0435\u043D (\u0441\u043C. \u0432\u044B\u0448\u0435), \u043F\u043E\u0440\u0442 ${d.port} \u0437\u0430\u043D\u044F\u0442 \u0438\u043C. \u0420\u043E\u0439 \u043E\u0441\u0442\u0430\u043B\u0441\u044F \u043A\u0430\u043A \u0431\u044B\u043B.`);
9040
+ return 1;
9041
+ }
9042
+ if (!await d.portFree()) {
9043
+ d.err(`\u0432\u0445\u043E\u0434 \u041D\u0415 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u0441\u044F: \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0433\u0430\u0448\u0435\u043D, \u043D\u043E \u043F\u043E\u0440\u0442 ${d.port} \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u0437\u0430\u043D\u044F\u0442 \u043A\u0435\u043C-\u0442\u043E \u0435\u0449\u0451. \u041F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0440\u043E\u0439 \u043E\u0431\u0440\u0430\u0442\u043D\u043E.`);
9044
+ await restoreDaemon(d, false);
9045
+ return 1;
9046
+ }
9047
+ let code = 1;
9048
+ try {
9049
+ code = await body();
9050
+ return code;
9051
+ } finally {
9052
+ await restoreDaemon(d, code === 0);
9053
+ }
9054
+ }
9055
+ async function restoreDaemon(d, loginOk) {
9056
+ d.out(
9057
+ loginOk ? "\u0432\u0445\u043E\u0434 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0434\u0435\u043C\u043E\u043D\u0430 \u043E\u0431\u0440\u0430\u0442\u043D\u043E, \u0443\u0436\u0435 \u0441 \u043D\u043E\u0432\u044B\u043C \u0442\u043E\u043A\u0435\u043D\u043E\u043C\u2026" : "\u0432\u0445\u043E\u0434 \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043B\u0441\u044F \u2014 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E \u0434\u0435\u043C\u043E\u043D\u0430 \u0432 \u043F\u0440\u0435\u0436\u043D\u0435\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435\u2026"
9058
+ );
9059
+ if (await d.start()) return;
9060
+ d.err("\u0434\u0435\u043C\u043E\u043D \u043E\u0431\u0440\u0430\u0442\u043D\u043E \u041D\u0415 \u043F\u043E\u0434\u043D\u044F\u043B\u0441\u044F (\u043F\u0440\u0438\u0447\u0438\u043D\u0430 \u0432\u044B\u0448\u0435) \u2014 \u0440\u043E\u0439 \u0441\u0442\u043E\u0438\u0442. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0432\u0440\u0443\u0447\u043D\u0443\u044E: swarm start");
9061
+ }
9062
+ function isPortFree(port, host = "127.0.0.1") {
9063
+ return new Promise((resolve4) => {
9064
+ const probe = createServer();
9065
+ probe.once("error", () => resolve4(false));
9066
+ probe.listen(port, host, () => probe.close(() => resolve4(true)));
9067
+ });
9068
+ }
8996
9069
  async function chooseWorkspace(d) {
8997
9070
  if (d.list.length === 0) {
8998
9071
  d.out("\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u043E\u0432 (list_workspaces \u043F\u0443\u0441\u0442)");
@@ -9264,6 +9337,59 @@ function takeWorkspace(argv) {
9264
9337
  return { workspace, rest: [...argv.slice(0, i), ...argv.slice(i + 2)] };
9265
9338
  }
9266
9339
  async function runLogin(db2, opts) {
9340
+ return withDaemonStopped(
9341
+ {
9342
+ status: async () => {
9343
+ const st = await daemonStatus2();
9344
+ return st ? { live: st.live } : null;
9345
+ },
9346
+ stop: stopDaemonForLogin,
9347
+ portFree: () => isPortFree(WEB_PORT),
9348
+ // Аргументы не передаём намеренно: пути к репозиториям демон помнит с прошлого
9349
+ // запуска (TASWM-335), а `swarm login` их и не знает — он про вход, не про репо.
9350
+ // А вот ОКРУЖЕНИЕ поднятого обратно демона — наше: если `swarm login` зовут из
9351
+ // шелла без SWARM_BACKEND, рой вернётся в sqlite-режиме, то есть слепым к
9352
+ // воркспейсу. В опубликованном пакете дефолт уже client (SWARM_DIST_BUILD) —
9353
+ // предупреждение достаётся монорепе, где это реальная разница.
9354
+ start: async () => {
9355
+ if (SWARM_BACKEND !== "client") {
9356
+ console.warn("SWARM_BACKEND=client \u043D\u0435 \u0437\u0430\u0434\u0430\u043D \u2014 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E \u0434\u0435\u043C\u043E\u043D\u0430 \u043E\u0431\u0440\u0430\u0442\u043D\u043E \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.");
9357
+ }
9358
+ return await runStartDetached([]) === 0;
9359
+ },
9360
+ out: (s) => console.log(s),
9361
+ err: (s) => console.error(s),
9362
+ port: WEB_PORT
9363
+ },
9364
+ () => runLoginFlow(db2, opts)
9365
+ );
9366
+ }
9367
+ async function stopDaemonForLogin() {
9368
+ const res = await daemonStop({ force: false, wait: true });
9369
+ if (!res) {
9370
+ const beat = readHeartbeat(HEARTBEAT_PATH);
9371
+ if (beat != null && Date.now() - beat < 6e4) {
9372
+ console.error(
9373
+ `\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, \u043F\u043E\u0442\u043E\u043C \u043F\u043E\u0432\u0442\u043E\u0440\u0438 swarm login.`
9374
+ );
9375
+ return "failed";
9376
+ }
9377
+ return "stopped";
9378
+ }
9379
+ if (res.waiting) {
9380
+ console.log(`\u0436\u0434\u0443 \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)} \u2014 \u043D\u043E\u0432\u0443\u044E \u0440\u0430\u0431\u043E\u0442\u0443 \u0440\u043E\u0439 \u0443\u0436\u0435 \u043D\u0435 \u0431\u0435\u0440\u0451\u0442`);
9381
+ console.log("\u043E\u0431\u043E\u0440\u0432\u0430\u0442\u044C \u0438\u0445 \u0438 \u0432\u043E\u0439\u0442\u0438 \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441: swarm stop --force \u0432 \u0434\u0440\u0443\u0433\u043E\u043C \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435");
9382
+ } else {
9383
+ console.log("\u0436\u0438\u0432\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0435 \u0431\u044B\u043B\u043E \u2014 \u0434\u0435\u043C\u043E\u043D \u043F\u043E\u0433\u0430\u0448\u0435\u043D");
9384
+ }
9385
+ const down = res.waiting ? await waitDaemonDown({ timeoutMs: 6 * 60 * 6e4 }) : await waitDaemonDown({ timeoutMs: 3e4, pollMs: 500 });
9386
+ if (down) return "stopped";
9387
+ console.error(
9388
+ res.waiting ? "\u0436\u0434\u0443 \u0431\u043E\u043B\u044C\u0448\u0435 6 \u0447\u0430\u0441\u043E\u0432 \u2014 \u0434\u0435\u043C\u043E\u043D \u0432\u0441\u0451 \u0435\u0449\u0451 \u0433\u0430\u0441\u043D\u0435\u0442. \u0412\u0445\u043E\u0434 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u044E: \u043F\u043E\u0440\u0442 \u0437\u0430\u043D\u044F\u0442 \u0438\u043C (swarm status \u043F\u043E\u043A\u0430\u0436\u0435\u0442, \u043A\u0442\u043E \u0435\u0449\u0451 \u0436\u0438\u0432)." : "\u0434\u0435\u043C\u043E\u043D \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u043E\u043C\u0430\u043D\u0434\u0443, \u043D\u043E \u0437\u0430 30\u0441 \u043D\u0435 \u0432\u044B\u0448\u0435\u043B \u2014 \u0432\u0445\u043E\u0434 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u044E (\u043F\u043E\u0440\u0442 \u0437\u0430\u043D\u044F\u0442 \u0438\u043C). \u041F\u0440\u043E\u0432\u0435\u0440\u044C: swarm status"
9389
+ );
9390
+ return "failed";
9391
+ }
9392
+ async function runLoginFlow(db2, opts) {
9267
9393
  const web = startWeb({ db: db2 });
9268
9394
  try {
9269
9395
  let agent = loadAgentToken();
@@ -9351,7 +9477,7 @@ async function runLogin(db2, opts) {
9351
9477
  console.log(
9352
9478
  others.length ? `\u0440\u043E\u0439 \u0431\u0435\u0440\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0443 \u0418\u0417 \u0412\u0421\u0415\u0425 \u0442\u0432\u043E\u0438\u0445 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u043E\u0432 (\u0435\u0449\u0451 ${others.length}: ${others.map((w) => w.slug).join(", ")}) \u2014 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u0438\u0435\u0437\u0436\u0430\u0435\u0442 \u0441 \u043A\u0430\u0436\u0434\u044B\u043C \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u043C. \u0412\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u043D\u0443\u0436\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0432\u044B\u0437\u043E\u0432\u043E\u0432 \u043D\u0435 \u043F\u0440\u043E \u043F\u0440\u043E\u0433\u043E\u043D.` : "\u044D\u0442\u043E \u0435\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u0442\u043E\u043A\u0435\u043D\u0430."
9353
9479
  );
9354
- console.log("\u0433\u043E\u0442\u043E\u0432\u043E. \u041F\u043E\u0434\u043D\u044F\u0442\u044C \u0434\u0435\u043C\u043E\u043D\u0430: swarm start --repo <\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u043F\u043E>");
9480
+ console.log("\u0433\u043E\u0442\u043E\u0432\u043E \u2014 \u0432\u0445\u043E\u0434 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D.");
9355
9481
  return 0;
9356
9482
  } finally {
9357
9483
  web.server.close();
@@ -9627,7 +9753,9 @@ function usage() {
9627
9753
  swarm update \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0440\u043E\u0439 \u0434\u043E \u043E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 (\u0442\u0435\u043C \u0436\u0435 \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u043E\u043C,
9628
9754
  \u043A\u0430\u043A\u0438\u043C \u043E\u043D \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D)
9629
9755
  swarm login [--workspace <slug|id>] [--no-open]
9630
- \u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441
9756
+ \u0432\u043E\u0439\u0442\u0438 \u0432 Taskless \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441. \u0414\u0435\u043C\u043E\u043D\u0430 \u0433\u0430\u0441\u0438\u0442 \u0438 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442
9757
+ \u0441\u0430\u043C\u0430 (\u0436\u0438\u0432\u044B\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u0434\u043E\u0438\u0433\u0440\u044B\u0432\u0430\u044E\u0442) \u2014 \u043F\u0435\u0440\u0435\u043B\u043E\u0433\u0438\u043D \u044D\u0442\u043E \u043E\u0434\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430,
9758
+ \u0431\u0435\u0437 stop/start \u0440\u0443\u043A\u0430\u043C\u0438
9631
9759
  swarm start [--repo <path>] [--foreground]
9632
9760
  \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.
9633
9761
  \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;