@taskless-app/swarm 0.6.66 → 0.6.68

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.
Files changed (3) hide show
  1. package/dist/cli.js +211 -43
  2. package/dist/overmind.js +550 -63
  3. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -254,7 +254,8 @@ var init_reviewPhase = __esm({
254
254
  verify_dead: "\u0412\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043D\u0435 \u0434\u0430\u043B \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
255
255
  ready: "\u0417\u0435\u043B\u0451\u043D\u044B\u0439 \u2014 \u0433\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443",
256
256
  ready_awaiting_ci: "\u0413\u043E\u0442\u043E\u0432 \u043A \u043C\u0435\u0440\u0436\u0443 \u2014 \u0436\u0434\u0451\u0442 CI",
257
- reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
257
+ reviewer_dead: "\u0420\u0435\u0432\u044C\u044E\u0435\u0440 \u0443\u043C\u0435\u0440 \u2014 \u0432\u0435\u0440\u0434\u0438\u043A\u0442\u0430 \u043D\u0435\u0442, \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A",
258
+ cycles_exhausted: "\u041B\u0438\u043C\u0438\u0442 \u043A\u0440\u0443\u0433\u043E\u0432 \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u043D\u0443\u0436\u0435\u043D \u0447\u0435\u043B\u043E\u0432\u0435\u043A"
258
259
  };
259
260
  PHASE_BY_LENGTH = Object.keys(REVIEW_PHASE_TEXT).sort(
260
261
  (a, b) => REVIEW_PHASE_TEXT[b].length - REVIEW_PHASE_TEXT[a].length
@@ -1789,7 +1790,7 @@ var init_effort = __esm({
1789
1790
  });
1790
1791
 
1791
1792
  // ../swarm-core/src/queuePlan.ts
1792
- var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS;
1793
+ var MIN_MS, QUEUE_NOMINAL_RUN_MS, QUEUE_MIN_TAIL_MS, SERVICE_DONE_KEEP_MS, PR_HEAD_LAG_MS, SERVICE_DONE_MARKER_MS;
1793
1794
  var init_queuePlan = __esm({
1794
1795
  "../swarm-core/src/queuePlan.ts"() {
1795
1796
  "use strict";
@@ -1802,6 +1803,9 @@ var init_queuePlan = __esm({
1802
1803
  MIN_MS = 6e4;
1803
1804
  QUEUE_NOMINAL_RUN_MS = 25 * MIN_MS;
1804
1805
  QUEUE_MIN_TAIL_MS = 3 * MIN_MS;
1806
+ SERVICE_DONE_KEEP_MS = 2 * 60 * MIN_MS;
1807
+ PR_HEAD_LAG_MS = 5 * MIN_MS;
1808
+ SERVICE_DONE_MARKER_MS = 8 * MIN_MS;
1805
1809
  }
1806
1810
  });
1807
1811
 
@@ -3526,7 +3530,7 @@ var init_linkFailure = __esm({
3526
3530
  "use strict";
3527
3531
  init_agentToken();
3528
3532
  UNKNOWN_LINK_REASON = "\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u043D\u0435\u0442, \u0430 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u043A\u043E\u043D\u043D\u0435\u043A\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B";
3529
- OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout/i;
3533
+ OFFLINE_RE = /fetch failed|econnreset|econnrefused|econnaborted|enotfound|eai_again|etimedout|ehostunreach|enetunreach|epipe|socket hang up|und_err|network error|connection (?:closed|refused|reset|error)|connect timeout|request timed out|-32001\b/i;
3530
3534
  MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
3531
3535
  }
3532
3536
  });
@@ -3876,7 +3880,7 @@ var init_package = __esm({
3876
3880
  "package.json"() {
3877
3881
  package_default = {
3878
3882
  name: "@taskless-app/swarm",
3879
- version: "0.6.66",
3883
+ version: "0.6.68",
3880
3884
  type: "module",
3881
3885
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3882
3886
  keywords: [
@@ -3925,9 +3929,10 @@ var init_package = __esm({
3925
3929
  "@hono/node-server": "^2.0.12",
3926
3930
  "@modelcontextprotocol/sdk": "^1.29.0",
3927
3931
  execa: "^10.0.0",
3932
+ undici: "^7.25.0",
3928
3933
  zod: "3.25.76"
3929
3934
  },
3930
- gitHead: "a6bc0ea303cb5336fd4e130fba73d90365268b6f"
3935
+ gitHead: "4bfd78926aa37ef7de8ea06a6763d7e84a2fded6"
3931
3936
  };
3932
3937
  }
3933
3938
  });
@@ -4660,6 +4665,47 @@ var init_shotUpload = __esm({
4660
4665
  }
4661
4666
  });
4662
4667
 
4668
+ // src/prodHttp.ts
4669
+ import { Agent, fetch as undiciFetch } from "undici";
4670
+ function prodDispatcher() {
4671
+ agent ??= new Agent({
4672
+ keepAliveTimeout: PROD_KEEPALIVE_MS,
4673
+ keepAliveMaxTimeout: PROD_KEEPALIVE_MS,
4674
+ headersTimeout: PROD_SOCKET_TIMEOUT_MS,
4675
+ bodyTimeout: PROD_SOCKET_TIMEOUT_MS,
4676
+ connect: { timeout: PROD_CONNECT_TIMEOUT_MS }
4677
+ });
4678
+ return agent;
4679
+ }
4680
+ function isProdTimeout(e) {
4681
+ for (let cur = e, depth = 0; cur != null && depth < 4; cur = cur.cause, depth++) {
4682
+ const code = cur.code;
4683
+ const name = cur.name;
4684
+ const msg = cur instanceof Error ? cur.message : String(cur);
4685
+ if (code === -32001 || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT") return true;
4686
+ if (name === "TimeoutError" || /request timed out/i.test(msg)) return true;
4687
+ }
4688
+ return false;
4689
+ }
4690
+ var PROD_CALL_TIMEOUT_MS, FETCH_GRACE_MS, PROD_KEEPALIVE_MS, PROD_SOCKET_TIMEOUT_MS, PROD_CONNECT_TIMEOUT_MS, agent, prodFetch;
4691
+ var init_prodHttp = __esm({
4692
+ "src/prodHttp.ts"() {
4693
+ "use strict";
4694
+ PROD_CALL_TIMEOUT_MS = 15e3;
4695
+ FETCH_GRACE_MS = 5e3;
4696
+ PROD_KEEPALIVE_MS = 1e4;
4697
+ PROD_SOCKET_TIMEOUT_MS = 3e4;
4698
+ PROD_CONNECT_TIMEOUT_MS = 1e4;
4699
+ agent = null;
4700
+ prodFetch = (url, init) => {
4701
+ const method = (init?.method ?? "GET").toUpperCase();
4702
+ const deadline = method === "GET" ? null : AbortSignal.timeout(PROD_CALL_TIMEOUT_MS + FETCH_GRACE_MS);
4703
+ const signal = deadline && init?.signal ? AbortSignal.any([init.signal, deadline]) : deadline ?? init?.signal ?? void 0;
4704
+ return undiciFetch(url, { ...init, ...signal ? { signal } : {}, dispatcher: prodDispatcher() });
4705
+ };
4706
+ }
4707
+ });
4708
+
4663
4709
  // src/workspace.ts
4664
4710
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
4665
4711
  import { join as join19 } from "path";
@@ -4711,8 +4757,8 @@ function parseJsonContent(res) {
4711
4757
  }
4712
4758
  async function resolveApiToken(api, explicit) {
4713
4759
  if (explicit) return explicit;
4714
- const agent = loadAgentToken();
4715
- if (agent?.token) return agent.token;
4760
+ const agent2 = loadAgentToken();
4761
+ if (agent2?.token) return agent2.token;
4716
4762
  const store = loadStore();
4717
4763
  if (store) {
4718
4764
  const r = await ensureAccess(store, { api });
@@ -4741,7 +4787,9 @@ async function connectSwarmMcp(cfg) {
4741
4787
  }
4742
4788
  const url = new URL(mcpEndpoint(api));
4743
4789
  const transport = new StreamableHTTPClientTransport(url, {
4744
- requestInit: { headers: { Authorization: `Bearer ${token}` } }
4790
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
4791
+ fetch: prodFetch
4792
+ // TASWM-611
4745
4793
  });
4746
4794
  const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
4747
4795
  try {
@@ -4760,12 +4808,31 @@ async function connectSwarmMcp(cfg) {
4760
4808
  }
4761
4809
  swarmConnectFailure = null;
4762
4810
  noteTokenAccepted();
4763
- return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
4811
+ const timeout = cfg?.callTimeoutMs ?? PROD_CALL_TIMEOUT_MS;
4812
+ return async (name, args) => {
4813
+ try {
4814
+ return await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) }, void 0, { timeout });
4815
+ } catch (e) {
4816
+ if (isProdTimeout(e)) {
4817
+ console.warn(`${prodTimeoutLine("Swarm MCP", name, timeout)} \u2014 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u043E, \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439 \u0432\u044B\u0437\u043E\u0432 \u043F\u0435\u0440\u0435\u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u0441\u044F`);
4818
+ }
4819
+ void client.close().catch(() => {
4820
+ });
4821
+ throw e;
4822
+ }
4823
+ };
4824
+ }
4825
+ function prodTimeoutLine(scope, tool, timeoutMs) {
4826
+ return `[${scope}] \u23F1 ${tool}: \u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(timeoutMs / 100) / 10}\u0441, \u0432\u044B\u0437\u043E\u0432 \u043F\u0440\u0435\u0440\u0432\u0430\u043D`;
4764
4827
  }
4765
4828
  function lastSwarmConnectFailure() {
4766
4829
  return swarmConnectFailure;
4767
4830
  }
4768
4831
  function swarmToolArgs(name, args, workspace) {
4832
+ if ("workspaceId" in args && args.workspaceId == null) {
4833
+ const { workspaceId: _drop, ...rest } = args;
4834
+ return rest;
4835
+ }
4769
4836
  if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
4770
4837
  return { workspaceId: workspace, ...args };
4771
4838
  }
@@ -4780,6 +4847,7 @@ var init_taskless = __esm({
4780
4847
  init_oauth();
4781
4848
  init_agentToken();
4782
4849
  init_linkFailure();
4850
+ init_prodHttp();
4783
4851
  init_toolFailure();
4784
4852
  init_authState();
4785
4853
  init_workspace();
@@ -7693,7 +7761,7 @@ var init_updateCli = __esm({
7693
7761
  // src/cli.ts
7694
7762
  init_cliIo();
7695
7763
  import { basename as basename6, dirname as dirname5, join as join27 } from "path";
7696
- import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync12, statSync as statSync12, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
7764
+ import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync12, statSync as statSync12, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
7697
7765
  import { setTimeout as sleep2 } from "timers/promises";
7698
7766
 
7699
7767
  // src/swarmClient.ts
@@ -10057,7 +10125,7 @@ var SwarmClient = class _SwarmClient {
10057
10125
  // 14.07/18.07, TASWM-201 — общий карантин; здесь — конкретный источник). Резолвим
10058
10126
  // слаг→cuid через get_task (кэш — taskIdCache) ПЕРЕД тем как слать create.
10059
10127
  taskIdCache = /* @__PURE__ */ new Map();
10060
- // task_slug → cuid прода
10128
+ // task_slug → cuid прода и воркспейс задачи
10061
10129
  // Create для этого runKey резолвился окончательно неудачно (задача не найдена/
10062
10130
  // недоступна) — событие/вопросы того же прогона тоже никогда не получат prod-id,
10063
10131
  // без карантина они бы бросали «нет prod-id» на каждом флаше и СНОВА вставали
@@ -10072,12 +10140,20 @@ var SwarmClient = class _SwarmClient {
10072
10140
  const cacheKey = `${taskRef}\0${repoFullName ?? ""}\0${workspaceId ?? ""}`;
10073
10141
  const cached = this.taskIdCache.get(cacheKey);
10074
10142
  if (cached) return cached;
10143
+ const first = await this.readTaskRef(taskRef, repoFullName, workspaceId);
10144
+ const res = first === null && repoFullName && !workspaceId ? await this.readTaskRef(taskRef, repoFullName, null) : first;
10145
+ if (res && res !== "transient") this.taskIdCache.set(cacheKey, res);
10146
+ return res;
10147
+ }
10148
+ /** Один get_task: cuid задачи и её воркспейс; null — прод ответил «нет такой». */
10149
+ async readTaskRef(taskRef, repoFullName, workspaceId) {
10075
10150
  let res;
10076
10151
  try {
10077
10152
  res = await this.call("get_task", {
10078
10153
  id: taskRef,
10079
10154
  ...repoFullName ? { repoFullName } : {},
10080
- ...workspaceId ? { workspaceId } : {}
10155
+ // null — «без пина демона» (см. swarmToolArgs), undefined — пин по умолчанию.
10156
+ ...workspaceId ? { workspaceId } : workspaceId === null ? { workspaceId: null } : {}
10081
10157
  });
10082
10158
  } catch {
10083
10159
  return "transient";
@@ -10087,10 +10163,9 @@ var SwarmClient = class _SwarmClient {
10087
10163
  const text = (res.content ?? []).map((c) => c.text ?? "").join(" ");
10088
10164
  return /not_found/i.test(text) ? null : "transient";
10089
10165
  }
10090
- const id = res.structuredContent?.task?.id;
10091
- if (!id) return null;
10092
- this.taskIdCache.set(cacheKey, id);
10093
- return id;
10166
+ const task = res.structuredContent?.task;
10167
+ if (!task?.id) return null;
10168
+ return { id: task.id, workspaceId: typeof task.workspaceId === "string" ? task.workspaceId : null };
10094
10169
  }
10095
10170
  // MCP-31: привести repo прогона к owner/repo-форме для подсказки резолверу
10096
10171
  // задачи. Прод сматчит подсказку с ProjectRepo.repoFullName → воркспейс. repo
@@ -10129,18 +10204,19 @@ var SwarmClient = class _SwarmClient {
10129
10204
  this.poisonedRunKeys.add(e.runKey);
10130
10205
  return "skip";
10131
10206
  }
10132
- const taskId = await this.resolveTaskCuid(e.taskRef, this.repoFullNameFor(fields.repo), e.workspaceId);
10133
- if (taskId === "transient") {
10207
+ const task = await this.resolveTaskCuid(e.taskRef, this.repoFullNameFor(fields.repo), e.workspaceId);
10208
+ if (task === "transient") {
10134
10209
  throw new Error(`slug\u2192cuid \u0434\u043B\u044F \xAB${e.taskRef}\xBB \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D (\u043F\u0440\u043E\u0434 \u043C\u043E\u0440\u0433\u043D\u0443\u043B) \u2014 retry`);
10135
10210
  }
10136
- if (!taskId) {
10211
+ if (!task) {
10137
10212
  console.error(
10138
10213
  `[swarm-client] create-\u0444\u043B\u0430\u0448 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D: \u043F\u0440\u043E\u0434 \u043D\u0435 \u0437\u043D\u0430\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\u0443 \xAB${e.taskRef}\xBB (runKey=${e.runKey}) \u2014 \u0437\u0430\u043F\u0438\u0441\u044C \u0432\u044B\u0431\u0440\u043E\u0448\u0435\u043D\u0430 \u0438\u0437 outbox`
10139
10214
  );
10140
10215
  this.poisonedRunKeys.add(e.runKey);
10141
10216
  return "skip";
10142
10217
  }
10143
- return { tool: "swarm_run_upsert", args: { taskId, ...e.workspaceId ? { workspaceId: e.workspaceId } : {}, ...fields } };
10218
+ const workspaceId = e.workspaceId || task.workspaceId;
10219
+ return { tool: "swarm_run_upsert", args: { taskId: task.id, ...workspaceId ? { workspaceId } : {}, ...fields } };
10144
10220
  }
10145
10221
  if (this.poisonedRunKeys.has(e.runKey)) {
10146
10222
  console.error(`[swarm-client] ${e.t}-\u0444\u043B\u0430\u0448 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D: create \u043F\u0440\u043E\u0433\u043E\u043D\u0430 runKey=${e.runKey} \u043D\u0435 \u0440\u0435\u0437\u043E\u043B\u0432\u0438\u043B\u0441\u044F \u0440\u0430\u043D\u0435\u0435 \u2014 \u0437\u0430\u043F\u0438\u0441\u044C \u0432\u044B\u0431\u0440\u043E\u0448\u0435\u043D\u0430 \u0438\u0437 outbox`);
@@ -10192,6 +10268,7 @@ var SwarmClient = class _SwarmClient {
10192
10268
  } catch {
10193
10269
  break;
10194
10270
  }
10271
+ if (res?.isError && res.unavailable) break;
10195
10272
  if (res?.isError) {
10196
10273
  const reason = (res.content ?? []).map((c) => c.text).filter(Boolean).join(" ").trim() || "\u0431\u0435\u0437 \u043E\u0431\u044A\u044F\u0441\u043D\u0435\u043D\u0438\u044F";
10197
10274
  const tries = (this.rejected.get(e.seq) ?? 0) + 1;
@@ -10444,7 +10521,7 @@ function tailFile(path, lines) {
10444
10521
  }
10445
10522
 
10446
10523
  // src/logRotate.ts
10447
- import { copyFileSync as copyFileSync4, renameSync as renameSync3, statSync as statSync7, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8 } from "fs";
10524
+ import { copyFileSync as copyFileSync4, renameSync as renameSync3, statSync as statSync7, fstatSync, truncateSync, unlinkSync as unlinkSync2, existsSync as existsSync8, openSync as openSync3, writeSync, closeSync as closeSync3 } from "fs";
10448
10525
  var realIo = {
10449
10526
  size: (path) => {
10450
10527
  try {
@@ -10459,6 +10536,31 @@ var realIo = {
10459
10536
  unlink: unlinkSync2,
10460
10537
  exists: existsSync8
10461
10538
  };
10539
+ var realOwnerIo = {
10540
+ statFd: (fd) => {
10541
+ try {
10542
+ const s = fstatSync(fd);
10543
+ return { dev: s.dev, ino: s.ino, isFile: s.isFile() };
10544
+ } catch {
10545
+ return null;
10546
+ }
10547
+ },
10548
+ statPath: (path) => {
10549
+ try {
10550
+ const s = statSync7(path);
10551
+ return { dev: s.dev, ino: s.ino };
10552
+ } catch {
10553
+ return null;
10554
+ }
10555
+ }
10556
+ };
10557
+ function stdoutGoesTo(path, io = realOwnerIo) {
10558
+ if (teedTo === path) return true;
10559
+ const out = io.statFd(1);
10560
+ if (!out || !out.isFile) return false;
10561
+ const file = io.statPath(path);
10562
+ return file != null && file.dev === out.dev && file.ino === out.ino;
10563
+ }
10462
10564
  var STRAY_GENERATIONS = 8;
10463
10565
  function rotateDaemonLog(path, opts, io = realIo) {
10464
10566
  const size = io.size(path);
@@ -10478,6 +10580,33 @@ function rotateDaemonLog(path, opts, io = realIo) {
10478
10580
  io.truncate(path);
10479
10581
  return "rotated";
10480
10582
  }
10583
+ var teedTo = null;
10584
+ function teeStdioToFile(path) {
10585
+ if (teedTo === path || stdoutGoesTo(path)) return null;
10586
+ const fd = openSync3(path, "a");
10587
+ const restore = [];
10588
+ for (const stream of [process.stdout, process.stderr]) {
10589
+ const original = stream.write;
10590
+ restore.push(() => {
10591
+ stream.write = original;
10592
+ });
10593
+ const orig = original.bind(stream);
10594
+ stream.write = ((chunk, ...rest) => {
10595
+ try {
10596
+ if (typeof chunk === "string") writeSync(fd, chunk);
10597
+ else if (chunk instanceof Uint8Array) writeSync(fd, chunk);
10598
+ } catch {
10599
+ }
10600
+ return orig(chunk, ...rest);
10601
+ });
10602
+ }
10603
+ teedTo = path;
10604
+ return () => {
10605
+ for (const undo of restore) undo();
10606
+ teedTo = null;
10607
+ closeSync3(fd);
10608
+ };
10609
+ }
10481
10610
 
10482
10611
  // src/cli.ts
10483
10612
  init_procCleanup();
@@ -12803,7 +12932,7 @@ import { cancelRun, respawnRun, runDaemon, wakeRun, OVERMIND_ENTRY, listDronePro
12803
12932
 
12804
12933
  // src/launcher.ts
12805
12934
  import { spawn as spawn2 } from "child_process";
12806
- import { closeSync as closeSync3, existsSync as existsSync10, mkdirSync as mkdirSync10, openSync as openSync3, readdirSync as readdirSync9 } from "fs";
12935
+ import { closeSync as closeSync4, existsSync as existsSync10, mkdirSync as mkdirSync10, openSync as openSync4, readdirSync as readdirSync9 } from "fs";
12807
12936
  import { createRequire } from "module";
12808
12937
  import { homedir as homedir5 } from "os";
12809
12938
  import { dirname as dirname4, join as join15 } from "path";
@@ -13012,14 +13141,14 @@ function openNextRunLog(runLogsDir, runLogKeys) {
13012
13141
  const path = nextRunLogPath(runLogsDir, runLogKeys);
13013
13142
  let out;
13014
13143
  try {
13015
- out = openSync3(path, "wx");
13144
+ out = openSync4(path, "wx");
13016
13145
  } catch (e) {
13017
13146
  if (e.code !== "EEXIST") throw e;
13018
13147
  continue;
13019
13148
  }
13020
13149
  let err = null;
13021
13150
  try {
13022
- err = openSync3(`${path.slice(0, -".json".length)}.err.log`, "w");
13151
+ err = openSync4(`${path.slice(0, -".json".length)}.err.log`, "w");
13023
13152
  } catch {
13024
13153
  err = null;
13025
13154
  }
@@ -13149,8 +13278,8 @@ function makeLauncher(cfg) {
13149
13278
  // они про его собственное состояние, и перебивать их ройным нечем.
13150
13279
  env: { ...droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null), ...plan.env }
13151
13280
  });
13152
- if (outFd !== void 0) closeSync3(outFd);
13153
- if (errFd !== void 0) closeSync3(errFd);
13281
+ if (outFd !== void 0) closeSync4(outFd);
13282
+ if (errFd !== void 0) closeSync4(errFd);
13154
13283
  child.unref();
13155
13284
  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)");
13156
13285
  return child.pid;
@@ -13167,11 +13296,17 @@ init_taskless();
13167
13296
  // src/mcpResilient.ts
13168
13297
  init_linkFailure();
13169
13298
  init_taskless();
13299
+ init_prodHttp();
13170
13300
  var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
13171
13301
  var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
13172
13302
  var DEFAULT_RETRY_DELAYS_MS = [200, 800];
13173
- function errorResult(reason) {
13174
- return { isError: true, content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }] };
13303
+ var PROD_PASS_BUDGET_MS = 2e4;
13304
+ function errorResult(reason, unavailable) {
13305
+ return {
13306
+ isError: true,
13307
+ ...unavailable ? { unavailable: true } : {},
13308
+ content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }]
13309
+ };
13175
13310
  }
13176
13311
  function resilientSwarmMcp(opts = {}) {
13177
13312
  const connect = opts.connect ?? (() => connectSwarmMcp());
@@ -13179,6 +13314,13 @@ function resilientSwarmMcp(opts = {}) {
13179
13314
  const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
13180
13315
  const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
13181
13316
  const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
13317
+ const clock = opts.now ?? Date.now;
13318
+ const passBudgetMs = opts.passBudgetMs ?? PROD_PASS_BUDGET_MS;
13319
+ let passSpentMs = null;
13320
+ let budgetNoted = false;
13321
+ const spend = (since) => {
13322
+ if (passSpentMs !== null) passSpentMs += Math.max(0, clock() - since);
13323
+ };
13182
13324
  let caller = null;
13183
13325
  let lastTryAt = 0;
13184
13326
  let failures = 0;
@@ -13201,6 +13343,7 @@ function resilientSwarmMcp(opts = {}) {
13201
13343
  if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
13202
13344
  if (!force) lastTryAt = now;
13203
13345
  let fresh = null;
13346
+ const connectStart = clock();
13204
13347
  try {
13205
13348
  fresh = await connect();
13206
13349
  if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
@@ -13217,17 +13360,30 @@ function resilientSwarmMcp(opts = {}) {
13217
13360
  downReported = false;
13218
13361
  return caller;
13219
13362
  }
13363
+ spend(connectStart);
13220
13364
  if (!force) failures += 1;
13221
13365
  if (downSince === null) downSince = now;
13222
13366
  return null;
13223
13367
  }
13224
13368
  const call = async (name, args) => {
13225
13369
  for (let attempt = 0; ; attempt++) {
13370
+ if (passSpentMs !== null && passSpentMs >= passBudgetMs) {
13371
+ if (!budgetNoted) {
13372
+ budgetNoted = true;
13373
+ opts.onBudgetSpent?.({ spentMs: passSpentMs, budgetMs: passBudgetMs });
13374
+ }
13375
+ maybeReportDown();
13376
+ return errorResult(
13377
+ `\u043F\u0440\u043E\u0434 \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u0431\u044E\u0434\u0436\u0435\u0442 \u043F\u0440\u043E\u0445\u043E\u0434\u0430 \u043D\u0430 \u0441\u0435\u0442\u044C (${Math.round(passBudgetMs / 1e3)}\u0441) \u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u2014 \u0434\u043E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0445\u043E\u0434\u0430 \u0431\u0435\u0437 \u0441\u0435\u0442\u0438${lastCallReason ?? lastConnectReason ? ` (\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u043F\u0440\u0438\u0447\u0438\u043D\u0430: ${lastCallReason ?? lastConnectReason})` : ""}`,
13378
+ true
13379
+ );
13380
+ }
13226
13381
  const active = await ensureCaller(attempt > 0);
13227
13382
  if (!active) {
13228
13383
  maybeReportDown();
13229
- return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
13384
+ return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON, true);
13230
13385
  }
13386
+ const callStart = clock();
13231
13387
  try {
13232
13388
  const res = await active(name, args);
13233
13389
  lastOkAt = Date.now();
@@ -13235,13 +13391,14 @@ function resilientSwarmMcp(opts = {}) {
13235
13391
  return res;
13236
13392
  } catch (e) {
13237
13393
  caller = null;
13394
+ spend(callStart);
13238
13395
  if (downSince === null) downSince = Date.now();
13239
13396
  const { kind, detail: reason } = explainLinkFailure(e);
13240
13397
  lastCallReason = reason;
13241
13398
  const delay = retryDelays[attempt];
13242
- if (delay === void 0 || kind !== "offline") {
13399
+ if (delay === void 0 || kind !== "offline" || isProdTimeout(e)) {
13243
13400
  maybeReportDown();
13244
- return errorResult(reason);
13401
+ return errorResult(reason, kind === "offline" || kind === "unknown" || kind === "unauthorized" || isProdTimeout(e));
13245
13402
  }
13246
13403
  opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
13247
13404
  await sleep3(delay);
@@ -13249,6 +13406,10 @@ function resilientSwarmMcp(opts = {}) {
13249
13406
  }
13250
13407
  };
13251
13408
  return Object.assign(call, {
13409
+ beginPass: () => {
13410
+ passSpentMs = 0;
13411
+ budgetNoted = false;
13412
+ },
13252
13413
  // Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
13253
13414
  // подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
13254
13415
  link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
@@ -13807,13 +13968,13 @@ async function stopDaemonForLogin() {
13807
13968
  async function runLoginFlow(db2, opts) {
13808
13969
  const web = startWeb({ db: db2 });
13809
13970
  try {
13810
- let agent = loadAgentToken();
13811
- if (agent && opts.force) {
13971
+ let agent2 = loadAgentToken();
13972
+ if (agent2 && opts.force) {
13812
13973
  console.log("--force: \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E, \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u0441\u0442\u0430\u0440\u044B\u0439 \u0442\u043E\u043A\u0435\u043D.");
13813
- agent = null;
13974
+ agent2 = null;
13814
13975
  }
13815
- if (agent) {
13816
- const probe = await probeAgentToken({ api: TASKLESS_API, token: agent.token });
13976
+ if (agent2) {
13977
+ const probe = await probeAgentToken({ api: TASKLESS_API, token: agent2.token });
13817
13978
  if (probe === "ok") {
13818
13979
  console.log("\u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435 \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u044E \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u044B\u0431\u043E\u0440 \u0432\u043E\u0440\u043A\u0441\u043F\u0435\u0439\u0441\u0430.");
13819
13980
  } else {
@@ -13825,10 +13986,10 @@ async function runLoginFlow(db2, opts) {
13825
13986
  "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0442\u043E\u043A\u0435\u043D \u0440\u043E\u044F \u043D\u0435 \u0432\u044B\u0448\u043B\u043E (\u0441\u0435\u0442\u044C/\u043F\u0440\u043E\u0434/\u043E\u0442\u043A\u0430\u0437) \u2014 \u0432\u0445\u043E\u0436\u0443 \u0437\u0430\u043D\u043E\u0432\u043E: \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u043D\u0435\u043F\u0440\u043E\u0448\u0435\u0434\u0448\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0437\u0430 \xAB\u0432\u0441\u0451 \u0432 \u043F\u043E\u0440\u044F\u0434\u043A\u0435\xBB \u043D\u0435\u043B\u044C\u0437\u044F."
13826
13987
  );
13827
13988
  }
13828
- agent = null;
13989
+ agent2 = null;
13829
13990
  }
13830
13991
  }
13831
- if (!agent) {
13992
+ if (!agent2) {
13832
13993
  let store = loadStore();
13833
13994
  if (!store) {
13834
13995
  const loginUrl = `http://127.0.0.1:${web.port}/login`;
@@ -14017,14 +14178,14 @@ async function runDaemonLogs(argv) {
14017
14178
  }
14018
14179
  if (size < pos) pos = 0;
14019
14180
  if (size === pos) continue;
14020
- const fd = openSync4(DAEMON_LOG_PATH, "r");
14181
+ const fd = openSync5(DAEMON_LOG_PATH, "r");
14021
14182
  try {
14022
14183
  const buf = Buffer.alloc(size - pos);
14023
14184
  const read = readSync2(fd, buf, 0, buf.length, pos);
14024
14185
  process.stdout.write(buf.subarray(0, read).toString("utf8"));
14025
14186
  pos += read;
14026
14187
  } finally {
14027
- closeSync4(fd);
14188
+ closeSync5(fd);
14028
14189
  }
14029
14190
  }
14030
14191
  }
@@ -14302,7 +14463,7 @@ async function makeCliStore(repo) {
14302
14463
  onDown: (ms) => console.warn(`Swarm MCP \u043C\u043E\u043B\u0447\u0438\u0442 ${Math.round(ms / 1e3)}\u0441 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u043C \u0432\u0441\u043B\u0435\u043F\u0443\u044E, \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0441\u0432\u044F\u0437\u044C \u0441 \u043F\u0440\u043E\u0434\u043E\u043C.`),
14303
14464
  onUp: () => console.warn("Swarm MCP \u0441\u043D\u043E\u0432\u0430 \u043D\u0430 \u0441\u0432\u044F\u0437\u0438.")
14304
14465
  });
14305
- const repoRegistry = await buildRepoRegistry(SWARM_REPO_PATHS);
14466
+ const repoRegistry = await buildRepoRegistry([...new Set([repo, ...SWARM_REPO_PATHS].filter(Boolean))]);
14306
14467
  const client = new SwarmClient({ call, outboxPath: SWARM_OUTBOX, defaultRepo: repo, repoRegistry });
14307
14468
  await client.hydrate();
14308
14469
  client.reconcileRuntime({ worktreesDir: effectiveWorktreesDir(), listProcesses: listDroneProcesses });
@@ -14559,6 +14720,12 @@ if (cmd === "start") {
14559
14720
  if (!foreground) {
14560
14721
  process.exit(await runStartDetached(startArgs));
14561
14722
  }
14723
+ try {
14724
+ rotateDaemonLog(DAEMON_LOG_PATH, { maxBytes: DAEMON_LOG_MAX_BYTES, keep: DAEMON_LOG_KEEP });
14725
+ if (teeStdioToFile(DAEMON_LOG_PATH)) console.log(`\u043B\u043E\u0433 \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u0438 \u0432 ${DAEMON_LOG_PATH}`);
14726
+ } catch (e) {
14727
+ console.warn(`\u043B\u043E\u0433 \u0432 \u0444\u0430\u0439\u043B \u043D\u0435 \u043F\u0438\u0448\u0435\u0442\u0441\u044F (${e instanceof Error ? e.message : e}) \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B`);
14728
+ }
14562
14729
  try {
14563
14730
  await runDaemon({ repo, repoFlag });
14564
14731
  } catch (e) {
@@ -14854,10 +15021,11 @@ switch (cmd) {
14854
15021
  // TASWM-592 (ревью): сессию ищет харнесс ПРОГОНА, а не глобальный исполнитель —
14855
15022
  // иначе побудка codex-прогона при мёртвом демоне теряла продолжаемую сессию.
14856
15023
  hasTranscript: (run2) => !!run2.worktree_path && executorForRun(run2, null).session.exists(run2.worktree_path),
14857
- respawn: (r, mode) => respawnRun(
15024
+ respawn: (r, mode, nudge) => respawnRun(
14858
15025
  { db, launch, repoFallback: SWARM_REPO, worktreesDir: effectiveWorktreesDir(), getTask: () => Promise.resolve(null) },
14859
15026
  r,
14860
- mode
15027
+ mode,
15028
+ nudge
14861
15029
  )
14862
15030
  },
14863
15031
  runId