@taskless-app/swarm 0.6.20 → 0.6.22

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
@@ -1776,10 +1776,24 @@ function asTokenUsage(m) {
1776
1776
  function budgetExhausted(result) {
1777
1777
  return result.subtype === "error_max_budget_usd" || result.terminal_reason === "budget_exhausted";
1778
1778
  }
1779
+ function sessionAbort(result) {
1780
+ if (budgetExhausted(result)) return null;
1781
+ const reason = result.terminal_reason ?? "";
1782
+ const subtype = result.subtype ?? "";
1783
+ if (reason === "aborted_tools") return { kind: "tools", raw: reason };
1784
+ if (reason === "aborted_streaming" || reason === "api_error") return { kind: "link", raw: reason };
1785
+ if (HONEST_ENDINGS.has(subtype) || HONEST_ENDINGS.has(reason)) return null;
1786
+ const failed = result.is_error === true || subtype === "error_during_execution" || reason.startsWith("aborted");
1787
+ if (!failed) return null;
1788
+ const raw = reason && reason !== "completed" ? reason : subtype || reason || "\u043E\u0448\u0438\u0431\u043A\u0430 \u0431\u0435\u0437 \u043C\u0435\u0442\u043A\u0438";
1789
+ return { kind: "other", raw };
1790
+ }
1791
+ var HONEST_ENDINGS;
1779
1792
  var init_claudeResult = __esm({
1780
1793
  "src/claudeResult.ts"() {
1781
1794
  "use strict";
1782
1795
  init_pricing();
1796
+ HONEST_ENDINGS = /* @__PURE__ */ new Set(["error_max_turns"]);
1783
1797
  }
1784
1798
  });
1785
1799
 
@@ -1854,6 +1868,9 @@ function writeMcpConfig(spec) {
1854
1868
  writeFileSync(path, JSON.stringify(cfg, null, 2));
1855
1869
  return path;
1856
1870
  }
1871
+ function abortField(abort) {
1872
+ return abort ? { aborted: abort } : {};
1873
+ }
1857
1874
  var ISOLATION_FLAGS, MCP_CONFIG_FILE, claudeCodeExecutor;
1858
1875
  var init_claudeCode = __esm({
1859
1876
  "src/executors/claudeCode.ts"() {
@@ -1892,7 +1909,10 @@ var init_claudeCode = __esm({
1892
1909
  durationMs: spend.durationMs,
1893
1910
  // TASWM-377: «упёрся в потолок» — отдельный факт об этом же итоге, а не отдельный
1894
1911
  // разбор файла: у claude он лежит в тех же полях, что деньги и токены.
1895
- ...budgetExhausted(result) ? { stoppedByBudget: true } : {}
1912
+ ...budgetExhausted(result) ? { stoppedByBudget: true } : {},
1913
+ // TASWM-487: и «оборвалось» — третий факт об этом же итоге. Порядок важен: потолок
1914
+ // трат sessionAbort отсекает сам, поэтому оба ключа одновременно не появятся.
1915
+ ...abortField(sessionAbort(result))
1896
1916
  };
1897
1917
  },
1898
1918
  session: {
@@ -2373,7 +2393,7 @@ var init_package = __esm({
2373
2393
  "package.json"() {
2374
2394
  package_default = {
2375
2395
  name: "@taskless-app/swarm",
2376
- version: "0.6.20",
2396
+ version: "0.6.22",
2377
2397
  type: "module",
2378
2398
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2379
2399
  keywords: [
@@ -2423,7 +2443,7 @@ var init_package = __esm({
2423
2443
  execa: "^10.0.0",
2424
2444
  zod: "3.25.76"
2425
2445
  },
2426
- gitHead: "2d05a2e2b7c197ab916f7ff756e3a829b138df16"
2446
+ gitHead: "b8eaac4a5562ffcf45a02af338267620e0a7843f"
2427
2447
  };
2428
2448
  }
2429
2449
  });
@@ -8510,10 +8530,24 @@ async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
8510
8530
  }
8511
8531
  }
8512
8532
  var NO_CHECKS_STDERR = /no checks reported/i;
8533
+ var CiAccessDeniedError = class extends Error {
8534
+ detail;
8535
+ constructor(detail) {
8536
+ super(`\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u0447\u0435\u043A\u0430\u043C: ${detail}`);
8537
+ this.name = "CiAccessDeniedError";
8538
+ this.detail = detail;
8539
+ }
8540
+ };
8541
+ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have admin rights|resource not accessible/i;
8542
+ function firstLine(s) {
8543
+ return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
8544
+ }
8513
8545
  var realChecksRunner = async (pr) => {
8514
8546
  const r = await $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
8515
8547
  const stdout = r.stdout ?? "";
8516
- if (!stdout.trim() && NO_CHECKS_STDERR.test(r.stderr ?? "")) return "[]";
8548
+ const stderr = r.stderr ?? "";
8549
+ if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
8550
+ if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
8517
8551
  return stdout;
8518
8552
  };
8519
8553
  async function prCheckRows(pr, run) {
@@ -8522,13 +8556,17 @@ async function prCheckRows(pr, run) {
8522
8556
  const rows = JSON.parse(out);
8523
8557
  if (!Array.isArray(rows)) return null;
8524
8558
  return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
8525
- } catch {
8559
+ } catch (e) {
8560
+ if (e instanceof CiAccessDeniedError) return { denied: e.detail };
8526
8561
  return null;
8527
8562
  }
8528
8563
  }
8564
+ function isDenied(rows) {
8565
+ return rows != null && !Array.isArray(rows);
8566
+ }
8529
8567
  async function ghChecks(pr, run = realChecksRunner) {
8530
8568
  const rows = await prCheckRows(pr, run);
8531
- if (!rows || rows.length === 0) return null;
8569
+ if (!rows || isDenied(rows) || rows.length === 0) return null;
8532
8570
  let pass = 0, fail = 0, pending2 = 0;
8533
8571
  for (const r of rows) {
8534
8572
  if (r.bucket === "pass") pass++;
@@ -11084,6 +11122,44 @@ init_config();
11084
11122
  init_agentToken();
11085
11123
  init_linkFailure();
11086
11124
 
11125
+ // src/toolFailure.ts
11126
+ init_linkFailure();
11127
+ var TOOL_FAILURE = {
11128
+ /** Тул отработал, но искомого в ответе нет: задачи с таким слагом в воркспейсе не существует. */
11129
+ NOT_FOUND: "not_found",
11130
+ /** Объект есть, но токену роя не виден (приватный проект, спейс, чужой воркспейс). */
11131
+ FORBIDDEN: "forbidden",
11132
+ /** Токен роя не состоит в воркспейсе, которым искали. */
11133
+ WORKSPACE_MISMATCH: "workspace_mismatch",
11134
+ /** До прода не достучались: сеть, DNS, таймаут, оборванный сокет. */
11135
+ UNREACHABLE: "unreachable",
11136
+ /** Прод ответил, но токен роя отвергнут (отозван, протух). Сетью это не лечится. */
11137
+ UNAUTHORIZED: "unauthorized",
11138
+ /** Тул ответил ошибкой, которую мы не узнали, — текст кладём в detail как есть. */
11139
+ TOOL_ERROR: "tool_error",
11140
+ /** Успех, но полезной нагрузки в ответе нет (форма ответа разошлась с ожиданием). */
11141
+ EMPTY: "empty"
11142
+ };
11143
+ var TOOL_FAILURE_TEXT = {
11144
+ [TOOL_FAILURE.NOT_FOUND]: "\u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435 \u0442\u0430\u043A\u043E\u0433\u043E \u043D\u0435\u0442 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u043B\u0430\u0433 \u0438 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441 \u043F\u0440\u043E\u0433\u043E\u043D\u0430",
11145
+ [TOOL_FAILURE.FORBIDDEN]: "\u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043D\u0435\u0442: \u043E\u0431\u044A\u0435\u043A\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E \u0442\u043E\u043A\u0435\u043D\u0443 \u0440\u043E\u044F \u043D\u0435 \u0432\u0438\u0434\u0435\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
11146
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442 \u0432 \u044D\u0442\u043E\u043C \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0435",
11147
+ [TOOL_FAILURE.UNREACHABLE]: "Taskless \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u2014 \u0441\u0435\u0442\u044C, DNS \u0438\u043B\u0438 \u0442\u0430\u0439\u043C\u0430\u0443\u0442",
11148
+ [TOOL_FAILURE.UNAUTHORIZED]: "\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043E\u0442\u0432\u0435\u0440\u0433\u043D\u0443\u0442 \u043F\u0440\u043E\u0434\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D swarm login \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435",
11149
+ [TOOL_FAILURE.TOOL_ERROR]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439",
11150
+ [TOOL_FAILURE.EMPTY]: "\u0442\u0443\u043B \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0443\u0441\u043F\u0435\u0445\u043E\u043C, \u043D\u043E \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442"
11151
+ };
11152
+ var TOOL_FAILURE_RETRIABLE = {
11153
+ [TOOL_FAILURE.NOT_FOUND]: false,
11154
+ [TOOL_FAILURE.FORBIDDEN]: false,
11155
+ [TOOL_FAILURE.WORKSPACE_MISMATCH]: false,
11156
+ [TOOL_FAILURE.UNREACHABLE]: true,
11157
+ // Отозванный токен повтором не чинится — его меняет человек (`swarm login`).
11158
+ [TOOL_FAILURE.UNAUTHORIZED]: false,
11159
+ [TOOL_FAILURE.TOOL_ERROR]: false,
11160
+ [TOOL_FAILURE.EMPTY]: false
11161
+ };
11162
+
11087
11163
  // src/workspace.ts
11088
11164
  init_config();
11089
11165
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4 } from "fs";