@taskless-app/swarm 0.6.67 → 0.6.69

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