@sleds/cli 0.1.52 → 0.1.54

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 (2) hide show
  1. package/dist/sleds.mjs +154 -71
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -8557,8 +8557,7 @@ function parseParked(v) {
8557
8557
  return {
8558
8558
  question: v.question,
8559
8559
  at: v.at,
8560
- ...typeof v.screen === "string" ? { screen: v.screen } : {},
8561
- ...v.still === true ? { still: true } : {},
8560
+ ...typeof v.words === "string" ? { words: v.words, ...v.still === true ? { still: true } : {} } : {},
8562
8561
  ...answerer ? { answerer } : {}
8563
8562
  };
8564
8563
  }
@@ -8601,7 +8600,9 @@ function parseRun(v) {
8601
8600
  options: Array.isArray(v.permission.options) ? v.permission.options.filter((x) => typeof x === "string") : [],
8602
8601
  at: v.permission.at,
8603
8602
  ...v.permission.onScreen === true ? { onScreen: true } : {},
8603
+ ...typeof v.permission.dialog === "string" ? { dialog: v.permission.dialog } : {},
8604
8604
  ...typeof v.permission.pressedAt === "number" ? { pressedAt: v.permission.pressedAt } : {},
8605
+ ...typeof v.permission.pressedOn === "string" ? { pressedOn: v.permission.pressedOn } : {},
8605
8606
  ...typeof v.permission.presses === "number" ? { presses: v.permission.presses } : {},
8606
8607
  ...v.permission.gaveUp === true ? { gaveUp: true } : {}
8607
8608
  }
@@ -8615,12 +8616,21 @@ function parseRun(v) {
8615
8616
  feedback: typeof v.pr.feedback === "number" ? v.pr.feedback : 0,
8616
8617
  ...typeof v.pr.authorFeedback === "number" ? { authorFeedback: v.pr.authorFeedback } : {},
8617
8618
  ...typeof v.pr.selfNudges === "number" ? { selfNudges: v.pr.selfNudges } : {},
8619
+ ...isObject8(v.pr.feedbackRead) && typeof v.pr.feedbackRead.updatedAt === "string" && typeof v.pr.feedbackRead.all === "number" && typeof v.pr.feedbackRead.author === "number" && typeof v.pr.feedbackRead.at === "number" ? {
8620
+ feedbackRead: {
8621
+ updatedAt: v.pr.feedbackRead.updatedAt,
8622
+ all: v.pr.feedbackRead.all,
8623
+ author: v.pr.feedbackRead.author,
8624
+ at: v.pr.feedbackRead.at
8625
+ }
8626
+ } : {},
8618
8627
  ...v.pr.clear === true ? { clear: true } : {},
8619
8628
  ...typeof v.pr.ci === "string" ? { ci: v.pr.ci } : {},
8620
8629
  ...typeof v.pr.review === "string" ? { review: v.pr.review } : {},
8621
8630
  ...typeof v.pr.posted === "string" ? { posted: v.pr.posted } : {},
8622
8631
  ...Array.isArray(v.pr.failedHeads) ? { failedHeads: v.pr.failedHeads.filter((x) => typeof x === "string") } : {},
8623
8632
  ...typeof v.pr.conflict === "string" ? { conflict: v.pr.conflict } : {},
8633
+ ...isObject8(v.pr.conflictCheck) && typeof v.pr.conflictCheck.head === "string" && typeof v.pr.conflictCheck.at === "number" ? { conflictCheck: { head: v.pr.conflictCheck.head, at: v.pr.conflictCheck.at } } : {},
8624
8634
  ...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
8625
8635
  ...conflicted ? { conflicted } : {},
8626
8636
  ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
@@ -10759,15 +10769,36 @@ var claudeAddDirFlag = (mainCheckout) => mainCheckout ? ` --add-dir=${shq(mainCh
10759
10769
  var shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
10760
10770
 
10761
10771
  // src/runners/exec.ts
10772
+ var EXEC_TIMEOUT_MS = 2 * 60000;
10773
+ var EXEC_TIMEOUT_STATUS = 124;
10774
+ var timedOut = (r) => r.error?.code === "ETIMEDOUT";
10775
+ var logTimeouts = (exec, log) => (cmd, args, o) => {
10776
+ const r = exec(cmd, args, o);
10777
+ if (r.status === EXEC_TIMEOUT_STATUS && isTimeout(r.stderr))
10778
+ log(`exec timed out: ${[cmd, ...args].join(" ")} — ${r.stderr.split(`
10779
+ `)[0]}`);
10780
+ return r;
10781
+ };
10762
10782
  var defaultExec = (cmd, args, o) => {
10783
+ const timeoutMs = o?.timeoutMs ?? EXEC_TIMEOUT_MS;
10763
10784
  const r = spawnSync3(cmd, [...args], {
10764
10785
  cwd: o?.cwd,
10765
10786
  input: o?.input,
10766
10787
  env: o?.env ? { ...process.env, ...o.env } : undefined,
10767
10788
  encoding: "utf8",
10768
10789
  shell: false,
10769
- maxBuffer: 16 * 1024 * 1024
10790
+ maxBuffer: 16 * 1024 * 1024,
10791
+ timeout: timeoutMs,
10792
+ killSignal: "SIGKILL"
10770
10793
  });
10794
+ if (timedOut(r)) {
10795
+ return {
10796
+ status: EXEC_TIMEOUT_STATUS,
10797
+ stdout: r.stdout ?? "",
10798
+ stderr: `${cmd}: timed out after ${timeoutMs / 1000}s and was killed${r.stderr ? `
10799
+ ${r.stderr}` : ""}`
10800
+ };
10801
+ }
10771
10802
  if (r.error) {
10772
10803
  return { status: 127, stdout: r.stdout ?? "", stderr: `${cmd}: ${r.error.message}` };
10773
10804
  }
@@ -11436,14 +11467,7 @@ ${r.stdout}`) };
11436
11467
  sendKey(h, key) {
11437
11468
  if (!isRunnerKey(key))
11438
11469
  throw new Error(`orca terminal send ${h.id}: no key ${JSON.stringify(key)}`);
11439
- const what = `orca terminal send ${h.id} (key ${key})`;
11440
- const send = (args) => orca(["terminal", "send", "--terminal", h.id, ...args, "--json"], what);
11441
- if (key === "esc") {
11442
- send(["--text", ESC]);
11443
- return;
11444
- }
11445
- send(["--text", key]);
11446
- send(["--enter"]);
11470
+ orca(["terminal", "send", "--terminal", h.id, "--text", key === "esc" ? ESC : key, "--json"], `orca terminal send ${h.id} (key ${key})`);
11447
11471
  },
