@taskless-app/swarm 0.6.67 → 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 +202 -39
  2. package/dist/overmind.js +299 -42
  3. package/package.json +3 -2
package/dist/overmind.js CHANGED
@@ -4092,7 +4092,7 @@ var init_linkFailure = __esm({
4092
4092
  "use strict";
4093
4093
  init_agentToken();
4094
4094
  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";
4095
- 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;
4095
+ 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;
4096
4096
  MISSING_TOOL_RE = /tool[^.]{0,40}not found|unknown tool|tool_not_found|method not found|-32601\b/i;
4097
4097
  }
4098
4098
  });
@@ -6024,6 +6024,47 @@ var init_oauth = __esm({
6024
6024
  }
6025
6025
  });
6026
6026
 
6027
+ // src/prodHttp.ts
6028
+ import { Agent, fetch as undiciFetch } from "undici";
6029
+ function prodDispatcher() {
6030
+ agent ??= new Agent({
6031
+ keepAliveTimeout: PROD_KEEPALIVE_MS,
6032
+ keepAliveMaxTimeout: PROD_KEEPALIVE_MS,
6033
+ headersTimeout: PROD_SOCKET_TIMEOUT_MS,
6034
+ bodyTimeout: PROD_SOCKET_TIMEOUT_MS,
6035
+ connect: { timeout: PROD_CONNECT_TIMEOUT_MS }
6036
+ });
6037
+ return agent;
6038
+ }
6039
+ function isProdTimeout(e) {
6040
+ for (let cur = e, depth = 0; cur != null && depth < 4; cur = cur.cause, depth++) {
6041
+ const code = cur.code;
6042
+ const name = cur.name;
6043
+ const msg = cur instanceof Error ? cur.message : String(cur);
6044
+ if (code === -32001 || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT") return true;
6045
+ if (name === "TimeoutError" || /request timed out/i.test(msg)) return true;
6046
+ }
6047
+ return false;
6048
+ }
6049
+ var PROD_CALL_TIMEOUT_MS, FETCH_GRACE_MS, PROD_KEEPALIVE_MS, PROD_SOCKET_TIMEOUT_MS, PROD_CONNECT_TIMEOUT_MS, agent, prodFetch;
6050
+ var init_prodHttp = __esm({
6051
+ "src/prodHttp.ts"() {
6052
+ "use strict";
6053
+ PROD_CALL_TIMEOUT_MS = 15e3;
6054
+ FETCH_GRACE_MS = 5e3;
6055
+ PROD_KEEPALIVE_MS = 1e4;
6056
+ PROD_SOCKET_TIMEOUT_MS = 3e4;
6057
+ PROD_CONNECT_TIMEOUT_MS = 1e4;
6058
+ agent = null;
6059
+ prodFetch = (url, init) => {
6060
+ const method = (init?.method ?? "GET").toUpperCase();
6061
+ const deadline = method === "GET" ? null : AbortSignal.timeout(PROD_CALL_TIMEOUT_MS + FETCH_GRACE_MS);
6062
+ const signal = deadline && init?.signal ? AbortSignal.any([init.signal, deadline]) : deadline ?? init?.signal ?? void 0;
6063
+ return undiciFetch(url, { ...init, ...signal ? { signal } : {}, dispatcher: prodDispatcher() });
6064
+ };
6065
+ }
6066
+ });
6067
+
6027
6068
  // src/authState.ts
6028
6069
  function noteTokenRejected(nowMs = Date.now()) {
6029
6070
  if (!denial) {
@@ -6512,8 +6553,8 @@ function escapeRegExp(s) {
6512
6553
  }
6513
6554
  async function resolveApiToken(api, explicit) {
6514
6555
  if (explicit) return explicit;
6515
- const agent = loadAgentToken();
6516
- if (agent?.token) return agent.token;
6556
+ const agent2 = loadAgentToken();
6557
+ if (agent2?.token) return agent2.token;
6517
6558
  const store = loadStore();
6518
6559
  if (store) {
6519
6560
  const r = await ensureAccess(store, { api });
@@ -6538,9 +6579,9 @@ function resetLegacyTokenWarning() {
6538
6579
  async function preflightAuth(opts = {}) {
6539
6580
  const api = opts.api ?? TASKLESS_API;
6540
6581
  const out = opts.out ?? ((s) => console.error(s));
6541
- const agent = loadAgentToken();
6542
- if (!agent?.token) return true;
6543
- const probe = await probeAgentToken({ api, token: agent.token }, opts.fetchFn ?? fetch);
6582
+ const agent2 = loadAgentToken();
6583
+ if (!agent2?.token) return true;
6584
+ const probe = await probeAgentToken({ api, token: agent2.token }, opts.fetchFn ?? fetch);
6544
6585
  if (probe === "revoked") {
6545
6586
  out(noteTokenRejected() ?? revokedTokenComplaint());
6546
6587
  return false;
@@ -6560,7 +6601,8 @@ async function connectTaskless(cfg) {
6560
6601
  if (!token) return null;
6561
6602
  const url = new URL(mcpEndpoint(api));
6562
6603
  const transport = new StreamableHTTPClientTransport(url, {
6563
- requestInit: { headers: { Authorization: `Bearer ${token}` } }
6604
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
6605
+ fetch: prodFetch
6564
6606
  });
6565
6607
  const client = new Client({ name: "taskless-swarm", version: "0.1.0" }, { capabilities: {} });
6566
6608
  try {
@@ -6575,7 +6617,14 @@ async function connectTaskless(cfg) {
6575
6617
  return null;
6576
6618
  }
6577
6619
  noteTokenAccepted();
6578
- const call = async (name, args) => await client.callTool({ name, arguments: args });
6620
+ const call = async (name, args) => {
6621
+ try {
6622
+ return await client.callTool({ name, arguments: args }, void 0, { timeout: PROD_CALL_TIMEOUT_MS });
6623
+ } catch (e) {
6624
+ if (isProdTimeout(e)) console.warn(prodTimeoutLine("Taskless", name, PROD_CALL_TIMEOUT_MS));
6625
+ throw e;
6626
+ }
6627
+ };
6579
6628
  return makeTasklessClient(
6580
6629
  call,
6581
6630
  workspace || void 0,
@@ -6593,7 +6642,9 @@ async function connectSwarmMcp(cfg) {
6593
6642
  }
6594
6643
  const url = new URL(mcpEndpoint(api));
6595
6644
  const transport = new StreamableHTTPClientTransport(url, {
6596
- requestInit: { headers: { Authorization: `Bearer ${token}` } }
6645
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
6646
+ fetch: prodFetch
6647
+ // TASWM-611
6597
6648
  });
6598
6649
  const client = new Client({ name: "taskless-swarm-client", version: "0.1.0" }, { capabilities: {} });
6599
6650
  try {
@@ -6612,12 +6663,31 @@ async function connectSwarmMcp(cfg) {
6612
6663
  }
6613
6664
  swarmConnectFailure = null;
6614
6665
  noteTokenAccepted();
6615
- return async (name, args) => await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) });
6666
+ const timeout = cfg?.callTimeoutMs ?? PROD_CALL_TIMEOUT_MS;
6667
+ return async (name, args) => {
6668
+ try {
6669
+ return await client.callTool({ name, arguments: swarmToolArgs(name, args, workspace) }, void 0, { timeout });
6670
+ } catch (e) {
6671
+ if (isProdTimeout(e)) {
6672
+ 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`);
6673
+ }
6674
+ void client.close().catch(() => {
6675
+ });
6676
+ throw e;
6677
+ }
6678
+ };
6679
+ }
6680
+ function prodTimeoutLine(scope, tool, timeoutMs) {
6681
+ 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`;
6616
6682
  }
6617
6683
  function lastSwarmConnectFailure() {
6618
6684
  return swarmConnectFailure;
6619
6685
  }
6620
6686
  function swarmToolArgs(name, args, workspace) {
6687
+ if ("workspaceId" in args && args.workspaceId == null) {
6688
+ const { workspaceId: _drop, ...rest } = args;
6689
+ return rest;
6690
+ }
6621
6691
  if (!WS_SCOPED_SWARM_TOOLS.has(name) || !workspace) return args;
6622
6692
  return { workspaceId: workspace, ...args };
6623
6693
  }
@@ -6632,6 +6702,7 @@ var init_taskless = __esm({
6632
6702
  init_oauth();
6633
6703
  init_agentToken();
6634
6704
  init_linkFailure();
6705
+ init_prodHttp();
6635
6706
  init_toolFailure();
6636
6707
  init_authState();
6637
6708
  init_workspace();
@@ -10340,7 +10411,7 @@ var SwarmClient = class _SwarmClient {
10340
10411
  // 14.07/18.07, TASWM-201 — общий карантин; здесь — конкретный источник). Резолвим
10341
10412
  // слаг→cuid через get_task (кэш — taskIdCache) ПЕРЕД тем как слать create.
10342
10413
  taskIdCache = /* @__PURE__ */ new Map();
10343
- // task_slug → cuid прода
10414
+ // task_slug → cuid прода и воркспейс задачи
10344
10415
  // Create для этого runKey резолвился окончательно неудачно (задача не найдена/
10345
10416
  // недоступна) — событие/вопросы того же прогона тоже никогда не получат prod-id,
10346
10417
  // без карантина они бы бросали «нет prod-id» на каждом флаше и СНОВА вставали
@@ -10355,12 +10426,20 @@ var SwarmClient = class _SwarmClient {
10355
10426
  const cacheKey = `${taskRef}\0${repoFullName ?? ""}\0${workspaceId ?? ""}`;
10356
10427
  const cached = this.taskIdCache.get(cacheKey);
10357
10428
  if (cached) return cached;
10429
+ const first = await this.readTaskRef(taskRef, repoFullName, workspaceId);
10430
+ const res = first === null && repoFullName && !workspaceId ? await this.readTaskRef(taskRef, repoFullName, null) : first;
10431
+ if (res && res !== "transient") this.taskIdCache.set(cacheKey, res);
10432
+ return res;
10433
+ }
10434
+ /** Один get_task: cuid задачи и её воркспейс; null — прод ответил «нет такой». */
10435
+ async readTaskRef(taskRef, repoFullName, workspaceId) {
10358
10436
  let res;
10359
10437
  try {
10360
10438
  res = await this.call("get_task", {
10361
10439
  id: taskRef,
10362
10440
  ...repoFullName ? { repoFullName } : {},
10363
- ...workspaceId ? { workspaceId } : {}
10441
+ // null — «без пина демона» (см. swarmToolArgs), undefined — пин по умолчанию.
10442
+ ...workspaceId ? { workspaceId } : workspaceId === null ? { workspaceId: null } : {}
10364
10443
  });
10365
10444
  } catch {
10366
10445
  return "transient";
@@ -10370,10 +10449,9 @@ var SwarmClient = class _SwarmClient {
10370
10449
  const text = (res.content ?? []).map((c) => c.text ?? "").join(" ");
10371
10450
  return /not_found/i.test(text) ? null : "transient";
10372
10451
  }
10373
- const id = res.structuredContent?.task?.id;
10374
- if (!id) return null;
10375
- this.taskIdCache.set(cacheKey, id);
10376
- return id;
10452
+ const task = res.structuredContent?.task;
10453
+ if (!task?.id) return null;
10454
+ return { id: task.id, workspaceId: typeof task.workspaceId === "string" ? task.workspaceId : null };
10377
10455
  }
10378
10456
  // MCP-31: привести repo прогона к owner/repo-форме для подсказки резолверу
10379
10457
  // задачи. Прод сматчит подсказку с ProjectRepo.repoFullName → воркспейс. repo
@@ -10412,18 +10490,19 @@ var SwarmClient = class _SwarmClient {
10412
10490
  this.poisonedRunKeys.add(e.runKey);
10413
10491
  return "skip";
10414
10492
  }
10415
- const taskId = await this.resolveTaskCuid(e.taskRef, this.repoFullNameFor(fields.repo), e.workspaceId);
10416
- if (taskId === "transient") {
10493
+ const task = await this.resolveTaskCuid(e.taskRef, this.repoFullNameFor(fields.repo), e.workspaceId);
10494
+ if (task === "transient") {
10417
10495
  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`);
10418
10496
  }
10419
- if (!taskId) {
10497
+ if (!task) {
10420
10498
  console.error(
10421
10499
  `[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`
10422
10500
  );
10423
10501
  this.poisonedRunKeys.add(e.runKey);
10424
10502
  return "skip";
10425
10503
  }
10426
- return { tool: "swarm_run_upsert", args: { taskId, ...e.workspaceId ? { workspaceId: e.workspaceId } : {}, ...fields } };
10504
+ const workspaceId = e.workspaceId || task.workspaceId;
10505
+ return { tool: "swarm_run_upsert", args: { taskId: task.id, ...workspaceId ? { workspaceId } : {}, ...fields } };
10427
10506
  }
10428
10507
  if (this.poisonedRunKeys.has(e.runKey)) {
10429
10508
  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`);
@@ -10475,6 +10554,7 @@ var SwarmClient = class _SwarmClient {
10475
10554
  } catch {
10476
10555
  break;
10477
10556
  }
10557
+ if (res?.isError && res.unavailable) break;
10478
10558
  if (res?.isError) {
10479
10559
  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";
10480
10560
  const tries = (this.rejected.get(e.seq) ?? 0) + 1;
@@ -10597,8 +10677,39 @@ function tickPulse(health, now, intervalMs) {
10597
10677
  return { seq: health.seq, ageMs: tickAgeMs(health, now), intervalMs };
10598
10678
  }
10599
10679
 
10680
+ // src/tickSteps.ts
10681
+ var SLOW_PASS_MS = 3e4;
10682
+ function makeStepClock(now = Date.now) {
10683
+ const startedAt = now();
10684
+ const spent = /* @__PURE__ */ new Map();
10685
+ let current = "\u043D\u0430\u0447\u0430\u043B\u043E \u043F\u0440\u043E\u0445\u043E\u0434\u0430";
10686
+ let since = startedAt;
10687
+ const close = () => {
10688
+ const t = now();
10689
+ spent.set(current, (spent.get(current) ?? 0) + (t - since));
10690
+ since = t;
10691
+ };
10692
+ return {
10693
+ mark(step) {
10694
+ close();
10695
+ current = step;
10696
+ },
10697
+ finish() {
10698
+ close();
10699
+ const steps = [...spent].map(([step, ms]) => ({ step, ms })).sort((a, b) => b.ms - a.ms);
10700
+ return { totalMs: since - startedAt, steps };
10701
+ }
10702
+ };
10703
+ }
10704
+ function slowPassLine(seq, done, thresholdMs = SLOW_PASS_MS) {
10705
+ if (done.totalMs <= thresholdMs) return null;
10706
+ const sec = (ms) => `${Math.round(ms / 100) / 10}\u0441`;
10707
+ const top = done.steps.slice(0, 3).map((s) => `${s.step} ${sec(s.ms)}`).join(", ");
10708
+ return `[overmind] \u23F1 \u043F\u0440\u043E\u0445\u043E\u0434 \u0442\u0438\u043A\u0430 #${seq} \u0434\u043B\u0438\u043B\u0441\u044F ${sec(done.totalMs)} (\u043F\u043E\u0440\u043E\u0433 ${sec(thresholdMs)}); \u0434\u043E\u043B\u044C\u0448\u0435 \u0432\u0441\u0435\u0433\u043E: ${top}`;
10709
+ }
10710
+
10600
10711
  // src/logRotate.ts
10601
- import { copyFileSync as copyFileSync4, renameSync as renameSync5, statSync as statSync7, fstatSync, truncateSync, unlinkSync as unlinkSync3, existsSync as existsSync9 } from "fs";
10712
+ import { copyFileSync as copyFileSync4, renameSync as renameSync5, statSync as statSync7, fstatSync, truncateSync, unlinkSync as unlinkSync3, existsSync as existsSync9, openSync as openSync2, writeSync, closeSync as closeSync2 } from "fs";
10602
10713
  var realIo = {
10603
10714
  size: (path) => {
10604
10715
  try {
@@ -10632,6 +10743,7 @@ var realOwnerIo = {
10632
10743
  }
10633
10744
  };
10634
10745
  function stdoutGoesTo(path, io = realOwnerIo) {
10746
+ if (teedTo === path) return true;
10635
10747
  const out = io.statFd(1);
10636
10748
  if (!out || !out.isFile) return false;
10637
10749
  const file = io.statPath(path);
@@ -10656,13 +10768,14 @@ function rotateDaemonLog(path, opts, io = realIo) {
10656
10768
  io.truncate(path);
10657
10769
  return "rotated";
10658
10770
  }
10771
+ var teedTo = null;
10659
10772
 
10660
10773
  // src/daemonLaunch.ts
10661
- import { openSync as openSync2, closeSync as closeSync2, readFileSync as readFileSync9 } from "fs";
10774
+ import { openSync as openSync3, closeSync as closeSync3, readFileSync as readFileSync9 } from "fs";
10662
10775
  import { spawn } from "child_process";
10663
10776
  var realRespawnIo = {
10664
10777
  isTty: () => process.stdout.isTTY === true,
10665
- open: (path) => openSync2(path, "a")
10778
+ open: (path) => openSync3(path, "a")
10666
10779
  };
10667
10780
  function respawnStdio(logPath, io = realRespawnIo) {
10668
10781
  if (io.isTty()) return "inherit";
@@ -11464,7 +11577,7 @@ function buildReliefCostCandidates(cost, t, sustainMs, build) {
11464
11577
 
11465
11578
  // src/launcher.ts
11466
11579
  import { spawn as spawn2 } from "child_process";
11467
- import { closeSync as closeSync3, existsSync as existsSync12, mkdirSync as mkdirSync9, openSync as openSync3, readdirSync as readdirSync10 } from "fs";
11580
+ import { closeSync as closeSync4, existsSync as existsSync12, mkdirSync as mkdirSync9, openSync as openSync4, readdirSync as readdirSync10 } from "fs";
11468
11581
  import { createRequire } from "module";
11469
11582
  import { homedir as homedir6 } from "os";
11470
11583
  import { dirname as dirname3, join as join18 } from "path";
@@ -11673,14 +11786,14 @@ function openNextRunLog(runLogsDir, runLogKeys) {
11673
11786
  const path = nextRunLogPath(runLogsDir, runLogKeys);
11674
11787
  let out;
11675
11788
  try {
11676
- out = openSync3(path, "wx");
11789
+ out = openSync4(path, "wx");
11677
11790
  } catch (e) {
11678
11791
  if (e.code !== "EEXIST") throw e;
11679
11792
  continue;
11680
11793
  }
11681
11794
  let err = null;
11682
11795
  try {
11683
- err = openSync3(`${path.slice(0, -".json".length)}.err.log`, "w");
11796
+ err = openSync4(`${path.slice(0, -".json".length)}.err.log`, "w");
11684
11797
  } catch {
11685
11798
  err = null;
11686
11799
  }
@@ -11810,8 +11923,8 @@ function makeLauncher(cfg) {
11810
11923
  // они про его собственное состояние, и перебивать их ройным нечем.
11811
11924
  env: { ...droneEnv(worktree, process.env, home ? { droneHome: home, realHome } : void 0, cfg.droneCap?.() ?? null), ...plan.env }
11812
11925
  });
11813
- if (outFd !== void 0) closeSync3(outFd);
11814
- if (errFd !== void 0) closeSync3(errFd);
11926
+ if (outFd !== void 0) closeSync4(outFd);
11927
+ if (errFd !== void 0) closeSync4(errFd);
11815
11928
  child.unref();
11816
11929
  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)");
11817
11930
  return child.pid;
@@ -12092,7 +12205,7 @@ import { execa } from "execa";
12092
12205
 
12093
12206
  // src/prepareLock.ts
12094
12207
  import { createHash } from "crypto";
12095
- import { closeSync as closeSync4, openSync as openSync4, rmSync as rmSync2, statSync as statSync12, writeSync } from "fs";
12208
+ import { closeSync as closeSync5, openSync as openSync5, rmSync as rmSync2, statSync as statSync12, writeSync as writeSync2 } from "fs";
12096
12209
  import { join as join23 } from "path";
12097
12210
  import { tmpdir } from "os";
12098
12211
  var WAIT_TIMEOUT_MS = 5 * 6e4;
@@ -12103,11 +12216,11 @@ function machineLockPath(key, dir = tmpdir()) {
12103
12216
  }
12104
12217
  function defaultTryCreate(path, content) {
12105
12218
  try {
12106
- const fd = openSync4(path, "wx");
12219
+ const fd = openSync5(path, "wx");
12107
12220
  try {
12108
- writeSync(fd, content);
12221
+ writeSync2(fd, content);
12109
12222
  } finally {
12110
- closeSync4(fd);
12223
+ closeSync5(fd);
12111
12224
  }
12112
12225
  return true;
12113
12226
  } catch {
@@ -12846,6 +12959,18 @@ var FileOverlapError = class extends SpawnDeferredError {
12846
12959
  }
12847
12960
  blockedByRunId;
12848
12961
  };
12962
+ var SpawnAbandonedError = class extends Error {
12963
+ constructor(status) {
12964
+ super(`\u043F\u0440\u043E\u0433\u043E\u043D \u0441\u043D\u044F\u0442 (${status}), \u043F\u043E\u043A\u0430 \u0433\u043E\u0442\u043E\u0432\u0438\u043B\u0430\u0441\u044C \u0440\u0430\u0431\u043E\u0447\u0430\u044F \u043A\u043E\u043F\u0438\u044F \u2014 \u0434\u0440\u043E\u043D\u0430 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E`);
12965
+ this.status = status;
12966
+ this.name = "SpawnAbandonedError";
12967
+ }
12968
+ status;
12969
+ };
12970
+ function assertStillWanted(db, runId) {
12971
+ const fresh = db.getRun(runId);
12972
+ if (fresh && isTerminalStatus(fresh.status)) throw new SpawnAbandonedError(fresh.status);
12973
+ }
12849
12974
  function healTaskSlug(db, run2, task) {
12850
12975
  const slug = task?.slug;
12851
12976
  if (!slug || slug === run2.task_slug || !isTaskSlug(slug) || isTaskSlug(run2.task_slug)) return run2;
@@ -12863,6 +12988,7 @@ async function spawnDrone(deps, run2) {
12863
12988
  if (run2.area && deps.db.runningAreas("").includes(run2.area)) throw new AreaBusyError(run2.area);
12864
12989
  deps.db.addEvent(run2.id, "step", `repoless-\u043F\u0440\u043E\u0433\u043E\u043D: \u0441\u043A\u0440\u0430\u0442\u0447-\u043A\u0430\u0442\u0430\u043B\u043E\u0433 ${workdir} (\u0431\u0435\u0437 worktree/\u0432\u0435\u0442\u043A\u0438/PR \u2014 TASWM-595)`);
12865
12990
  const built2 = await deps.buildPrompt(run2, workdir);
12991
+ assertStillWanted(deps.db, run2.id);
12866
12992
  const pid2 = deps.launch({ worktree: workdir, run: run2, prompt: built2 });
12867
12993
  deps.db.addEvent(run2.id, "step", `\u0434\u0440\u043E\u043D \u0437\u0430\u043F\u0443\u0449\u0435\u043D \u0432 ${workdir} (pid ${pid2})`);
12868
12994
  return deps.db.updateRun(run2.id, {
@@ -12898,6 +13024,7 @@ async function spawnDrone(deps, run2) {
12898
13024
  const prompt = notice ? `${built}
12899
13025
 
12900
13026
  ${notice}` : built;
13027
+ assertStillWanted(deps.db, run2.id);
12901
13028
  const pid = deps.launch({ worktree, run: run2, prompt });
12902
13029
  deps.db.addEvent(run2.id, "step", `\u0434\u0440\u043E\u043D \u0437\u0430\u043F\u0443\u0449\u0435\u043D \u0432 ${worktree} (pid ${pid})`);
12903
13030
  return deps.db.updateRun(run2.id, {
@@ -13074,7 +13201,7 @@ function parkReasonUnretriable(reason) {
13074
13201
  // package.json
13075
13202
  var package_default = {
13076
13203
  name: "@taskless-app/swarm",
13077
- version: "0.6.67",
13204
+ version: "0.6.68",
13078
13205
  type: "module",
13079
13206
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13080
13207
  keywords: [
@@ -13123,9 +13250,10 @@ var package_default = {
13123
13250
  "@hono/node-server": "^2.0.12",
13124
13251
  "@modelcontextprotocol/sdk": "^1.29.0",
13125
13252
  execa: "^10.0.0",
13253
+ undici: "^7.25.0",
13126
13254
  zod: "3.25.76"
13127
13255
  },
13128
- gitHead: "92a3176867bc1668677a4b6a25e02e0cb6661ce0"
13256
+ gitHead: "4bfd78926aa37ef7de8ea06a6763d7e84a2fded6"
13129
13257
  };
13130
13258
 
13131
13259
  // src/version.ts
@@ -15798,11 +15926,17 @@ init_procList();
15798
15926
  // src/mcpResilient.ts
15799
15927
  init_linkFailure();
15800
15928
  init_taskless();
15929
+ init_prodHttp();
15801
15930
  var DEFAULT_BACKOFF_MS = [5e3, 15e3, 3e4, 6e4];
15802
15931
  var DEFAULT_DOWN_AFTER_MS = 5 * 6e4;
15803
15932
  var DEFAULT_RETRY_DELAYS_MS = [200, 800];
15804
- function errorResult(reason) {
15805
- return { isError: true, content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }] };
15933
+ var PROD_PASS_BUDGET_MS = 2e4;
15934
+ function errorResult(reason, unavailable) {
15935
+ return {
15936
+ isError: true,
15937
+ ...unavailable ? { unavailable: true } : {},
15938
+ content: [{ type: "text", text: `Swarm MCP \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${reason}` }]
15939
+ };
15806
15940
  }
15807
15941
  function resilientSwarmMcp(opts = {}) {
15808
15942
  const connect = opts.connect ?? (() => connectSwarmMcp());
@@ -15810,6 +15944,13 @@ function resilientSwarmMcp(opts = {}) {
15810
15944
  const downAfterMs = opts.downAfterMs ?? DEFAULT_DOWN_AFTER_MS;
15811
15945
  const retryDelays = opts.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
15812
15946
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15947
+ const clock = opts.now ?? Date.now;
15948
+ const passBudgetMs = opts.passBudgetMs ?? PROD_PASS_BUDGET_MS;
15949
+ let passSpentMs = null;
15950
+ let budgetNoted = false;
15951
+ const spend = (since) => {
15952
+ if (passSpentMs !== null) passSpentMs += Math.max(0, clock() - since);
15953
+ };
15813
15954
  let caller = null;
15814
15955
  let lastTryAt = 0;
15815
15956
  let failures = 0;
@@ -15832,6 +15973,7 @@ function resilientSwarmMcp(opts = {}) {
15832
15973
  if (!force && lastTryAt !== 0 && now - lastTryAt < wait) return null;
15833
15974
  if (!force) lastTryAt = now;
15834
15975
  let fresh = null;
15976
+ const connectStart = clock();
15835
15977
  try {
15836
15978
  fresh = await connect();
15837
15979
  if (!fresh) lastConnectReason = lastSwarmConnectFailure() ?? UNKNOWN_LINK_REASON;
@@ -15848,17 +15990,30 @@ function resilientSwarmMcp(opts = {}) {
15848
15990
  downReported = false;
15849
15991
  return caller;
15850
15992
  }
15993
+ spend(connectStart);
15851
15994
  if (!force) failures += 1;
15852
15995
  if (downSince === null) downSince = now;
15853
15996
  return null;
15854
15997
  }
15855
15998
  const call = async (name, args) => {
15856
15999
  for (let attempt = 0; ; attempt++) {
16000
+ if (passSpentMs !== null && passSpentMs >= passBudgetMs) {
16001
+ if (!budgetNoted) {
16002
+ budgetNoted = true;
16003
+ opts.onBudgetSpent?.({ spentMs: passSpentMs, budgetMs: passBudgetMs });
16004
+ }
16005
+ maybeReportDown();
16006
+ return errorResult(
16007
+ `\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})` : ""}`,
16008
+ true
16009
+ );
16010
+ }
15857
16011
  const active = await ensureCaller(attempt > 0);
15858
16012
  if (!active) {
15859
16013
  maybeReportDown();
15860
- return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON);
16014
+ return errorResult(lastConnectReason ?? UNKNOWN_LINK_REASON, true);
15861
16015
  }
16016
+ const callStart = clock();
15862
16017
  try {
15863
16018
  const res = await active(name, args);
15864
16019
  lastOkAt = Date.now();
@@ -15866,13 +16021,14 @@ function resilientSwarmMcp(opts = {}) {
15866
16021
  return res;
15867
16022
  } catch (e) {
15868
16023
  caller = null;
16024
+ spend(callStart);
15869
16025
  if (downSince === null) downSince = Date.now();
15870
16026
  const { kind, detail: reason } = explainLinkFailure(e);
15871
16027
  lastCallReason = reason;
15872
16028
  const delay = retryDelays[attempt];
15873
- if (delay === void 0 || kind !== "offline") {
16029
+ if (delay === void 0 || kind !== "offline" || isProdTimeout(e)) {
15874
16030
  maybeReportDown();
15875
- return errorResult(reason);
16031
+ return errorResult(reason, kind === "offline" || kind === "unknown" || kind === "unauthorized" || isProdTimeout(e));
15876
16032
  }
15877
16033
  opts.onRetry?.({ tool: name, attempt: attempt + 1, reason });
15878
16034
  await sleep2(delay);
@@ -15880,6 +16036,10 @@ function resilientSwarmMcp(opts = {}) {
15880
16036
  }
15881
16037
  };
15882
16038
  return Object.assign(call, {
16039
+ beginPass: () => {
16040
+ passSpentMs = 0;
16041
+ budgetNoted = false;
16042
+ },
15883
16043
  // Причина — ТА ЖЕ, что уехала в лог: сначала свежий отказ вызова, иначе отказ
15884
16044
  // подключения. Своего разбора здесь нет намеренно (см. lastCallReason выше).
15885
16045
  link: () => ({ connected: caller !== null, downSince, lastOkAt, lastError: caller !== null ? null : lastCallReason ?? lastConnectReason })
@@ -19470,8 +19630,19 @@ function countInFlight(db) {
19470
19630
  return db.listRuns().filter((r) => r.kind !== "hero" && IN_FLIGHT_STATUSES.has(r.status)).length;
19471
19631
  }
19472
19632
  function countActiveDrones(db) {
19473
- return db.listRuns().filter((r) => r.kind !== "hero" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
19633
+ const preparing = preparingRuns(db);
19634
+ return db.listRuns().filter((r) => r.kind !== "hero" && (ACTIVE_DRONE_STATUSES.has(r.status) || preparing.has(r.id))).length;
19635
+ }
19636
+ var preparingByStore = /* @__PURE__ */ new WeakMap();
19637
+ function preparingRuns(db) {
19638
+ let map = preparingByStore.get(db);
19639
+ if (!map) preparingByStore.set(db, map = /* @__PURE__ */ new Map());
19640
+ return map;
19474
19641
  }
19642
+ function hasPreparingRuns(db) {
19643
+ return preparingRuns(db).size > 0;
19644
+ }
19645
+ var SPAWN_INLINE_WAIT_MS = 2e3;
19475
19646
  function countActiveReviewers(db) {
19476
19647
  return db.listRuns().filter((r) => r.kind === "reviewer" && ACTIVE_DRONE_STATUSES.has(r.status)).length;
19477
19648
  }
@@ -21092,6 +21263,7 @@ function noteStormStall(deps) {
21092
21263
  for (const run2 of waiting) deferSpawn(deps, run2, reason, { quiet: true });
21093
21264
  }
21094
21265
  async function trySpawnQueuedRun(deps, run2) {
21266
+ if (preparingRuns(deps.db).has(run2.id)) return "skipped";
21095
21267
  if (deps.control?.paused) {
21096
21268
  const why = deps.control.state?.reason;
21097
21269
  return deferSpawn(deps, run2, `\u043E\u043F\u0435\u0440\u0430\u0442\u043E\u0440 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B \u043F\u0440\u0438\u0451\u043C \u043D\u043E\u0432\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B \u043D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435${why ? ` \u2014 ${why}` : ""} (\u043F\u0430\u0443\u0437\u0430)`, {
@@ -21186,12 +21358,55 @@ async function trySpawnQueuedRun(deps, run2) {
21186
21358
  await fillSuggestedArea(deps, run2);
21187
21359
  const repoGap = unresolvedRepoReason(run2.repo, deps.reposServed ? deps.reposServed() : []);
21188
21360
  if (repoGap) return await failBeforeStart(deps, run2, repoGap);
21361
+ return await spawnOrPrepareInBackground(deps, run2);
21362
+ }
21363
+ async function spawnOrPrepareInBackground(deps, run2) {
21364
+ const preparing = preparingRuns(deps.db);
21365
+ const repoKey = normalizeRepo(run2.repo || deps.repoFallback || "");
21366
+ for (const [otherId, otherRepo] of preparing) {
21367
+ if (otherId !== run2.id && otherRepo === repoKey) {
21368
+ return deferSpawn(deps, run2, `\u0432 \u044D\u0442\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0435\u0449\u0451 \u0433\u043E\u0442\u043E\u0432\u0438\u0442\u0441\u044F \u0440\u0430\u0431\u043E\u0447\u0430\u044F \u043A\u043E\u043F\u0438\u044F \u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${otherId} \u2014 \u0436\u0434\u0443 \u0435\u0451, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u0434\u0432\u0435 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438 \u0440\u0430\u0437\u043E\u043C (TASWM-611)`);
21369
+ }
21370
+ }
21371
+ preparing.set(run2.id, repoKey);
21372
+ const attempt = spawnQueuedRun(deps, run2);
21373
+ const release = () => {
21374
+ preparing.delete(run2.id);
21375
+ };
21376
+ void attempt.then(release, release);
21377
+ let timer;
21378
+ const background = /* @__PURE__ */ Symbol("background");
21379
+ const waited = new Promise((resolve5) => {
21380
+ timer = setTimeout(() => resolve5(background), SPAWN_INLINE_WAIT_MS);
21381
+ });
21382
+ try {
21383
+ const first = await Promise.race([attempt, waited]);
21384
+ if (first !== background) return first;
21385
+ } finally {
21386
+ clearTimeout(timer);
21387
+ }
21388
+ deps.db.addEvent(run2.id, "step", "\u0433\u043E\u0442\u043E\u0432\u0438\u0442\u0441\u044F: \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u0440\u0430\u0431\u043E\u0447\u0435\u0439 \u043A\u043E\u043F\u0438\u0438 \u0438\u0434\u0451\u0442 \u0444\u043E\u043D\u043E\u043C, \u0442\u0438\u043A \u0435\u0451 \u043D\u0435 \u0436\u0434\u0451\u0442 (TASWM-611)");
21389
+ console.log(`[overmind] ${droneLabel(run2)} (${run2.task_slug}) \u0433\u043E\u0442\u043E\u0432\u0438\u0442\u0441\u044F \u0444\u043E\u043D\u043E\u043C \u2014 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 \u0434\u043E\u043B\u044C\u0448\u0435 ${SPAWN_INLINE_WAIT_MS / 1e3}\u0441, \u0442\u0438\u043A \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`);
21390
+ void attempt.then(
21391
+ (outcome) => {
21392
+ if (outcome !== "spawned") console.warn(`[overmind] \u0444\u043E\u043D\u043E\u0432\u0430\u044F \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 ${run2.task_slug} \u043A\u043E\u043D\u0447\u0438\u043B\u0430\u0441\u044C \u0431\u0435\u0437 \u0434\u0440\u043E\u043D\u0430: ${outcome}`);
21393
+ },
21394
+ (e) => console.error(`[overmind] \u0444\u043E\u043D\u043E\u0432\u0430\u044F \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0430 ${run2.task_slug} \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C: ${describeTickError(e)}`)
21395
+ );
21396
+ return "spawned";
21397
+ }
21398
+ async function spawnQueuedRun(deps, run2) {
21189
21399
  try {
21190
21400
  await deps.spawn(run2);
21191
21401
  if (deps.spawnDefer) clearDefer(deps.spawnDefer, `run:${run2.id}`);
21192
21402
  narrate(deps, `\u0411\u0435\u0440\u0443 ${run2.task_slug} \u0438\u0437 \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u2014 \u0441\u043F\u0430\u0432\u043D\u044E ${droneLabel(run2)}.`);
21193
21403
  return "spawned";
21194
21404
  } catch (e) {
21405
+ if (e instanceof SpawnAbandonedError) {
21406
+ deps.db.addEvent(run2.id, "step", e.message);
21407
+ console.log(`[overmind] ${run2.task_slug}: ${e.message}`);
21408
+ return "closed";
21409
+ }
21195
21410
  if (e instanceof FileOverlapError) {
21196
21411
  const logged = deps.overlapLogged ??= /* @__PURE__ */ new Map();
21197
21412
  if (logged.get(run2.id) !== e.message) {
@@ -22165,7 +22380,9 @@ async function tickOnce(deps) {
22165
22380
  const now = deps.now ? deps.now() : Date.now();
22166
22381
  const worktreeExists = deps.worktreeExists ?? existsSync22;
22167
22382
  const hasTranscript = deps.hasTranscript ?? executorHasSession;
22383
+ deps.stepClock?.mark("0 \u0437\u0430\u043C\u0435\u0440 \u0445\u043E\u0441\u0442\u0430");
22168
22384
  deps.sampleHost?.();
22385
+ deps.stepClock?.mark("0a re-hydrate \u0437\u0435\u0440\u043A\u0430\u043B\u0430");
22169
22386
  if (deps.rehydrate) {
22170
22387
  const rh = deps.rehydrate;
22171
22388
  const sinceLast = rh.lastAt == null ? Infinity : now - rh.lastAt;
@@ -22178,6 +22395,7 @@ async function tickOnce(deps) {
22178
22395
  }
22179
22396
  }
22180
22397
  }
22398
+ deps.stepClock?.mark("0a1 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u043E\u0447\u0435\u0440\u0435\u0434\u0438");
22181
22399
  if (deps.db.refreshDeps) {
22182
22400
  const waiting = deps.db.listRuns("queued").flatMap((r) => r.depends_on ?? []);
22183
22401
  if (waiting.length) {
@@ -22188,6 +22406,7 @@ async function tickOnce(deps) {
22188
22406
  }
22189
22407
  }
22190
22408
  }
22409
+ deps.stepClock?.mark("0a2b \u043C\u043E\u0441\u0442 \xABsqlite \u0434\u0440\u043E\u043D\u0430 \u2192 \u0437\u0435\u0440\u043A\u0430\u043B\u043E \u2192 \u043F\u0440\u043E\u0434\xBB");
22191
22410
  if (deps.droneBridge) {
22192
22411
  const br = deps.droneBridge;
22193
22412
  const sinceLast = br.lastAt == null ? Infinity : now - br.lastAt;
@@ -22200,6 +22419,7 @@ async function tickOnce(deps) {
22200
22419
  }
22201
22420
  }
22202
22421
  }
22422
+ deps.stepClock?.mark("0a3 \u0441\u0432\u0435\u0440\u043A\u0430 \u043A\u043E\u0434\u0430 \u0434\u0435\u043C\u043E\u043D\u0430 \u0441 main");
22203
22423
  if (deps.daemonVersion) {
22204
22424
  const dv = deps.daemonVersion;
22205
22425
  const sinceLast = dv.lastAt == null ? Infinity : now - dv.lastAt;
@@ -22208,6 +22428,7 @@ async function tickOnce(deps) {
22208
22428
  await dv.run();
22209
22429
  }
22210
22430
  }
22431
+ deps.stepClock?.mark("0a4 \u043A\u0430\u0440\u0442\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432 \u043C\u0430\u0448\u0438\u043D\u044B");
22211
22432
  if (deps.repoMap) {
22212
22433
  const rm4 = deps.repoMap;
22213
22434
  const sinceLast = rm4.lastAt == null ? Infinity : now - rm4.lastAt;
@@ -22220,6 +22441,7 @@ async function tickOnce(deps) {
22220
22441
  }
22221
22442
  }
22222
22443
  }
22444
+ deps.stepClock?.mark("0b reap \u0432\u043D\u0435\u0448\u043D\u0435-\u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D\u043D\u044B\u0445");
22223
22445
  if (deps.rehydrate && deps.kill) {
22224
22446
  const kill = deps.kill;
22225
22447
  for (const run2 of deps.db.listRuns()) {
@@ -22233,6 +22455,7 @@ async function tickOnce(deps) {
22233
22455
  });
22234
22456
  }
22235
22457
  }
22458
+ deps.stepClock?.mark("0c \u0437\u0430\u0449\u0438\u0442\u043D\u044B\u0439 sweep \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0445 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432/doc");
22236
22459
  if (deps.sweep) {
22237
22460
  const sw = deps.sweep;
22238
22461
  const sinceLast = sw.lastAt == null ? Infinity : now - sw.lastAt;
@@ -22245,6 +22468,7 @@ async function tickOnce(deps) {
22245
22468
  }
22246
22469
  }
22247
22470
  }
22471
+ deps.stepClock?.mark("0c2 \u0436\u043D\u0435\u0446 worktree");
22248
22472
  if (deps.reapWorktrees) {
22249
22473
  const rw = deps.reapWorktrees;
22250
22474
  const sinceLast = rw.lastAt == null ? Infinity : now - rw.lastAt;
@@ -22257,6 +22481,7 @@ async function tickOnce(deps) {
22257
22481
  }
22258
22482
  }
22259
22483
  }
22484
+ deps.stepClock?.mark("0d \u0445\u043E\u0437\u044F\u0439\u0441\u0442\u0432\u043E \u0434\u043E\u043B\u0433\u043E\u0436\u0438\u0432\u0443\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430");
22260
22485
  if (deps.logRotate) {
22261
22486
  const lr = deps.logRotate;
22262
22487
  const sinceLast = lr.lastAt == null ? Infinity : now - lr.lastAt;
@@ -22270,6 +22495,7 @@ async function tickOnce(deps) {
22270
22495
  }
22271
22496
  }
22272
22497
  pruneGateLogs(deps);
22498
+ deps.stepClock?.mark("0 \u0448\u0442\u043E\u0440\u043C");
22273
22499
  let stormActive = false;
22274
22500
  if (deps.storm) {
22275
22501
  for (const run2 of deps.db.listRuns("running")) {
@@ -22287,7 +22513,9 @@ async function tickOnce(deps) {
22287
22513
  stormActive = await manageStorm(deps, now, worktreeExists, hasTranscript);
22288
22514
  }
22289
22515
  const droneCap = deps.maxDrones ? await deps.maxDrones() : Infinity;
22516
+ deps.stepClock?.mark("1 \u043F\u043E\u0434\u043D\u044F\u0442\u044C \u043E\u0447\u0435\u0440\u0435\u0434\u044C");
22290
22517
  if (stormActive) noteStormStall(deps);
22518
+ deps.stepClock?.mark("0b \u0432\u0435\u0441 \u0437\u0430\u0434\u0430\u0447\u0438");
22291
22519
  try {
22292
22520
  await manageWeight(deps);
22293
22521
  } catch (e) {
@@ -22298,14 +22526,17 @@ async function tickOnce(deps) {
22298
22526
  } else if (!stormActive) {
22299
22527
  await batchSpawnQueue(deps, droneCap, now);
22300
22528
  }
22529
+ deps.stepClock?.mark("1b \u0432\u043E\u0437\u0432\u0440\u0430\u0442 \u0437\u0430\u043F\u0430\u0440\u043A\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0441\u0442\u043E\u0440\u043E\u0436\u0435\u043C \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430");
22301
22530
  if (!stormActive) {
22302
22531
  await resumeWatchdogParked(deps, droneCap);
22303
22532
  }
22533
+ deps.stepClock?.mark("1c \u043F\u043E\u0432\u0442\u043E\u0440 \u043E\u0442\u043B\u043E\u0436\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043F\u0430\u0432\u043D\u0430");
22304
22534
  try {
22305
22535
  await retryDeferredSpawns(deps, now, droneCap, stormActive);
22306
22536
  } catch (e) {
22307
22537
  console.error("[overmind] \u043F\u043E\u0432\u0442\u043E\u0440 \u043E\u0442\u043B\u043E\u0436\u0435\u043D\u043D\u043E\u0433\u043E \u0441\u043F\u0430\u0432\u043D\u0430 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
22308
22538
  }
22539
+ deps.stepClock?.mark("2 review \u2192 \u0441\u043C\u0435\u0440\u0436\u0435\u043D? \u0437\u0430\u043A\u0440\u044B\u0442\u044C");
22309
22540
  for (let run2 of deps.db.listRuns("review")) {
22310
22541
  try {
22311
22542
  if (run2.resolves_run_id != null) {
@@ -22555,12 +22786,19 @@ async function tickOnce(deps) {
22555
22786
  console.error(`[overmind] \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B review-\u043F\u0440\u043E\u0433\u043E\u043D\u0430 #${run2.id} (${run2.task_slug}) \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:`, e);
22556
22787
  }
22557
22788
  }
22789
+ deps.stepClock?.mark("2b \u0434\u0443\u0431\u043B\u0438-\u0440\u0430\u043D\u044B \u0437\u0430\u0434\u0430\u0447\u0438");
22558
22790
  await closeMergedTaskRuns(deps);
22791
+ deps.stepClock?.mark("2b1 \u0430\u0432\u0442\u043E\u043F\u043E\u0434\u044A\u0451\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E");
22559
22792
  await rescueReviewlessPrs(deps, stormActive, now);
22793
+ deps.stepClock?.mark("2b1a \u0440\u0435\u0432\u044C\u044E\u0435\u0440 \u0431\u0435\u0437 \u043F\u0440\u0438\u0432\u044F\u0437\u043A\u0438");
22560
22794
  relinkUnboundReviewers(deps);
22795
+ deps.stepClock?.mark("2b2 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u044B-\u0441\u0438\u0440\u043E\u0442\u044B");
22561
22796
  await closeOrphanedReviewers(deps);
22797
+ deps.stepClock?.mark("2b3 \u0432\u0435\u0440\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F");
22562
22798
  await spawnVerifiersForOpenPrs(deps, stormActive);
22799
+ deps.stepClock?.mark("2c \u0432\u044B\u0447\u0438\u0441\u043B\u044F\u0435\u043C\u0430\u044F \u0444\u0430\u0437\u0430 \u0440\u0435\u0432\u044C\u044E");
22563
22800
  await refreshReviewPhases(deps);
22801
+ deps.stepClock?.mark("3 liveness/stall/\u0430\u0432\u0442\u043E-\u043E\u0436\u0438\u0432\u043B\u0435\u043D\u0438\u0435");
22564
22802
  let liveChildPpids;
22565
22803
  try {
22566
22804
  liveChildPpids = deps.livePpids?.() ?? null;
@@ -22820,6 +23058,7 @@ async function tickOnce(deps) {
22820
23058
  });
22821
23059
  }
22822
23060
  }
23061
+ deps.stepClock?.mark("3b \u043F\u043E\u043B \u0441\u043D\u0438\u0437\u0443");
22823
23062
  for (const run2 of deps.db.listRuns("paused")) {
22824
23063
  if (run2.kind === "hero") continue;
22825
23064
  if (run2.stop_reason === AWAITING_CI_REASON || isReliefPause(run2.stop_reason)) continue;
@@ -22834,7 +23073,9 @@ async function tickOnce(deps) {
22834
23073
  deps.db.addEvent(run2.id, "stalled", reason);
22835
23074
  narrate(deps, `${droneLabel(run2)} \u0432\u0438\u0441\u0435\u043B paused \u0431\u0435\u0437 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F ${Math.round(STALL_FLOOR_MS / 6e4)} \u043C\u0438\u043D \u2014 \u043F\u043E\u043C\u0435\u0447\u0430\u044E \u0437\u0430\u0432\u0438\u0441\u0448\u0438\u043C (TASWM-275).`);
22836
23075
  }
23076
+ deps.stepClock?.mark("3c \u043F\u0440\u0435\u0434\u0435\u043B \u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F ci");
22837
23077
  await escalateStaleCiWaits(deps, now);
23078
+ deps.stepClock?.mark("4 \u043F\u0440\u043E\u0431\u0443\u0436\u0434\u0435\u043D\u0438\u0435 \u0437\u0430\u043C\u0435\u0440\u0448\u0438\u0445");
22838
23079
  if (deps.respawn && !stormActive) {
22839
23080
  for (const run2 of deps.db.listRuns("paused")) {
22840
23081
  if (run2.kind === "hero") continue;
@@ -22864,9 +23105,11 @@ async function tickOnce(deps) {
22864
23105
  }
22865
23106
  }
22866
23107
  }
23108
+ deps.stepClock?.mark("4c \u0433\u0435\u0439\u0442-\u0443\u0432\u0430\u0436\u0430\u044E\u0449\u0438\u0439 \u043F\u043E\u0434\u044A\u0451\u043C \u043E\u0441\u0442\u0430\u0432\u0448\u0438\u0445\u0441\u044F storm_d");
22867
23109
  if (deps.respawn && deps.resourcesOk && !stormActive) {
22868
23110
  await resurrectStormDead(deps, worktreeExists, hasTranscript, { gateOpen: deps.resourcesOk(), cap: droneCap });
22869
23111
  }
23112
+ deps.stepClock?.mark("4d \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B");
22870
23113
  try {
22871
23114
  const reaped = reapOrphanedQuestions(deps.db, now);
22872
23115
  if (reaped.withdrawn > 0) {
@@ -22877,11 +23120,13 @@ async function tickOnce(deps) {
22877
23120
  } catch (e) {
22878
23121
  console.error("[overmind] \u0443\u0431\u043E\u0440\u043A\u0430 \u043E\u0441\u0438\u0440\u043E\u0442\u0435\u0432\u0448\u0438\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u0441\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C:", e);
22879
23122
  }
23123
+ deps.stepClock?.mark("4e \u043E\u0442\u0432\u0435\u0442 \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 \u043E \u0431\u044E\u0434\u0436\u0435\u0442\u0435");
22880
23124
  try {
22881
23125
  await answerBudgetQuestions(deps, { stormActive, droneCap });
22882
23126
  } catch (e) {
22883
23127
  console.error("[overmind] \u0440\u0430\u0437\u0431\u043E\u0440 \u043E\u0442\u0432\u0435\u0442\u043E\u0432 \u043E \u0431\u044E\u0434\u0436\u0435\u0442\u0435 \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
22884
23128
  }
23129
+ deps.stepClock?.mark("4b \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 \u043D\u0430\u0434\u0437\u043E\u0440");
22885
23130
  if (deps.anomalies && !deps.control?.paused) {
22886
23131
  const az = deps.anomalies;
22887
23132
  let ready = [];
@@ -22958,6 +23203,7 @@ async function tickOnce(deps) {
22958
23203
  }
22959
23204
  }
22960
23205
  }
23206
+ deps.stepClock?.mark("4c \u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430");
22961
23207
  if (deps.watchdog) {
22962
23208
  try {
22963
23209
  const action = decideWatchdogAction(deps.db, now, deps.watchdog.state, deps.watchdog.thresholds, droneCap, { exists: worktreeExists, hasTranscript }, await projectQuotaBlockedRunIds(deps));
@@ -22966,6 +23212,7 @@ async function tickOnce(deps) {
22966
23212
  console.error("[overmind] \u0441\u0442\u043E\u0440\u043E\u0436 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441\u0430: \u0442\u0438\u043A \u0441\u043E\u0440\u0432\u0430\u043B\u0441\u044F:", e);
22967
23213
  }
22968
23214
  }
23215
+ deps.stepClock?.mark("5 \u0430\u0432\u0442\u043E-\u0438\u043D\u0442\u0435\u0439\u043A");
22969
23216
  const heavyInFlight = heavyRunsInFlight(deps);
22970
23217
  if (heavyInFlight.length && deps.intake) {
22971
23218
  const d = noteDefer(deps.spawnDefer ??= /* @__PURE__ */ new Map(), "weight-intake-hold", intakeHoldReason(heavyInFlight.map(droneLabel).join(", ")), GATE_LOG_HEARTBEAT_TICKS);
@@ -23404,7 +23651,11 @@ async function makeStore(repo, worktreesDir, repoFlag) {
23404
23651
  // Ретрай не молчит: обрыв, переигранный в том же вызове, всё равно виден.
23405
23652
  // Без этой строки самолечение связи — единственный путь, о котором в логе
23406
23653
  // ни слова, и разбор «почему тик подтормозил» упирается в тишину.
23407
- onRetry: ({ tool, attempt, reason }) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP: ${tool} \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F (${reason}) \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 ${attempt}.`)
23654
+ onRetry: ({ tool, attempt, reason }) => console.warn(`[overmind] \u043F\u0440\u043E\u0434-MCP: ${tool} \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0441\u044F (${reason}) \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 ${attempt}.`),
23655
+ // TASWM-611: молчащий прод больше не держит проход минутами — остаток идёт без сети.
23656
+ onBudgetSpent: ({ spentMs, budgetMs }) => console.warn(
23657
+ `[overmind] \u043F\u0440\u043E\u0434-MCP \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442: \u043F\u0440\u043E\u0445\u043E\u0434 \u0443\u0436\u0435 \u043F\u043E\u0442\u0440\u0430\u0442\u0438\u043B \u043D\u0430 \u043D\u0435\u0433\u043E ${Math.round(spentMs / 1e3)}\u0441 (\u0431\u044E\u0434\u0436\u0435\u0442 ${Math.round(budgetMs / 1e3)}\u0441) \u2014 \u0434\u043E \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043F\u0440\u043E\u0445\u043E\u0434\u0430 \u0440\u0430\u0431\u043E\u0442\u0430\u044E \u0431\u0435\u0437 \u0441\u0435\u0442\u0438, \u0437\u0430\u043F\u0438\u0441\u0438 \u0436\u0434\u0443\u0442 \u0432 outbox.`
23658
+ )
23408
23659
  });
23409
23660
  const explicitRegistry = await buildRepoRegistry(repoPaths);
23410
23661
  if (repoPaths.length > 0 && explicitRegistry.length < repoPaths.length) {
@@ -24774,12 +25025,14 @@ async function runDaemon(opts) {
24774
25025
  tickHealth.startedAt = Date.now();
24775
25026
  for (; ; ) {
24776
25027
  let tickError;
25028
+ prodMcp?.beginPass?.();
25029
+ deps.stepClock = makeStepClock();
24777
25030
  try {
24778
25031
  writeHeartbeat(HEARTBEAT_PATH, Date.now());
24779
25032
  await tickOnce(deps);
24780
25033
  if (restart.phase === "waiting") {
24781
25034
  const live2 = liveSessionIds(db, pidAlive);
24782
- if (live2.length === 0) performRestart([], restart.intent);
25035
+ if (live2.length === 0 && !hasPreparingRuns(db)) performRestart([], restart.intent);
24783
25036
  else onRestartStillWaiting?.(live2.length);
24784
25037
  }
24785
25038
  } catch (e) {
@@ -24787,6 +25040,8 @@ async function runDaemon(opts) {
24787
25040
  console.error(`[overmind] \u0442\u0438\u043A \u0443\u043F\u0430\u043B: ${describeTickError(e)} \u2014 \u0446\u0438\u043A\u043B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0443`, e);
24788
25041
  }
24789
25042
  markTickDone(tickHealth, Date.now(), tickError);
25043
+ const slow = slowPassLine(tickHealth.seq, deps.stepClock.finish());
25044
+ if (slow) console.warn(slow);
24790
25045
  await new Promise((r) => setTimeout(r, interval));
24791
25046
  }
24792
25047
  }
@@ -24864,6 +25119,7 @@ export {
24864
25119
  REVIEW_SPAWN_LIMIT,
24865
25120
  REVIEW_UNRECOVERABLE_MARK,
24866
25121
  SELF_REFERENCE_MARK,
25122
+ SPAWN_INLINE_WAIT_MS,
24867
25123
  STALL_FLOOR_MS,
24868
25124
  STALL_MS,
24869
25125
  STORM_DEAD_LIMIT,
@@ -24891,6 +25147,7 @@ export {
24891
25147
  cycleCapHolder,
24892
25148
  effectiveReviewCycleLimit,
24893
25149
  hasLiveDescendant,
25150
+ hasPreparingRuns,
24894
25151
  isSubrun,
24895
25152
  lastChangesSummary,
24896
25153
  lastVerifySummary,