@taskless-app/swarm 0.6.22 → 0.6.23

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 +144 -14
  2. package/dist/overmind.js +187 -63
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1131,8 +1131,8 @@ var init_runGuards = __esm({
1131
1131
  function hasZoneModel(m) {
1132
1132
  return m.tasks > 0 && m.zones.length > 0;
1133
1133
  }
1134
- function testFileFeature(basename5) {
1135
- const head = basename5.replace(/\.(test|spec)\.[a-z]+$/i, "").split(".")[0].toLowerCase();
1134
+ function testFileFeature(basename7) {
1135
+ const head = basename7.replace(/\.(test|spec)\.[a-z]+$/i, "").split(".")[0].toLowerCase();
1136
1136
  return head && !GENERIC_DIRS.has(head) ? head : null;
1137
1137
  }
1138
1138
  function pathZoneCandidate(path) {
@@ -2393,7 +2393,7 @@ var init_package = __esm({
2393
2393
  "package.json"() {
2394
2394
  package_default = {
2395
2395
  name: "@taskless-app/swarm",
2396
- version: "0.6.22",
2396
+ version: "0.6.23",
2397
2397
  type: "module",
2398
2398
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
2399
2399
  keywords: [
@@ -2443,7 +2443,7 @@ var init_package = __esm({
2443
2443
  execa: "^10.0.0",
2444
2444
  zod: "3.25.76"
2445
2445
  },
2446
- gitHead: "b8eaac4a5562ffcf45a02af338267620e0a7843f"
2446
+ gitHead: "af6c4af0f523891b5db805d9b4583021688ed1f5"
2447
2447
  };
2448
2448
  }
2449
2449
  });
@@ -5250,7 +5250,7 @@ var init_updateCli = __esm({
5250
5250
 
5251
5251
  // src/cli.ts
5252
5252
  init_cliIo();
5253
- import { basename as basename4, dirname as dirname4, join as join24 } from "path";
5253
+ import { basename as basename6, dirname as dirname4, join as join24 } from "path";
5254
5254
  import { existsSync as existsSync16, readFileSync as readFileSync17, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync10, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
5255
5255
  import { setTimeout as sleep2 } from "timers/promises";
5256
5256
 
@@ -8322,7 +8322,7 @@ function hostLines(hosts, opts = {}) {
8322
8322
  // src/web.ts
8323
8323
  init_src();
8324
8324
  import { randomBytes as randomBytes3 } from "crypto";
8325
- import { basename as basename2 } from "path";
8325
+ import { basename as basename4 } from "path";
8326
8326
  import { serve as serve2 } from "@hono/node-server";
8327
8327
 
8328
8328
  // src/diff.ts
@@ -8518,7 +8518,123 @@ init_storm();
8518
8518
  // src/gh.ts
8519
8519
  init_src();
8520
8520
  import { $ as $4 } from "execa";
8521
- var realRepoRunner = async (args, cwd) => (await $4({ cwd })`gh ${args}`).stdout;
8521
+ import { basename as basename3 } from "path";
8522
+
8523
+ // src/logNoise.ts
8524
+ var DEFAULT_REMIND_MS = 10 * 6e4;
8525
+ function timesWord(n) {
8526
+ const tens = n % 100;
8527
+ if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
8528
+ return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
8529
+ }
8530
+ function makeRepeatFilter(opts = {}) {
8531
+ const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
8532
+ const streaks = /* @__PURE__ */ new Map();
8533
+ return {
8534
+ say(key, message, now) {
8535
+ const prev = streaks.get(key);
8536
+ if (!prev || prev.message !== message) {
8537
+ const tail = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
8538
+ streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
8539
+ return `${message}${tail}`;
8540
+ }
8541
+ prev.hidden += 1;
8542
+ if (now - prev.saidAt < remindAfterMs) return null;
8543
+ const hidden = prev.hidden;
8544
+ const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
8545
+ prev.saidAt = now;
8546
+ prev.hidden = 0;
8547
+ return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
8548
+ },
8549
+ forget(key) {
8550
+ streaks.delete(key);
8551
+ }
8552
+ };
8553
+ }
8554
+
8555
+ // src/netTimeout.ts
8556
+ import { basename as basename2 } from "path";
8557
+ var GH_CALL_TIMEOUT_MS = 15e3;
8558
+ var GIT_LOCAL_TIMEOUT_MS = 5e3;
8559
+ var HARD_CAP_GRACE_MS = 2e3;
8560
+ var CallTimedOutError = class extends Error {
8561
+ timedOut = true;
8562
+ constructor(call, timeoutMs) {
8563
+ super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
8564
+ this.name = "CallTimedOutError";
8565
+ }
8566
+ };
8567
+ function isTimedOut(outcome) {
8568
+ return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
8569
+ }
8570
+ function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
8571
+ const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
8572
+ if (!isTimedOut(outcome)) {
8573
+ timeoutNoise.forget(noiseKey(scope, call));
8574
+ return false;
8575
+ }
8576
+ const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
8577
+ if (say) warn(say);
8578
+ return true;
8579
+ }
8580
+ function noiseKey(scope, call) {
8581
+ return `timeout:${scope}:${call}`;
8582
+ }
8583
+ var timeoutNoise = makeRepeatFilter();
8584
+ function humanMs(ms) {
8585
+ return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
8586
+ }
8587
+ function destroyPipes(child) {
8588
+ for (const pipe of [child.stdout, child.stderr, child.stdin]) {
8589
+ try {
8590
+ pipe?.destroy?.();
8591
+ } catch {
8592
+ }
8593
+ }
8594
+ }
8595
+ async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
8596
+ let timer;
8597
+ const capped = new Promise((_, reject) => {
8598
+ timer = setTimeout(() => {
8599
+ child.kill("SIGKILL");
8600
+ destroyPipes(child);
8601
+ reject(new CallTimedOutError(call, timeoutMs));
8602
+ }, timeoutMs + HARD_CAP_GRACE_MS);
8603
+ timer.unref?.();
8604
+ });
8605
+ try {
8606
+ const res = await Promise.race([child, capped]);
8607
+ noteTimedOut(scope, call, timeoutMs, res, warn);
8608
+ return res;
8609
+ } catch (e) {
8610
+ noteTimedOut(scope, call, timeoutMs, e, warn);
8611
+ throw e;
8612
+ } finally {
8613
+ if (timer) clearTimeout(timer);
8614
+ }
8615
+ }
8616
+ function callLabel(cmd2, args, head = 4) {
8617
+ const shown = args.slice(0, head).join(" ");
8618
+ return `${cmd2} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
8619
+ }
8620
+ function gitCallLabel(repo, args) {
8621
+ return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
8622
+ }
8623
+
8624
+ // src/gh.ts
8625
+ async function runGh(args, timeoutMs, cwd) {
8626
+ const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
8627
+ const child = cwd ? $4({ cwd, timeout: timeoutMs })`gh ${args}` : $4({ timeout: timeoutMs })`gh ${args}`;
8628
+ return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
8629
+ }
8630
+ function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
8631
+ return (args) => runGh(args, timeoutMs);
8632
+ }
8633
+ function ghRepoRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
8634
+ return (args, cwd) => runGh(args, timeoutMs, cwd);
8635
+ }
8636
+ var realRunner = ghRunner();
8637
+ var realRepoRunner = ghRepoRunner();
8522
8638
  async function openPrsForBranch(branch, repoDir, run = realRepoRunner) {
8523
8639
  if (!branch || !repoDir) return [];
8524
8640
  try {
@@ -8543,7 +8659,12 @@ function firstLine(s) {
8543
8659
  return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
8544
8660
  }
8545
8661
  var realChecksRunner = async (pr) => {
8546
- const r = await $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
8662
+ const r = await withHardTimeout(
8663
+ $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`,
8664
+ "gh",
8665
+ callLabel("gh", ["pr", "checks", pr]),
8666
+ 15e3
8667
+ );
8547
8668
  const stdout = r.stdout ?? "";
8548
8669
  const stderr = r.stderr ?? "";
8549
8670
  if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
@@ -9105,7 +9226,7 @@ function timelineState(db2, now = Date.now(), overmindLog = []) {
9105
9226
  id: r.id,
9106
9227
  name: r.name,
9107
9228
  slug: r.task_slug,
9108
- repo: basename2(r.repo || "") || "?",
9229
+ repo: basename4(r.repo || "") || "?",
9109
9230
  status: r.status,
9110
9231
  area: r.area,
9111
9232
  start: sqliteIso(r.created_at),
@@ -10882,6 +11003,14 @@ import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
10882
11003
  import { isAbsolute, join as join13, sep as sep2 } from "path";
10883
11004
  import { $ as $5 } from "execa";
10884
11005
  init_swarmFiles();
11006
+ function gitNet(repo, args, timeoutMs) {
11007
+ return withHardTimeout(
11008
+ $5({ reject: false, timeout: timeoutMs })`git -C ${repo} ${args}`,
11009
+ "git",
11010
+ gitCallLabel(repo, args),
11011
+ timeoutMs
11012
+ ).catch(() => null);
11013
+ }
10885
11014
  var WorktreeBusyError = class extends Error {
10886
11015
  constructor(branch, worktreePath) {
10887
11016
  super(`worktree \u0437\u0430\u043D\u044F\u0442: ${worktreePath} (\u0432\u0435\u0442\u043A\u0430 ${branch}) \u2014 \u043F\u0440\u0438\u0431\u0435\u0440\u0438 worktree \u0438\u043B\u0438 \u043E\u0442\u043C\u0435\u043D\u0438 \u043F\u0440\u043E\u0433\u043E\u043D (swarm cancel ${branch})`);
@@ -11086,8 +11215,9 @@ async function remoteMainRef(repo) {
11086
11215
  throw new Error(`\u043D\u0435 \u043D\u0430\u0448\u0451\u043B \u0434\u0435\u0444\u043E\u043B\u0442-\u0432\u0435\u0442\u043A\u0443 origin \u0432 ${repo} (\u043D\u0435\u0442 origin/HEAD, origin/main, origin/master)`);
11087
11216
  }
11088
11217
  var GITHUB_REMOTE_RE = /github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?\/?$/i;
11089
- async function remoteRepoFullName(repo) {
11090
- const url = (await $5({ reject: false })`git -C ${repo} remote get-url origin`).stdout.trim();
11218
+ async function remoteRepoFullName(repo, timeoutMs = GIT_LOCAL_TIMEOUT_MS) {
11219
+ const res = await gitNet(repo, ["remote", "get-url", "origin"], timeoutMs);
11220
+ const url = res?.stdout.trim() ?? "";
11091
11221
  const m = url.match(GITHUB_REMOTE_RE);
11092
11222
  return m ? m[1] : null;
11093
11223
  }
@@ -11114,7 +11244,7 @@ var KNOWN_EXT = /* @__PURE__ */ new Set([...IMAGE_EXT, ...DOC_EXT, ...TEXT_EXT])
11114
11244
 
11115
11245
  // src/shotUpload.ts
11116
11246
  import { readFile as fsReadFile } from "fs/promises";
11117
- import { basename as basename3, isAbsolute as isAbsolute2, join as join15 } from "path";
11247
+ import { basename as basename5, isAbsolute as isAbsolute2, join as join15 } from "path";
11118
11248
  var SHOT_MAX_FILE_BYTES = 10 * 1024 * 1024;
11119
11249
 
11120
11250
  // src/taskless.ts
@@ -12214,7 +12344,7 @@ var thisHost = loadHostIdentity({ home: SWARM_HOME, envHostId: process.env.SWARM
12214
12344
  function fmtRun(r, opts = {}) {
12215
12345
  const mark = STATUS_MARK[r.status] ?? "?";
12216
12346
  const area = r.area ? ` [${r.area}]` : "";
12217
- const repo = r.repo ? ` (${basename4(r.repo)})` : "";
12347
+ const repo = r.repo ? ` (${basename6(r.repo)})` : "";
12218
12348
  const q = r.status === "blocked" ? " \u2190 \u0436\u0434\u0451\u0442 \u043E\u0442\u0432\u0435\u0442\u0430" : "";
12219
12349
  const why = r.status === "stopped" && r.stop_reason ? ` \u2014 ${r.stop_reason}` : "";
12220
12350
  const priority = r.priority ? ` \u2691${r.priority > 0 ? "+" : ""}${r.priority}` : "";
@@ -12972,7 +13102,7 @@ switch (cmd) {
12972
13102
  process.exit(1);
12973
13103
  }
12974
13104
  const run = db.getRun(runId);
12975
- if (run) console.log(`#${run.id} ${run.name} ${run.task_slug}${run.repo ? ` (${basename4(run.repo)})` : ""}`);
13105
+ if (run) console.log(`#${run.id} ${run.name} ${run.task_slug}${run.repo ? ` (${basename6(run.repo)})` : ""}`);
12976
13106
  const events = db.events(runId);
12977
13107
  if (events.length === 0) console.log("\u0441\u043E\u0431\u044B\u0442\u0438\u0439 \u043D\u0435\u0442");
12978
13108
  else events.forEach((e) => console.log(`${e.at} ${e.kind}${e.text ? " " + e.text : ""}`));
package/dist/overmind.js CHANGED
@@ -1212,8 +1212,8 @@ var init_runGuards = __esm({
1212
1212
  function hasZoneModel(m) {
1213
1213
  return m.tasks > 0 && m.zones.length > 0;
1214
1214
  }
1215
- function testFileFeature(basename5) {
1216
- const head = basename5.replace(/\.(test|spec)\.[a-z]+$/i, "").split(".")[0].toLowerCase();
1215
+ function testFileFeature(basename7) {
1216
+ const head = basename7.replace(/\.(test|spec)\.[a-z]+$/i, "").split(".")[0].toLowerCase();
1217
1217
  return head && !GENERIC_DIRS.has(head) ? head : null;
1218
1218
  }
1219
1219
  function pathZoneCandidate(path) {
@@ -2907,6 +2907,44 @@ var init_heartbeat = __esm({
2907
2907
  }
2908
2908
  });
2909
2909
 
2910
+ // src/logNoise.ts
2911
+ function timesWord(n) {
2912
+ const tens = n % 100;
2913
+ if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
2914
+ return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
2915
+ }
2916
+ function makeRepeatFilter(opts = {}) {
2917
+ const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
2918
+ const streaks = /* @__PURE__ */ new Map();
2919
+ return {
2920
+ say(key, message, now) {
2921
+ const prev = streaks.get(key);
2922
+ if (!prev || prev.message !== message) {
2923
+ const tail2 = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
2924
+ streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
2925
+ return `${message}${tail2}`;
2926
+ }
2927
+ prev.hidden += 1;
2928
+ if (now - prev.saidAt < remindAfterMs) return null;
2929
+ const hidden = prev.hidden;
2930
+ const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
2931
+ prev.saidAt = now;
2932
+ prev.hidden = 0;
2933
+ return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
2934
+ },
2935
+ forget(key) {
2936
+ streaks.delete(key);
2937
+ }
2938
+ };
2939
+ }
2940
+ var DEFAULT_REMIND_MS;
2941
+ var init_logNoise = __esm({
2942
+ "src/logNoise.ts"() {
2943
+ "use strict";
2944
+ DEFAULT_REMIND_MS = 10 * 6e4;
2945
+ }
2946
+ });
2947
+
2910
2948
  // src/agentToken.ts
2911
2949
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
2912
2950
  import { join as join8 } from "path";
@@ -3324,11 +3362,100 @@ var init_procCleanup = __esm({
3324
3362
  }
3325
3363
  });
3326
3364
 
3365
+ // src/netTimeout.ts
3366
+ import { basename as basename2 } from "path";
3367
+ function isTimedOut(outcome) {
3368
+ return typeof outcome === "object" && outcome !== null && outcome.timedOut === true;
3369
+ }
3370
+ function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
3371
+ const line = `[${scope}] \u23F1 \u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${humanMs(timeoutMs)} \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
3372
+ if (!isTimedOut(outcome)) {
3373
+ timeoutNoise.forget(noiseKey(scope, call));
3374
+ return false;
3375
+ }
3376
+ const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
3377
+ if (say) warn(say);
3378
+ return true;
3379
+ }
3380
+ function noiseKey(scope, call) {
3381
+ return `timeout:${scope}:${call}`;
3382
+ }
3383
+ function humanMs(ms) {
3384
+ return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
3385
+ }
3386
+ function destroyPipes(child) {
3387
+ for (const pipe of [child.stdout, child.stderr, child.stdin]) {
3388
+ try {
3389
+ pipe?.destroy?.();
3390
+ } catch {
3391
+ }
3392
+ }
3393
+ }
3394
+ async function withHardTimeout(child, scope, call, timeoutMs, warn = console.warn) {
3395
+ let timer;
3396
+ const capped = new Promise((_, reject) => {
3397
+ timer = setTimeout(() => {
3398
+ child.kill("SIGKILL");
3399
+ destroyPipes(child);
3400
+ reject(new CallTimedOutError(call, timeoutMs));
3401
+ }, timeoutMs + HARD_CAP_GRACE_MS);
3402
+ timer.unref?.();
3403
+ });
3404
+ try {
3405
+ const res = await Promise.race([child, capped]);
3406
+ noteTimedOut(scope, call, timeoutMs, res, warn);
3407
+ return res;
3408
+ } catch (e) {
3409
+ noteTimedOut(scope, call, timeoutMs, e, warn);
3410
+ throw e;
3411
+ } finally {
3412
+ if (timer) clearTimeout(timer);
3413
+ }
3414
+ }
3415
+ function callLabel(cmd, args, head = 4) {
3416
+ const shown = args.slice(0, head).join(" ");
3417
+ return `${cmd} ${shown}${args.length > head ? " \u2026" : ""}`.trim();
3418
+ }
3419
+ function gitCallLabel(repo, args) {
3420
+ return `git -C ${basename2(repo) || repo} ${args.join(" ")}`;
3421
+ }
3422
+ var GH_CALL_TIMEOUT_MS, GIT_NET_TIMEOUT_MS, GIT_LOCAL_TIMEOUT_MS, GIT_HEAVY_NET_TIMEOUT_MS, HARD_CAP_GRACE_MS, CallTimedOutError, timeoutNoise;
3423
+ var init_netTimeout = __esm({
3424
+ "src/netTimeout.ts"() {
3425
+ "use strict";
3426
+ init_logNoise();
3427
+ GH_CALL_TIMEOUT_MS = 15e3;
3428
+ GIT_NET_TIMEOUT_MS = 15e3;
3429
+ GIT_LOCAL_TIMEOUT_MS = 5e3;
3430
+ GIT_HEAVY_NET_TIMEOUT_MS = 6e4;
3431
+ HARD_CAP_GRACE_MS = 2e3;
3432
+ CallTimedOutError = class extends Error {
3433
+ timedOut = true;
3434
+ constructor(call, timeoutMs) {
3435
+ super(`\u0432\u044B\u0437\u043E\u0432 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${timeoutMs}\u043C\u0441 \u0438 \u043F\u0440\u0435\u0440\u0432\u0430\u043D: ${call}`);
3436
+ this.name = "CallTimedOutError";
3437
+ }
3438
+ };
3439
+ timeoutNoise = makeRepeatFilter();
3440
+ }
3441
+ });
3442
+
3327
3443
  // src/git.ts
3328
3444
  import { existsSync as existsSync10, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
3329
3445
  import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
3330
3446
  import { isAbsolute as isAbsolute2, join as join17, sep as sep2 } from "path";
3331
3447
  import { $ as $2 } from "execa";
3448
+ function gitNet(repo, args, timeoutMs) {
3449
+ return withHardTimeout(
3450
+ $2({ reject: false, timeout: timeoutMs })`git -C ${repo} ${args}`,
3451
+ "git",
3452
+ gitCallLabel(repo, args),
3453
+ timeoutMs
3454
+ ).catch(() => null);
3455
+ }
3456
+ function gitNetStrict(repo, args, timeoutMs) {
3457
+ return withHardTimeout($2({ timeout: timeoutMs })`git -C ${repo} ${args}`, "git", gitCallLabel(repo, args), timeoutMs);
3458
+ }
3332
3459
  function isUnrecoverableRefError(e) {
3333
3460
  if (e instanceof MissingRefError) return true;
3334
3461
  const msg = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
@@ -3553,26 +3680,27 @@ async function remoteMainRef(repo) {
3553
3680
  throw new Error(`\u043D\u0435 \u043D\u0430\u0448\u0451\u043B \u0434\u0435\u0444\u043E\u043B\u0442-\u0432\u0435\u0442\u043A\u0443 origin \u0432 ${repo} (\u043D\u0435\u0442 origin/HEAD, origin/main, origin/master)`);
3554
3681
  }
3555
3682
  async function remoteBranchExists(repo, branch) {
3556
- const res = await $2({ reject: false, timeout: 5e3 })`git -C ${repo} ls-remote --heads origin ${`refs/heads/${branch}`}`;
3557
- if (res.exitCode !== 0) return null;
3683
+ const res = await gitNet(repo, ["ls-remote", "--heads", "origin", `refs/heads/${branch}`], 5e3);
3684
+ if (!res || res.exitCode !== 0) return null;
3558
3685
  return res.stdout.trim().length > 0;
3559
3686
  }
3560
- async function remoteBaseRef(repo, branch) {
3561
- await $2({ reject: false })`git -C ${repo} fetch --quiet origin ${branch}`;
3687
+ async function remoteBaseRef(repo, branch, timeoutMs = GIT_NET_TIMEOUT_MS) {
3688
+ await gitNet(repo, ["fetch", "--quiet", "origin", branch], timeoutMs);
3562
3689
  return `origin/${branch}`;
3563
3690
  }
3564
- async function remoteRepoFullName(repo) {
3565
- const url = (await $2({ reject: false })`git -C ${repo} remote get-url origin`).stdout.trim();
3691
+ async function remoteRepoFullName(repo, timeoutMs = GIT_LOCAL_TIMEOUT_MS) {
3692
+ const res = await gitNet(repo, ["remote", "get-url", "origin"], timeoutMs);
3693
+ const url = res?.stdout.trim() ?? "";
3566
3694
  const m = url.match(GITHUB_REMOTE_RE);
3567
3695
  return m ? m[1] : null;
3568
3696
  }
3569
3697
  async function journalForBranch(repo, branch, slug) {
3570
- await $2({ reject: false })`git -C ${repo} fetch --quiet origin ${branch}`;
3698
+ await gitNet(repo, ["fetch", "--quiet", "origin", branch], GIT_NET_TIMEOUT_MS);
3571
3699
  const out = await $2({ reject: false })`git -C ${repo} show ${`origin/${branch}:journal/${slug}.md`}`;
3572
3700
  return out.exitCode === 0 && out.stdout ? out.stdout : null;
3573
3701
  }
3574
3702
  async function prepareDetachedWorktree(repo, branch, worktree) {
3575
- await $2`git -C ${repo} fetch --quiet origin`;
3703
+ await gitNetStrict(repo, ["fetch", "--quiet", "origin"], GIT_HEAVY_NET_TIMEOUT_MS);
3576
3704
  const ref = `origin/${branch}`;
3577
3705
  if (!await revParse(repo, `${ref}^{commit}`)) throw new MissingRefError(ref, repo);
3578
3706
  await $2`git -C ${repo} worktree add --detach --force ${worktree} ${ref}`;
@@ -3589,7 +3717,7 @@ async function mergeRef(worktree, ref) {
3589
3717
  return { clean: false, conflicts: u ? u.split("\n") : [], changed: false };
3590
3718
  }
3591
3719
  async function pushHeadTo(worktree, branch) {
3592
- await $2`git -C ${worktree} push origin ${`HEAD:${branch}`}`;
3720
+ await gitNetStrict(worktree, ["push", "origin", `HEAD:${branch}`], GIT_HEAVY_NET_TIMEOUT_MS);
3593
3721
  }
3594
3722
  async function snapshotWorktree(worktree, message) {
3595
3723
  try {
@@ -3598,7 +3726,7 @@ async function snapshotWorktree(worktree, message) {
3598
3726
  await $2({ reject: false })`git -C ${worktree} add -A`;
3599
3727
  const commit = await $2({ reject: false })`git -C ${worktree} commit --no-verify -m ${message}`;
3600
3728
  if (commit.exitCode !== 0) return false;
3601
- await $2({ reject: false })`git -C ${worktree} push`;
3729
+ await gitNet(worktree, ["push"], GIT_NET_TIMEOUT_MS);
3602
3730
  return true;
3603
3731
  } catch {
3604
3732
  return false;
@@ -3614,6 +3742,7 @@ var init_git = __esm({
3614
3742
  "src/git.ts"() {
3615
3743
  "use strict";
3616
3744
  init_procCleanup();
3745
+ init_netTimeout();
3617
3746
  init_swarmFiles();
3618
3747
  WorktreeBusyError = class extends Error {
3619
3748
  constructor(branch, worktreePath) {
@@ -4301,7 +4430,7 @@ var init_attachments = __esm({
4301
4430
 
4302
4431
  // src/shotUpload.ts
4303
4432
  import { readFile as fsReadFile } from "fs/promises";
4304
- import { basename as basename2, isAbsolute as isAbsolute3, join as join21 } from "path";
4433
+ import { basename as basename4, isAbsolute as isAbsolute3, join as join21 } from "path";
4305
4434
  function shotMimetype(path) {
4306
4435
  const dot = path.lastIndexOf(".");
4307
4436
  if (dot < 0) return null;
@@ -4340,7 +4469,7 @@ async function readShots(paths, opts) {
4340
4469
  skipped.push({ path, reason: `${(bytes.byteLength / 1024 / 1024).toFixed(1)} \u041C\u0411 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043B\u0438\u043C\u0438\u0442\u0430 ${maxBytes / 1024 / 1024} \u041C\u0411` });
4341
4470
  continue;
4342
4471
  }
4343
- files.push({ path, fileName: basename2(path), mimetype, bytes });
4472
+ files.push({ path, fileName: basename4(path), mimetype, bytes });
4344
4473
  }
4345
4474
  return { files, skipped };
4346
4475
  }
@@ -6237,7 +6366,7 @@ var init_droneStore = __esm({
6237
6366
  // src/overmind.ts
6238
6367
  import { homedir as homedir5 } from "os";
6239
6368
  import { existsSync as existsSync17, readFileSync as readFileSync20, readdirSync as readdirSync13, renameSync as renameSync11 } from "fs";
6240
- import { basename as basename4, dirname as dirname4, join as join30 } from "path";
6369
+ import { basename as basename6, dirname as dirname4, join as join30 } from "path";
6241
6370
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
6242
6371
  import { spawn as spawn3 } from "child_process";
6243
6372
 
@@ -8940,39 +9069,8 @@ function installLogStamps(now = () => /* @__PURE__ */ new Date()) {
8940
9069
  };
8941
9070
  }
8942
9071
 
8943
- // src/logNoise.ts
8944
- var DEFAULT_REMIND_MS = 10 * 6e4;
8945
- function timesWord(n) {
8946
- const tens = n % 100;
8947
- if (tens >= 11 && tens <= 14) return "\u0440\u0430\u0437";
8948
- return n % 10 >= 2 && n % 10 <= 4 ? "\u0440\u0430\u0437\u0430" : "\u0440\u0430\u0437";
8949
- }
8950
- function makeRepeatFilter(opts = {}) {
8951
- const remindAfterMs = opts.remindAfterMs ?? DEFAULT_REMIND_MS;
8952
- const streaks = /* @__PURE__ */ new Map();
8953
- return {
8954
- say(key, message, now) {
8955
- const prev = streaks.get(key);
8956
- if (!prev || prev.message !== message) {
8957
- const tail2 = prev && prev.hidden > 0 ? ` (\u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 \u043C\u043E\u043B\u0447\u0430 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u043B\u043E\u0441\u044C \u0435\u0449\u0451 ${prev.hidden} ${timesWord(prev.hidden)})` : "";
8958
- streaks.set(key, { message, since: now, saidAt: now, hidden: 0 });
8959
- return `${message}${tail2}`;
8960
- }
8961
- prev.hidden += 1;
8962
- if (now - prev.saidAt < remindAfterMs) return null;
8963
- const hidden = prev.hidden;
8964
- const minutes = Math.max(1, Math.round((now - prev.since) / 6e4));
8965
- prev.saidAt = now;
8966
- prev.hidden = 0;
8967
- return `${message} \u2014 \u0442\u043E \u0436\u0435 \u0441\u0430\u043C\u043E\u0435 ${hidden} ${timesWord(hidden)} \u043F\u043E\u0434\u0440\u044F\u0434, \u0442\u044F\u043D\u0435\u0442\u0441\u044F ${minutes} \u043C\u0438\u043D`;
8968
- },
8969
- forget(key) {
8970
- streaks.delete(key);
8971
- }
8972
- };
8973
- }
8974
-
8975
9072
  // src/overmind.ts
9073
+ init_logNoise();
8976
9074
  init_linkFailure();
8977
9075
 
8978
9076
  // src/repoRegistry.ts
@@ -10830,9 +10928,22 @@ init_git();
10830
10928
 
10831
10929
  // src/gh.ts
10832
10930
  init_src();
10931
+ init_netTimeout();
10833
10932
  import { $ as $3 } from "execa";
10834
- var realRunner = async (args) => (await $3`gh ${args}`).stdout;
10835
- var realRepoRunner = async (args, cwd) => (await $3({ cwd })`gh ${args}`).stdout;
10933
+ import { basename as basename3 } from "path";
10934
+ async function runGh(args, timeoutMs, cwd) {
10935
+ const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
10936
+ const child = cwd ? $3({ cwd, timeout: timeoutMs })`gh ${args}` : $3({ timeout: timeoutMs })`gh ${args}`;
10937
+ return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
10938
+ }
10939
+ function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
10940
+ return (args) => runGh(args, timeoutMs);
10941
+ }
10942
+ function ghRepoRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
10943
+ return (args, cwd) => runGh(args, timeoutMs, cwd);
10944
+ }
10945
+ var realRunner = ghRunner();
10946
+ var realRepoRunner = ghRepoRunner();
10836
10947
  var GH_PR_RE = /^(?:https?:\/\/)?(?:www\.)?github\.com\/[^/\s]+\/[^/\s]+\/pull\/\d+(?:[/?#].*)?$/i;
10837
10948
  function isGithubPrUrl(url) {
10838
10949
  return GH_PR_RE.test(url.trim());
@@ -10922,7 +11033,7 @@ async function closePR(prUrl, run = realRunner) {
10922
11033
  async function postPrComment(prUrl, body, run = realRunner) {
10923
11034
  await run(["pr", "comment", prUrl, "--body", body]);
10924
11035
  }
10925
- var realCommentsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json comments`).stdout;
11036
+ var realCommentsRunner = (prUrl) => runGh(["pr", "view", prUrl, "--json", "comments"], GH_CALL_TIMEOUT_MS);
10926
11037
  var VERDICT_RE = /^[^A-Za-z]*(APPROVED?|LGTM|(?:REQUEST(?:ED)?\s+)?CHANGES(?:\s+REQUESTED)?)\b/i;
10927
11038
  var APPROVE_RE = /^(APPROVE|LGTM)/i;
10928
11039
  function verdictLabel(verdict) {
@@ -10964,7 +11075,7 @@ async function scanPrComments(prUrl, sinceIso, run, pick3) {
10964
11075
  async function latestVerifyVerdictComment(prUrl, sinceIso, run = realCommentsRunner) {
10965
11076
  return scanPrComments(prUrl, sinceIso, run, (body) => parseVerifyVerdictComment(body));
10966
11077
  }
10967
- var realPrCommitsRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json commits`).stdout;
11078
+ var realPrCommitsRunner = (prUrl) => runGh(["pr", "view", prUrl, "--json", "commits"], GH_CALL_TIMEOUT_MS);
10968
11079
  async function prHasCommitsSince(prUrl, sinceIso, run = realPrCommitsRunner) {
10969
11080
  if (!isGithubPrUrl(prUrl)) return false;
10970
11081
  let out;
@@ -10988,7 +11099,7 @@ async function prHasCommitsSince(prUrl, sinceIso, run = realPrCommitsRunner) {
10988
11099
  return Number.isFinite(ms) && (!Number.isFinite(sinceMs) || ms >= sinceMs);
10989
11100
  });
10990
11101
  }
10991
- var realPrCommitTitlesRunner = async (prUrl) => (await $3`gh pr view ${prUrl} --json commits`).stdout;
11102
+ var realPrCommitTitlesRunner = (prUrl) => runGh(["pr", "view", prUrl, "--json", "commits"], GH_CALL_TIMEOUT_MS);
10992
11103
  async function prCommitTitles(prUrl, run = realPrCommitTitlesRunner) {
10993
11104
  if (!isGithubPrUrl(prUrl)) return [];
10994
11105
  let out;
@@ -11027,7 +11138,12 @@ function firstLine(s) {
11027
11138
  return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
11028
11139
  }
11029
11140
  var realChecksRunner = async (pr) => {
11030
- const r = await $3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`;
11141
+ const r = await withHardTimeout(
11142
+ $3({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`,
11143
+ "gh",
11144
+ callLabel("gh", ["pr", "checks", pr]),
11145
+ 15e3
11146
+ );
11031
11147
  const stdout = r.stdout ?? "";
11032
11148
  const stderr = r.stderr ?? "";
11033
11149
  if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
@@ -11096,7 +11212,15 @@ async function prCiStatus(pr, run = realChecksRunner) {
11096
11212
  if (rows.some((r) => r.bucket === "pending")) return { bucket: "pending", failing: [], failingRunIds: [], ...counts };
11097
11213
  return { bucket: "pass", failing: [], failingRunIds: [], ...counts };
11098
11214
  }
11099
- var realRunLogRunner = async (runId) => (await $3({ reject: false, timeout: 3e4, maxBuffer: 10 * 1024 * 1024 })`gh run view ${runId} --log-failed`).stdout;
11215
+ var realRunLogRunner = async (runId) => {
11216
+ const r = await withHardTimeout(
11217
+ $3({ reject: false, timeout: 3e4, maxBuffer: 10 * 1024 * 1024 })`gh run view ${runId} --log-failed`,
11218
+ "gh",
11219
+ callLabel("gh", ["run", "view", runId]),
11220
+ 3e4
11221
+ );
11222
+ return r.stdout;
11223
+ };
11100
11224
  var CI_SUMMARY_LOG_CHARS = 4e3;
11101
11225
  var CI_SUMMARY_MAX_RUNS = 2;
11102
11226
  async function ciFailureSummary(failing, failingRunIds, logRun = realRunLogRunner) {
@@ -13371,7 +13495,7 @@ function makeEnvGate(opts) {
13371
13495
  // src/web.ts
13372
13496
  init_src();
13373
13497
  import { randomBytes as randomBytes3 } from "crypto";
13374
- import { basename as basename3 } from "path";
13498
+ import { basename as basename5 } from "path";
13375
13499
  import { serve as serve2 } from "@hono/node-server";
13376
13500
 
13377
13501
  // src/diff.ts
@@ -13577,7 +13701,7 @@ async function changedPaths(worktree, opts = {}) {
13577
13701
  // package.json
13578
13702
  var package_default = {
13579
13703
  name: "@taskless-app/swarm",
13580
- version: "0.6.22",
13704
+ version: "0.6.23",
13581
13705
  type: "module",
13582
13706
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
13583
13707
  keywords: [
@@ -13627,7 +13751,7 @@ var package_default = {
13627
13751
  execa: "^10.0.0",
13628
13752
  zod: "3.25.76"
13629
13753
  },
13630
- gitHead: "b8eaac4a5562ffcf45a02af338267620e0a7843f"
13754
+ gitHead: "af6c4af0f523891b5db805d9b4583021688ed1f5"
13631
13755
  };
13632
13756
 
13633
13757
  // src/version.ts
@@ -13841,7 +13965,7 @@ function timelineState(db, now = Date.now(), overmindLog = []) {
13841
13965
  id: r.id,
13842
13966
  name: r.name,
13843
13967
  slug: r.task_slug,
13844
- repo: basename3(r.repo || "") || "?",
13968
+ repo: basename5(r.repo || "") || "?",
13845
13969
  status: r.status,
13846
13970
  area: r.area,
13847
13971
  start: sqliteIso(r.created_at),
@@ -18045,7 +18169,7 @@ async function buildReliefCostCandidatesFor(deps, now, droneCap) {
18045
18169
  if (deps.exclusiveProject && await deps.exclusiveProject(repo)) continue;
18046
18170
  } catch {
18047
18171
  }
18048
- exclusiveRepos.push(basename4(repo));
18172
+ exclusiveRepos.push(basename6(repo));
18049
18173
  }
18050
18174
  const otherHosts = [
18051
18175
  ...new Set(
@@ -19016,7 +19140,7 @@ async function resolveConflict(deps, reviewRun) {
19016
19140
  model: reviewRun.model,
19017
19141
  review_model: reviewRun.review_model
19018
19142
  });
19019
- const wt = join30(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19143
+ const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-resolve-${resolver.id}`);
19020
19144
  try {
19021
19145
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19022
19146
  await deps.git.setWorktreeIdentity(wt, resolver.name);
@@ -19073,7 +19197,7 @@ async function spawnReviewer(deps, reviewRun, files) {
19073
19197
  // (своей у ревьюера нет, TASWM-204), и коммент с вердиктом должен уехать именно туда.
19074
19198
  workspace_id: reviewRun.workspace_id
19075
19199
  });
19076
- const wt = join30(deps.worktreesDir, basename4(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19200
+ const wt = join30(deps.worktreesDir, basename6(repo), `${reviewRun.task_slug}-review-${reviewer.id}`);
19077
19201
  try {
19078
19202
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19079
19203
  await deps.git.setWorktreeIdentity(wt, reviewer.name);
@@ -19149,7 +19273,7 @@ async function spawnVerifier(deps, verifiedRun, files) {
19149
19273
  // штамп цикла: фикс-итерацию проверяет свежий верификатор
19150
19274
  workspace_id: verifiedRun.workspace_id
19151
19275
  });
19152
- const wt = join30(deps.worktreesDir, basename4(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19276
+ const wt = join30(deps.worktreesDir, basename6(repo), `${verifiedRun.task_slug}-verify-${verifier.id}`);
19153
19277
  try {
19154
19278
  await deps.git.prepareDetachedWorktree(repo, branch, wt);
19155
19279
  await deps.git.setWorktreeIdentity(wt, verifier.name);
@@ -20017,7 +20141,7 @@ async function runDaemon(opts) {
20017
20141
  worktreesDir,
20018
20142
  activeWorktreePaths: new Set(runsWithWorktree.filter((r) => !isTerminalStatus(r.status)).map((r) => r.worktree_path)),
20019
20143
  containerRunPrefixes: runsWithWorktree.map((r) => ({
20020
- prefix: composeProjectPrefix(basename4(r.worktree_path)),
20144
+ prefix: composeProjectPrefix(basename6(r.worktree_path)),
20021
20145
  active: !isTerminalStatus(r.status)
20022
20146
  }))
20023
20147
  }).then(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskless-app/swarm",
3
- "version": "0.6.22",
3
+ "version": "0.6.23",
4
4
  "type": "module",
5
5
  "description": "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
6
6
  "keywords": [
@@ -38,7 +38,7 @@
38
38
  "execa": "^10.0.0",
39
39
  "zod": "3.25.76"
40
40
  },
41
- "gitHead": "b8eaac4a5562ffcf45a02af338267620e0a7843f",
41
+ "gitHead": "af6c4af0f523891b5db805d9b4583021688ed1f5",
42
42
  "scripts": {
43
43
  "overmind": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/overmind.ts",
44
44
  "swarm": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --conditions=development\" tsx src/cli.ts",