11448
11472
  stop(h) {
11449
11473
  exec("orca", ["terminal", "close", "--terminal", h.id]);
@@ -11511,7 +11535,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11511
11535
  name: flags.name,
11512
11536
  os: host.os ?? process.platform,
11513
11537
  runner: flags.runner,
11514
- version: host.version ?? "0.1.52" ?? "dev",
11538
+ version: host.version ?? "0.1.54" ?? "dev",
11515
11539
  running: live.length,
11516
11540
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11517
11541
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -11911,6 +11935,7 @@ function parsePrView(stdout) {
11911
11935
  feedback: 0,
11912
11936
  authorFeedback: 0,
11913
11937
  draft: o.isDraft === true,
11938
+ updatedAt: typeof o.updatedAt === "string" ? o.updatedAt : "",
11914
11939
  ...mock ? { mock } : {},
11915
11940
  ...typeof o.headRefOid === "string" && o.headRefOid ? { head: o.headRefOid } : {},
11916
11941
  ...typeof mergeCommit === "string" && mergeCommit ? { mergeCommit } : {}
@@ -11959,20 +11984,24 @@ function prFeedback(exec, repoPath, url, author) {
11959
11984
  }
11960
11985
  return total;
11961
11986
  }
11962
- function prView(exec, repoPath, url) {
11987
+ function prView(exec, repoPath, url, last) {
11963
11988
  try {
11964
11989
  const r = exec("gh", [
11965
11990
  "pr",
11966
11991
  "view",
11967
11992
  url,
11968
11993
  "--json",
11969
- "state,author,statusCheckRollup,reviewDecision,comments,reviews,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body"
11994
+ "state,author,statusCheckRollup,reviewDecision,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body,updatedAt"
11970
11995
  ], { cwd: repoPath });
11971
11996
  const view = r.status === 0 ? parsePrView(r.stdout) : null;
11972
11997
  if (!view || view.state !== "OPEN")
11973
11998
  return view;
11974
- const feedback = prFeedback(exec, repoPath, url, view.author);
11975
- return feedback === null ? view : { ...view, feedback: feedback.all, authorFeedback: feedback.author };
11999
+ let read = view.updatedAt && last?.updatedAt === view.updatedAt ? last : undefined;
12000
+ if (!read) {
12001
+ const f = prFeedback(exec, repoPath, url, view.author);
12002
+ read = f && { ...f, updatedAt: view.updatedAt };
12003
+ }
12004
+ return read ? { ...view, feedback: read.all, authorFeedback: read.author, read } : view;
11976
12005
  } catch {
11977
12006
  return null;
11978
12007
  }
@@ -13011,26 +13040,28 @@ function sledsSource(client, o) {
13011
13040
  });
13012
13041
  if (r?.status !== 409)
13013
13042
  return "running";
13043
+ let route;
13044
+ try {
13045
+ route = await routeOf(claim.item);
13046
+ } catch {
13047
+ route = null;
13048
+ }
13049
+ if (!isObject8(route))
13050
+ return "running";
13014
13051
  if (claim.kind === "draft") {
13015
- try {
13016
- const route = await routeOf(claim.item);
13017
- const filed = Array.isArray(route?.nodes) && route.nodes.length > 0;
13018
- if (filed || isObject8(route?.proposed))
13019
- return "done";
13020
- if (isObject8(route?.draftParked))
13021
- return "parked";
13022
- } catch {}
13052
+ const filed = Array.isArray(route.nodes) && route.nodes.length > 0;
13053
+ if (filed || isObject8(route.proposed))
13054
+ return "done";
13055
+ if (isObject8(route.draftParked))
13056
+ return "parked";
13023
13057
  return "lost";
13024
13058
  }
13025
- try {
13026
- const route = await routeOf(claim.item);
13027
- const nodes = Array.isArray(route?.nodes) ? route.nodes.filter(isObject8) : [];
13028
- const state = nodes.find((n) => n.key === claim.node)?.state;
13029
- if (state === "parked")
13030
- return "parked";
13031
- if (state === "pr" || state === "landed" || state === "skipped")
13032
- return "done";
13033
- } catch {}
13059
+ const nodes = Array.isArray(route.nodes) ? route.nodes.filter(isObject8) : [];
13060
+ const state = nodes.find((n) => n.key === claim.node)?.state;
13061
+ if (state === "parked")
13062
+ return "parked";
13063
+ if (state === "pr" || state === "landed" || state === "skipped")
13064
+ return "done";
13034
13065
  return "lost";
13035
13066
  },
13036
13067
  async report(claim, outcome, by) {
@@ -13526,6 +13557,7 @@ function fastForwardUntouched(exec, path, base, log) {
13526
13557
  log?.(`${path}: fast-forwarded to origin/${base}`);
13527
13558
  return true;
13528
13559
  }
13560
+ var INSTALL_TIMEOUT_MS = 15 * 60000;
13529
13561
  var MANAGERS = new Set(["pnpm", "npm", "yarn", "bun"]);
13530
13562
  var INSTALL_VERBS = new Set(["install", "i", "ci"]);
13531
13563
  function installArgv(command) {
@@ -13617,7 +13649,7 @@ function ensureInstalled(exec, path, fs, o = {}) {
13617
13649
  return null;
13618
13650
  const [cmd, ...args] = argv;
13619
13651
  o.log?.(`${path}: no node_modules — running ${argv.join(" ")}`);
13620
- const r = exec(cmd, args, { cwd: path });
13652
+ const r = exec(cmd, args, { cwd: path, timeoutMs: INSTALL_TIMEOUT_MS });
13621
13653
  const ok = r.status === 0;
13622
13654
  if (!ok)
13623
13655
  o.log?.(`${path}: ${argv.join(" ")} failed (exit ${r.status}) — the session starts anyway: ${(r.stderr || r.stdout).trim().split(`
@@ -13701,6 +13733,9 @@ function fingerprint(text) {
13701
13733
  }
13702
13734
  return (h >>> 0).toString(16);
13703
13735
  }
13736
+ function wordsPrint(screen) {
13737
+ return fingerprint(stripTuiChrome(screen));
13738
+ }
13704
13739
  function sandboxFor(d, name, cwd, allow) {
13705
13740
  if (!d.sandbox)
13706
13741
  return {};
@@ -13967,7 +14002,7 @@ async function tickOnce(d) {
13967
14002
  if (beat === "parked") {
13968
14003
  const question = "the session parked itself; the question is on the route";
13969
14004
  d.log(`${ref(run)}: parked with a question — the session waits for the answer (${runner.watchHint(run.handle)})`);
13970
- holdParked(d, runner, run, question);
14005
+ holdParked(d, runner, run, question, shown);
13971
14006
  finished.push({ item: run.item, node: run.node, outcome: { kind: "park", question } });
13972
14007
  keep.push(run);
13973
14008
  continue;
@@ -14002,7 +14037,7 @@ async function tickOnce(d) {
14002
14037
  }
14003
14038
  d.log(`${ref(run)}: ${why}, tries spent — parked for a person (${runner.watchHint(run.handle)})`);
14004
14039
  run.provider = undefined;
14005
- holdParked(d, runner, run, question);
14040
+ holdParked(d, runner, run, question, shown);
14006
14041
  finished.push({ item: run.item, node: run.node, outcome });
14007
14042
  keep.push(run);
14008
14043
  continue;
@@ -14023,7 +14058,7 @@ async function tickOnce(d) {
14023
14058
  continue;
14024
14059
  }
14025
14060
  d.log(asked ? `${ref(run)}: stopped on a question with no PR — parked with its last message as the question (${runner.watchHint(run.handle)})` : `${ref(run)}: idle ${d.idleMin} min with no PR — parked with its last message as the question (${runner.watchHint(run.handle)})`);
14026
- holdParked(d, runner, run, question);
14061
+ holdParked(d, runner, run, question, shown);
14027
14062
  finished.push({ item: run.item, node: run.node, outcome });
14028
14063
  keep.push(run);
14029
14064
  continue;
@@ -14364,6 +14399,10 @@ function permissionKey(answer) {
14364
14399
  const m = /^\s*(\d|esc)\s*·/i.exec(answer);
14365
14400
  return m ? m[1].toLowerCase() : null;
14366
14401
  }
14402
+ function dialogPrint(dialog) {
14403
+ return [dialog.text, ...dialog.options.map((o) => `${o.key} · ${o.label}`)].join(`
14404
+ `);
14405
+ }
14367
14406
  async function settlePermission(d, runner, run, shown) {
14368
14407
  const perms = permissionsOf(d.source);
14369
14408
  if (!perms || !run.runId)
@@ -14373,19 +14412,20 @@ async function settlePermission(d, runner, run, shown) {
14373
14412
  const dialog = runner.readsSession ? permissionDialog(shown) : null;
14374
14413
  const p = run.permission;
14375
14414
  if (p) {
14376
- const gone = runner.readsSession && (p.onScreen ? !dialog : (run.seenAt ?? 0) > (p.pressedAt ?? p.at));
14415
+ const gone = runner.readsSession && (p.onScreen ? !dialog || p.dialog !== undefined && dialogPrint(dialog) !== p.dialog || p.pressedOn !== undefined && fingerprint(shown) !== p.pressedOn : (run.seenAt ?? 0) > (p.pressedAt ?? p.at));
14377
14416
  const answered = () => {
14378
14417
  run.permission = undefined;
14379
14418
  run.answeredAt = d.now();
14380
14419
  if (markFile)
14381
14420
  clearPermissionMark(markFile);
14382
14421
  };
14422
+ const next = (held) => dialog ? settlePermission(d, runner, run, shown) : held;
14383
14423
  if (p.gaveUp) {
14384
14424
  if (!gone)
14385
14425
  return true;
14386
14426
  d.log(`${at}: the permission dialog was answered in the terminal`);
14387
14427
  answered();
14388
- return false;
14428
+ return next(false);
14389
14429
  }
14390
14430
  let now;
14391
14431
  try {
@@ -14403,18 +14443,23 @@ async function settlePermission(d, runner, run, shown) {
14403
14443
  if (!pressed)
14404
14444
  return true;
14405
14445
  if (pressed === "key") {
14406
- run.permission = { ...p, pressedAt: d.now(), presses: 1 };
14446
+ run.permission = {
14447
+ ...p,
14448
+ pressedAt: d.now(),
14449
+ pressedOn: fingerprint(shown),
14450
+ presses: 1
14451
+ };
14407
14452
  return true;
14408
14453
  }
14409
14454
  }
14410
14455
  answered();
14411
- return true;
14456
+ return gone ? next(true) : true;
14412
14457
  }
14413
14458
  const key = permissionKey(now.answer) ?? now.answer;
14414
14459
  if (gone) {
14415
14460
  d.log(`${at}: the permission is answered — pressed ${key}; the session goes on`);
14416
14461
  answered();
14417
- return false;
14462
+ return next(false);
14418
14463
  }
14419
14464
  const presses = p.presses ?? 1;
14420
14465
  if (presses >= PRESS_TRIES) {
@@ -14430,7 +14475,12 @@ async function settlePermission(d, runner, run, shown) {
14430
14475
  return true;
14431
14476
  }
14432
14477
  if (pressAnswer(d, runner, run, p.options, now.answer) === "key")
14433
- run.permission = { ...p, pressedAt: d.now(), presses: presses + 1 };
14478
+ run.permission = {
14479
+ ...p,
14480
+ pressedAt: d.now(),
14481
+ pressedOn: fingerprint(shown),
14482
+ presses: presses + 1
14483
+ };
14434
14484
  return true;
14435
14485
  }
14436
14486
  if (now?.state !== "open" && now?.state !== "escalated") {
@@ -14447,7 +14497,7 @@ async function settlePermission(d, runner, run, shown) {
14447
14497
  }
14448
14498
  d.log(`${at}: the permission dialog closed before an answer — cleared`);
14449
14499
  run.permission = undefined;
14450
- return false;
14500
+ return next(false);
14451
14501
  }
14452
14502
  let mark = markFile ? readPermissionMark(markFile) : null;
14453
14503
  if (mark && markFile && (!isToolPermission(mark.message) || runner.readsSession && choicePicker(shown))) {
@@ -14473,7 +14523,7 @@ async function settlePermission(d, runner, run, shown) {
14473
14523
  decisionId: opened.decisionId,
14474
14524
  options,
14475
14525
  at: d.now(),
14476
- ...dialog ? { onScreen: true } : {}
14526
+ ...dialog ? { onScreen: true, dialog: dialogPrint(dialog) } : {}
14477
14527
  };
14478
14528
  if (markFile)
14479
14529
  clearPermissionMark(markFile);
@@ -14739,8 +14789,12 @@ function stopOpen(d, run, runner) {
14739
14789
  d.log(`${ref(run)}: could not stop the left-open session: ${message(e)}`);
14740
14790
  }
14741
14791
  }
14742
- function holdParked(d, runner, run, question) {
14743
- run.parked = { question, at: d.now(), ...run.seen ? { screen: run.seen } : {} };
14792
+ function holdParked(d, runner, run, question, shown) {
14793
+ run.parked = {
14794
+ question,
14795
+ at: d.now(),
14796
+ ...runner.readsSession ? { words: wordsPrint(shown) } : {}
14797
+ };
14744
14798
  run.leaseUntil = undefined;
14745
14799
  setLane(d, runner, run.handle, "parked");
14746
14800
  }
@@ -14749,18 +14803,19 @@ async function answeredInSession(d, runner, run) {
14749
14803
  const resumes = resumesOf(d.source);
14750
14804
  if (!resumes || !runner.readsSession || !run.runId)
14751
14805
  return false;
14752
- let print;
14806
+ let shown;
14753
14807
  try {
14754
- print = fingerprint(runner.read(run.handle));
14808
+ shown = runner.read(run.handle);
14755
14809
  } catch {
14756
14810
  return false;
14757
14811
  }
14758
- if (print === parked.screen) {
14812
+ const words = wordsPrint(shown);
14813
+ if (words === parked.words) {
14759
14814
  parked.still = true;
14760
14815
  return false;
14761
14816
  }
14762
14817
  const moved = parked.still === true;
14763
- parked.screen = print;
14818
+ parked.words = words;
14764
14819
  parked.still = false;
14765
14820
  if (!moved)
14766
14821
  return false;
@@ -14778,7 +14833,7 @@ async function answeredInSession(d, runner, run) {
14778
14833
  stopAnswerer(d, run);
14779
14834
  const now = d.now();
14780
14835
  run.parked = undefined;
14781
- run.seen = print;
14836
+ run.seen = fingerprint(shown);
14782
14837
  run.seenAt = now;
14783
14838
  run.seenTicks = 0;
14784
14839
  run.answeredAt = now;
@@ -15361,6 +15416,8 @@ function ciNudge(url, failing) {
15361
15416
  return `CI failed on your PR (${failing.join(", ")}): ${url}. Read the failing logs (gh pr checks ${url}), fix the cause, run the repo's gate, and push to the same branch.`;
15362
15417
  }
15363
15418
  var SELF_NUDGES = 2;
15419
+ var FEEDBACK_REREAD_MS = 10 * 60000;
15420
+ var CONFLICT_RECHECK_MS = 10 * 60000;
15364
15421
  function reviewNudge(url, count) {
15365
15422
  return `New review feedback on your PR (${count} ${count === 1 ? "comment" : "comments"} from people so far): ${url}. Read all of it (gh pr view ${url} --comments, and the inline threads), answer each comment on its own thread with the fix or a reasoned reply, push, and leave the threads for the reviewer to resolve. End every comment and reply you post on the PR with the line ${ENGINE_MARK} so the engine knows it is yours and not the reviewer's.`;
15366
15423
  }
@@ -15401,9 +15458,12 @@ async function judgeLook(d, run, view) {
15401
15458
  }
15402
15459
  async function follow(d, state, run, claim, runner, finished, attention, changes, decided) {
15403
15460
  const pr = run.pr;
15404
- const view = d.prView(d.exec, run.repoPath, pr.url);
15461
+ const last = pr.feedbackRead && d.now() - pr.feedbackRead.at < FEEDBACK_REREAD_MS ? pr.feedbackRead : undefined;
15462
+ const view = d.prView(d.exec, run.repoPath, pr.url, last);
15405
15463
  if (!view)
15406
15464
  return true;
15465
+ if (view.read && view.read !== last)
15466
+ pr.feedbackRead = { ...view.read, at: d.now() };
15407
15467
  await judgeLook(d, run, view);
15408
15468
  if (view.state === "MERGED") {
15409
15469
  await dropPermission(d, run, "the PR merged");
@@ -15457,6 +15517,7 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
15457
15517
  else if (view.mergeable === "MERGEABLE") {
15458
15518
  pr.resolving = undefined;
15459
15519
  pr.conflicted = undefined;
15520
+ pr.conflictCheck = undefined;
15460
15521
  }
15461
15522
  if (view.feedback > pr.feedback) {
15462
15523
  const what = `${view.feedback - pr.feedback} new review comment(s)`;
@@ -15696,10 +15757,15 @@ async function judgeConflict(d, state, run, view, attention, decided) {
15696
15757
  const pr = run.pr;
15697
15758
  if (!d.mergeTree)
15698
15759
  return;
15760
+ const check = pr.conflictCheck;
15761
+ if (view.head && check?.head === view.head && !pr.resolving && d.now() - check.at < CONFLICT_RECHECK_MS)
15762
+ return;
15699
15763
  const base = view.base || d.defaultBranch(d.exec, run.repoPath);
15700
15764
  const mt = d.mergeTree(d.exec, run.repoPath, base, run.branch);
15701
15765
  if (!mt)
15702
15766
  return;
15767
+ if (view.head)
15768
+ pr.conflictCheck = { head: view.head, at: d.now() };
15703
15769
  if (pr.resolving === mt.sha && mt.files.length > 0 && !sessionRuns(d, run.handle)) {
15704
15770
  pr.resolving = undefined;
15705
15771
  const what = `${pr.url} still conflicts with ${base} on ${mt.files.join(", ")} after its session had the brief`;
@@ -17362,7 +17428,7 @@ async function fetchVersion(o) {
17362
17428
  const platform = o.platform ?? process.platform;
17363
17429
  const arch = o.arch ?? process.arch;
17364
17430
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
17365
- const running = o.running ?? "0.1.52";
17431
+ const running = o.running ?? "0.1.54";
17366
17432
  if (!publicKey)
17367
17433
  return { ok: false, why: "no release key yet" };
17368
17434
  if (!VERSION_RE2.test(o.version))
@@ -18946,7 +19012,7 @@ function engineClient(o, org) {
18946
19012
  return fetchClient({
18947
19013
  ...src,
18948
19014
  timeoutMs: ENGINE_TIMEOUT_MS,
18949
- version: "0.1.52"
19015
+ version: "0.1.54"
18950
19016
  });
18951
19017
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
18952
19018
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -19011,10 +19077,10 @@ function engineToken(o, org) {
19011
19077
  }
19012
19078
  var withToken = (token, now) => token ? { token, tokenNow: now } : {};
19013
19079
  function engineDeps(o, flags, source, runner, stateName) {
19014
- const exec = guardGit(o.exec ?? defaultExec);
19080
+ const log = o.log ?? console.log;
19081
+ const exec = logTimeouts(guardGit(o.exec ?? defaultExec), log);
19015
19082
  const cwd = o.cwd ?? process.cwd();
19016
19083
  const exists = o.exists ?? realExists;
19017
- const log = o.log ?? console.log;
19018
19084
  const stateDir = engineStateDir(o, flags.org, flags.seat);
19019
19085
  const file = statePath(stateDir, stateName);
19020
19086
  const mcpOrg = flags.org && engineOrg(o, flags.org).seated ? flags.org : undefined;
@@ -19237,7 +19303,7 @@ async function driveEngine(o, flags, deps, until = {}) {
19237
19303
  runner: deps.runner.name,
19238
19304
  atOnce: flags.atOnce,
19239
19305
  interval: flags.interval,
19240
- cliVersion: "0.1.52"
19306
+ cliVersion: "0.1.54"
19241
19307
  };
19242
19308
  const beat = (checkedAt) => {
19243
19309
  const m = deps.machine;
@@ -19429,7 +19495,7 @@ async function drive(o, flags, deps, until, beat) {
19429
19495
  var SEAT_CHANGED_EXIT = 3;
19430
19496
  function engineRunner(o, flags) {
19431
19497
  const runnerFor = o.runnerFor ?? runnerFor2;
19432
- return runnerFor(flags.runner, guardGit(o.exec ?? defaultExec), {
19498
+ return runnerFor(flags.runner, logTimeouts(guardGit(o.exec ?? defaultExec), o.log ?? console.log), {
19433
19499
  ...flags.runnerCmd ? { command: flags.runnerCmd } : {}
19434
19500
  });
19435
19501
  }
@@ -21094,7 +21160,7 @@ function engineClient2(o, org) {
21094
21160
  return fetchClient({
21095
21161
  ...src,
21096
21162
  timeoutMs: ENGINE_TIMEOUT_MS2,
21097
- version: "0.1.52"
21163
+ version: "0.1.54"
21098
21164
  });
21099
21165
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
21100
21166
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -21181,10 +21247,10 @@ function engineCredential(o) {
21181
21247
  })?.kind;
21182
21248
  }
21183
21249
  function engineDeps2(o, flags, source, runner, stateName) {
21184
- const exec = guardGit(o.exec ?? defaultExec);
21250
+ const log = o.log ?? console.log;
21251
+ const exec = logTimeouts(guardGit(o.exec ?? defaultExec), log);
21185
21252
  const cwd = o.cwd ?? process.cwd();
21186
21253
  const exists = o.exists ?? realExists2;
21187
- const log = o.log ?? console.log;
21188
21254
  const stateDir = engineStateDir2(o, flags.org, flags.seat);
21189
21255
  const file = statePath(stateDir, stateName);
21190
21256
  const mcpOrg = flags.org && engineOrg2(o, flags.org).seated ? flags.org : undefined;
@@ -21420,7 +21486,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21420
21486
  runner: deps.runner.name,
21421
21487
  atOnce: flags.atOnce,
21422
21488
  interval: flags.interval,
21423
- cliVersion: "0.1.52"
21489
+ cliVersion: "0.1.54"
21424
21490
  };
21425
21491
  const beat = (checkedAt) => {
21426
21492
  const m = deps.machine;
@@ -21612,7 +21678,7 @@ async function drive2(o, flags, deps, until, beat) {
21612
21678
  var SEAT_CHANGED_EXIT2 = 3;
21613
21679
  function engineRunner2(o, flags) {
21614
21680
  const runnerFor = o.runnerFor ?? runnerFor2;
21615
- return runnerFor(flags.runner, guardGit(o.exec ?? defaultExec), {
21681
+ return runnerFor(flags.runner, logTimeouts(guardGit(o.exec ?? defaultExec), o.log ?? console.log), {
21616
21682
  ...flags.runnerCmd ? { command: flags.runnerCmd } : {}
21617
21683
  });
21618
21684
  }
@@ -23508,7 +23574,7 @@ function contextFor(deps) {
23508
23574
  ...src,
23509
23575
  fetchFn: deps.fetchFn,
23510
23576
  timeoutMs: deps.timeoutMs,
23511
- version: "0.1.52"
23577
+ version: "0.1.54"
23512
23578
  }), deps.home, now) : null;
23513
23579
  return {
23514
23580
  channel: "code",
@@ -26095,14 +26161,17 @@ async function sledOff(client, key, json, log, error) {
26095
26161
  return 0;
26096
26162
  }
26097
26163
  const k = typeof r.json?.key === "string" ? r.json.key : key;
26164
+ const why = typeof r.json?.why === "string" && r.json.why.trim() ? r.json.why : null;
26098
26165
  if (r.json?.changed === false) {
26099
- log(`${k} was not on a sled`);
26100
- return 0;
26166
+ error(why ?? `${k} was not on a sled`);
26167
+ return 1;
26101
26168
  }
26102
26169
  log(`${k} is off your sled — back in the pool`);
26103
26170
  const running = typeof r.json?.running === "number" ? r.json.running : 0;
26104
26171
  if (running > 0)
26105
26172
  log(`${running} running node${running === 1 ? "" : "s"} stopped`);
26173
+ if (why)
26174
+ log(why);
26106
26175
  return 0;
26107
26176
  }
26108
26177
 
@@ -27623,7 +27692,7 @@ var realPidAlive4 = (pid) => {
27623
27692
  };
27624
27693
  function machineClient(home, env) {
27625
27694
  const src = meterSource(home, undefined, { env });
27626
- return src ? fetchClient({ ...src, version: "0.1.52" }) : null;
27695
+ return src ? fetchClient({ ...src, version: "0.1.54" }) : null;
27627
27696
  }
27628
27697
  async function noteAnswer2(home, a, now) {
27629
27698
  try {
@@ -28093,15 +28162,29 @@ var readConfig2 = (home) => {
28093
28162
 
28094
28163
  // src/runners/exec.ts
28095
28164
  import { spawn as spawn3, spawnSync as spawnSync10 } from "node:child_process";
28165
+ var EXEC_TIMEOUT_MS2 = 2 * 60000;
28166
+ var EXEC_TIMEOUT_STATUS2 = 124;
28167
+ var timedOut2 = (r) => r.error?.code === "ETIMEDOUT";
28096
28168
  var defaultExec4 = (cmd, args, o) => {
28169
+ const timeoutMs = o?.timeoutMs ?? EXEC_TIMEOUT_MS2;
28097
28170
  const r = spawnSync10(cmd, [...args], {
28098
28171
  cwd: o?.cwd,
28099
28172
  input: o?.input,
28100
28173
  env: o?.env ? { ...process.env, ...o.env } : undefined,
28101
28174
  encoding: "utf8",
28102
28175
  shell: false,
28103
- maxBuffer: 16 * 1024 * 1024
28176
+ maxBuffer: 16 * 1024 * 1024,
28177
+ timeout: timeoutMs,
28178
+ killSignal: "SIGKILL"
28104
28179
  });
28180
+ if (timedOut2(r)) {
28181
+ return {
28182
+ status: EXEC_TIMEOUT_STATUS2,
28183
+ stdout: r.stdout ?? "",
28184
+ stderr: `${cmd}: timed out after ${timeoutMs / 1000}s and was killed${r.stderr ? `
28185
+ ${r.stderr}` : ""}`
28186
+ };
28187
+ }
28105
28188
  if (r.error) {
28106
28189
  return { status: 127, stdout: r.stdout ?? "", stderr: `${cmd}: ${r.error.message}` };
28107
28190
  }
@@ -28469,7 +28552,7 @@ var main = async (argv) => {
28469
28552
  now: Date.now(),
28470
28553
  stderr: (line) => console.error(line),
28471
28554
  isTTY: !!process.stderr.isTTY,
28472
- version: "0.1.52"
28555
+ version: "0.1.54"
28473
28556
  });
28474
28557
  switch (cmd) {
28475
28558
  case "update":
@@ -28702,7 +28785,7 @@ ${renderHelpText(argv[1])}`);
28702
28785
  case "version":
28703
28786
  case "--version":
28704
28787
  case "-v":
28705
- console.log("sleds 0.1.52");
28788
+ console.log("sleds 0.1.54");
28706
28789
  return 0;
28707
28790
  default:
28708
28791
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "SLEDS CLI — the cost and limits of AI agent work, decided at the wall. Point Claude Code at a SLEDS proxy; estimate before you run; hold, switch model, or run overnight when you hit a limit.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",