@taskless-app/swarm 0.6.31 → 0.6.33

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 +82 -17
  2. package/dist/overmind.js +1082 -505
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -2390,7 +2390,7 @@ function tokenFromFile() {
2390
2390
  return "";
2391
2391
  }
2392
2392
  }
2393
- var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_REST_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2393
+ var DB_PATH, EXECUTOR, DRONE_HOME, DRONE_HOME_ISOLATION, DRONE_ISOLATION, WORKTREES_NOINDEX_DIR, WORKTREES_LEGACY_DIR, WORKTREES_DIR, RUN_LOGS_DIR, DAEMON_LOG_PATH, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_KEEP, DAEMON_LOG_CHECK_MS, DIST_BUILD, SWARM_BACKEND, SWARM_OUTBOX, SWARM_MAX_IN_FLIGHT, SWARM_CLAIM_INTERVAL_MS, ENV_CACHE_DIR, ENV_CACHE_ENABLED, ENV_CACHE_KEEP, SWARM_MAX_LOAD_FACTOR, SWARM_MIN_FREE_GB, SWARM_MAX_FD_LOAD_FACTOR, SWARM_MAX_CPU_BUSY, SWARM_CPU_WINDOW_MS, SWARM_CPU_SANITY_BUSY, SWARM_CPU_SANITY_LOAD_FACTOR, SWARM_CPU_SANITY_LAG_MS, SWARM_RELIEF_LOAD_FACTOR, SWARM_RELIEF_MIN_FREE_GB, SWARM_RELIEF_MAX_FD_LOAD_FACTOR, SWARM_RELIEF_CPU_BUSY, SWARM_RELIEF_COST_WINDOW_MS, SWARM_RELIEF_ALERT_PAUSES, SWARM_RELIEF_ALERT_LOST_MS, SWARM_RELIEF_ALERT_MIN_STEPS, SWARM_RELIEF_ALERT_SUSTAIN_MS, SWARM_LOW_DISK_GB, SWARM_REHYDRATE_MS, SWARM_DRONE_BRIDGE_MS, SWARM_PROD_HEARTBEAT_MS, SWARM_VERSION_CHECK_MS, SWARM_SWEEP_INTERVAL_MS, SWARM_WORKTREE_REAP_INTERVAL_MS, DRONE_CHANNEL_ENABLED, DRONE_CHANNEL_PORT, DRONE_CHANNEL_URL, DRONE_CHANNEL_SECRET_PATH, DRONE_CHANNEL_GRACE_MS, HEARTBEAT_PATH, HEARTBEAT_STALE_MS, SWARM_REPO, SWARM_SCAN_DEPTH, SWARM_REPO_PATHS, SWARM_SCAN_ROOTS_RAW, REPO_MAP_PATH, SWARM_REPO_MAP_MS, SWARM_REPO_SCAN_TTL_MS, TASKLESS_API, SWARM_SHOT_TIMEOUT_MS, TASKLESS_MCP_PATH, TASKLESS_REST_PATH, TASKLESS_TOKEN, TASKLESS_WORKSPACE, TASKLESS_WORKSPACE_SLUG, TASKLESS_TASK_URL_BASE, SWARM_VERIFY, SWARM_AUTO_REVIEW, SWARM_AUTO_VERIFY, SWARM_MERGE_QUEUE, REVIEW_CUTOFF_PATH, SWARM_REVIEW_CAP, SWARM_STORM_URL, WEB_PORT, WEB_REDIRECT_URI, SWARM_ANOMALY_RENOTIFY_MS, SWARM_ANOMALY_MAX_REPEATS, SWARM_ANOMALY_RUN_DOWN_WINDOW_MS, SWARM_ANOMALY_FAILED_SPIKE_COUNT, SWARM_ANOMALY_FAILED_SPIKE_WINDOW_MS, SWARM_ANOMALY_QUEUE_STALLED_MS, SWARM_ANOMALY_PR_STUCK_MERGE_MS, SWARM_ANOMALY_PR_NO_REVIEWER_MS, SWARM_ANOMALY_QUESTION_UNANSWERED_MS, SWARM_ANOMALY_OUTBOX_STUCK_MS, SWARM_ANOMALY_RESOURCE_GATE_STALL_MS, SWARM_ANOMALY_DEP_MISSING_MS, SWARM_WATCHDOG_STALL_MS;
2394
2394
  var init_config = __esm({
2395
2395
  "src/config.ts"() {
2396
2396
  "use strict";
@@ -2416,6 +2416,9 @@ var init_config = __esm({
2416
2416
  SWARM_OUTBOX = process.env.SWARM_OUTBOX || join5(SWARM_HOME, "outbox.jsonl");
2417
2417
  SWARM_MAX_IN_FLIGHT = Math.trunc(envNum("SWARM_MAX_IN_FLIGHT", 3));
2418
2418
  SWARM_CLAIM_INTERVAL_MS = envNum("SWARM_CLAIM_INTERVAL_MS", 6e4);
2419
+ ENV_CACHE_DIR = process.env.SWARM_ENV_CACHE_DIR || join5(SWARM_HOME, "env-cache");
2420
+ ENV_CACHE_ENABLED = process.env.SWARM_ENV_CACHE !== "0";
2421
+ ENV_CACHE_KEEP = Math.trunc(envNum("SWARM_ENV_CACHE_KEEP", 4));
2419
2422
  SWARM_MAX_LOAD_FACTOR = envNum("SWARM_MAX_LOAD_FACTOR", 0.9);
2420
2423
  SWARM_MIN_FREE_GB = envNum("SWARM_MIN_FREE_GB", 1.5);
2421
2424
  SWARM_MAX_FD_LOAD_FACTOR = envNum("SWARM_MAX_FD_LOAD_FACTOR", 0.9);
@@ -3239,7 +3242,7 @@ var init_package = __esm({
3239
3242
  "package.json"() {
3240
3243
  package_default = {
3241
3244
  name: "@taskless-app/swarm",
3242
- version: "0.6.31",
3245
+ version: "0.6.33",
3243
3246
  type: "module",
3244
3247
  description: "Local control plane for a fleet of AI coding agents (drones), coordinating through Taskless. The overmind runs the swarm.",
3245
3248
  keywords: [
@@ -3290,7 +3293,7 @@ var init_package = __esm({
3290
3293
  execa: "^10.0.0",
3291
3294
  zod: "3.25.76"
3292
3295
  },
3293
- gitHead: "82950eae7a6ad09be4193708257798c811bb6fae"
3296
+ gitHead: "7b17b06b041af953afde46fbd41fc68ebc6dc4e9"
3294
3297
  };
3295
3298
  }
3296
3299
  });
@@ -3362,6 +3365,40 @@ function noteTimedOut(scope, call, timeoutMs, outcome, warn = console.warn) {
3362
3365
  function noiseKey(scope, call) {
3363
3366
  return `timeout:${scope}:${call}`;
3364
3367
  }
3368
+ function callFailureWord(f) {
3369
+ switch (f.reason) {
3370
+ case "timeout":
3371
+ return "\u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 \u043E\u0442\u0432\u0435\u0434\u0451\u043D\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F (\u043F\u0440\u0435\u0440\u0432\u0430\u043D)";
3372
+ case "unreadable":
3373
+ return "\u043E\u0442\u0432\u0435\u0442\u0438\u043B, \u043D\u043E \u0432\u044B\u0432\u043E\u0434 \u043D\u0435 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u043D";
3374
+ case "crashed":
3375
+ return "\u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F";
3376
+ default:
3377
+ return "\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0431\u0435\u0437 \u0432\u044B\u0432\u043E\u0434\u0430";
3378
+ }
3379
+ }
3380
+ function classifyCallFailure(outcome) {
3381
+ if (isTimedOut(outcome)) return { reason: "timeout", detail: firstLineOf(errText(outcome)) };
3382
+ const code = outcome?.code;
3383
+ if (code === "ENOENT") return { reason: "crashed", detail: firstLineOf(errText(outcome)) };
3384
+ return { reason: "empty", detail: firstLineOf(errText(outcome)) };
3385
+ }
3386
+ function errText(outcome) {
3387
+ const o = outcome;
3388
+ for (const v of [o?.stderr, o?.shortMessage, o?.message]) if (typeof v === "string" && v.trim()) return v;
3389
+ return "";
3390
+ }
3391
+ function firstLineOf(s) {
3392
+ return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
3393
+ }
3394
+ function noteCallFailure(scope, call, outcome, warn = console.warn) {
3395
+ const f = classifyCallFailure(outcome);
3396
+ if (f.reason === "timeout") return f;
3397
+ const line = `[${scope}] \u2716 \u0432\u044B\u0437\u043E\u0432 ${callFailureWord(f)}: ${call}${f.detail ? ` \u2014 ${f.detail}` : ""} \u2014 \u0440\u0435\u043A\u043E\u043D\u0441\u0430\u0439\u043B \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435`;
3398
+ const say = timeoutNoise.say(noiseKey(scope, call), line, Date.now());
3399
+ if (say) warn(say);
3400
+ return f;
3401
+ }
3365
3402
  function humanMs(ms) {
3366
3403
  return ms >= 1e3 ? `${Math.round(ms / 1e3)}\u0441` : `${ms}\u043C\u0441`;
3367
3404
  }
@@ -9875,7 +9912,12 @@ import { basename as basename3 } from "path";
9875
9912
  async function runGh(args, timeoutMs, cwd) {
9876
9913
  const label2 = callLabel("gh", args) + (cwd ? ` (repo ${basename3(cwd)})` : "");
9877
9914
  const child = cwd ? $4({ cwd, timeout: timeoutMs })`gh ${args}` : $4({ timeout: timeoutMs })`gh ${args}`;
9878
- return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
9915
+ try {
9916
+ return (await withHardTimeout(child, "gh", label2, timeoutMs)).stdout;
9917
+ } catch (e) {
9918
+ noteCallFailure("gh", label2, e);
9919
+ throw e;
9920
+ }
9879
9921
  }
9880
9922
  function ghRunner(timeoutMs = GH_CALL_TIMEOUT_MS) {
9881
9923
  return (args) => runGh(args, timeoutMs);
@@ -9908,36 +9950,59 @@ var DENIED_STDERR = /(?:^|\D)403(?:\D|$)|not accessible by integration|must have
9908
9950
  function firstLine(s) {
9909
9951
  return s.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "gh \u0432\u0435\u0440\u043D\u0443\u043B 403";
9910
9952
  }
9953
+ var GhCallFailedError = class extends Error {
9954
+ failure;
9955
+ constructor(call, failure) {
9956
+ super(`${call}: ${failure.reason}${failure.detail ? ` \u2014 ${failure.detail}` : ""}`);
9957
+ this.name = "GhCallFailedError";
9958
+ this.failure = failure;
9959
+ }
9960
+ };
9911
9961
  var realChecksRunner = async (pr) => {
9912
- const r = await withHardTimeout(
9913
- $4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`,
9914
- "gh",
9915
- callLabel("gh", ["pr", "checks", pr]),
9916
- 15e3
9917
- );
9962
+ const label2 = callLabel("gh", ["pr", "checks", pr]);
9963
+ const r = await withHardTimeout($4({ reject: false, timeout: 15e3 })`gh pr checks ${pr} --json state,name,bucket,link`, "gh", label2, 15e3);
9918
9964
  const stdout = r.stdout ?? "";
9919
9965
  const stderr = r.stderr ?? "";
9966
+ if (isTimedOut(r)) throw new GhCallFailedError(label2, classifyCallFailure(r));
9920
9967
  if (!stdout.trim() && NO_CHECKS_STDERR.test(stderr)) return "[]";
9921
9968
  if (!stdout.trim() && DENIED_STDERR.test(stderr)) throw new CiAccessDeniedError(firstLine(stderr));
9969
+ if (!stdout.trim()) throw new GhCallFailedError(label2, noteCallFailure("gh", label2, r));
9922
9970
  return stdout;
9923
9971
  };
9924
9972
  async function prCheckRows(pr, run) {
9973
+ let out;
9925
9974
  try {
9926
- const out = await run(pr);
9927
- const rows = JSON.parse(out);
9928
- if (!Array.isArray(rows)) return null;
9929
- return rows.map((r) => ({ name: r.name ?? "?", bucket: (r.bucket ?? "").toLowerCase(), link: r.link ?? "" }));
9975
+ out = await run(pr);
9930
9976
  } catch (e) {
9931
9977
  if (e instanceof CiAccessDeniedError) return { denied: e.detail };
9932
- return null;
9978
+ return { unavailable: e instanceof GhCallFailedError ? e.failure : classifyCallFailure(e) };
9933
9979
  }
9980
+ let parsed;
9981
+ try {
9982
+ parsed = JSON.parse(out);
9983
+ } catch {
9984
+ return { unavailable: { reason: "unreadable", detail: headOfOutput(out) } };
9985
+ }
9986
+ if (!Array.isArray(parsed)) return { unavailable: { reason: "unreadable", detail: "\u043E\u0442\u0432\u0435\u0442 gh \u2014 \u043D\u0435 \u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u043A\u043E\u0432" } };
9987
+ return parsed.map((r) => ({
9988
+ name: r.name ?? "?",
9989
+ bucket: (r.bucket ?? "").toLowerCase(),
9990
+ link: r.link ?? ""
9991
+ }));
9992
+ }
9993
+ function headOfOutput(out) {
9994
+ const head = firstLine(out).slice(0, 120);
9995
+ return head === "gh \u0432\u0435\u0440\u043D\u0443\u043B 403" ? "\u043F\u0443\u0441\u0442\u043E" : head;
9934
9996
  }
9935
9997
  function isDenied(rows) {
9936
- return rows != null && !Array.isArray(rows);
9998
+ return !Array.isArray(rows) && "denied" in rows;
9999
+ }
10000
+ function isUnavailable(rows) {
10001
+ return !Array.isArray(rows) && "unavailable" in rows;
9937
10002
  }
9938
10003
  async function ghChecks(pr, run = realChecksRunner) {
9939
10004
  const rows = await prCheckRows(pr, run);
9940
- if (!rows || isDenied(rows) || rows.length === 0) return null;
10005
+ if (isDenied(rows) || isUnavailable(rows) || rows.length === 0) return null;
9941
10006
  let pass = 0, fail = 0, pending2 = 0;
9942
10007
  for (const r of rows) {
9943
10008
  if (r.bucket === "pass") pass++;