@sleds/cli 0.1.51 → 0.1.53

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 +346 -63
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -5689,11 +5689,20 @@ var init_copy = __esm(() => {
5689
5689
  });
5690
5690
 
5691
5691
  // ../../packages/shared/src/slack/agent.ts
5692
+ var REPLY_RULES;
5692
5693
  var init_agent = __esm(() => {
5693
5694
  init_intake();
5694
5695
  init_brief();
5695
5696
  init_copy();
5696
5697
  init_mention();
5698
+ REPLY_RULES = [
5699
+ "How to answer this reply:",
5700
+ '- If it answers nothing and changes nothing — thanks, "noted", "copy", "will do", or people talking to each other — set quiet to true and post nothing.',
5701
+ "- Never ask a question the thread already asked. If it is still open, say only what is still needed, once, in a few words.",
5702
+ "- If someone in the thread already gave the cause or a fix, do not ask the poster for details it makes moot. Say in one line what is left, or set quiet.",
5703
+ "- Keep it to what changed and what is next. No recap of the ask."
5704
+ ].join(`
5705
+ `);
5697
5706
  });
5698
5707
 
5699
5708
  // ../../packages/shared/src/slack/channel.ts
@@ -8545,7 +8554,13 @@ function parseParked(v) {
8545
8554
  ...typeof a.effort === "string" ? { effort: a.effort } : {}
8546
8555
  };
8547
8556
  }
8548
- return { question: v.question, at: v.at, ...answerer ? { answerer } : {} };
8557
+ return {
8558
+ question: v.question,
8559
+ at: v.at,
8560
+ ...typeof v.screen === "string" ? { screen: v.screen } : {},
8561
+ ...v.still === true ? { still: true } : {},
8562
+ ...answerer ? { answerer } : {}
8563
+ };
8549
8564
  }
8550
8565
  function parseRun(v) {
8551
8566
  if (!isObject8(v))
@@ -8585,7 +8600,11 @@ function parseRun(v) {
8585
8600
  decisionId: v.permission.decisionId,
8586
8601
  options: Array.isArray(v.permission.options) ? v.permission.options.filter((x) => typeof x === "string") : [],
8587
8602
  at: v.permission.at,
8588
- ...v.permission.onScreen === true ? { onScreen: true } : {}
8603
+ ...v.permission.onScreen === true ? { onScreen: true } : {},
8604
+ ...typeof v.permission.dialog === "string" ? { dialog: v.permission.dialog } : {},
8605
+ ...typeof v.permission.pressedAt === "number" ? { pressedAt: v.permission.pressedAt } : {},
8606
+ ...typeof v.permission.presses === "number" ? { presses: v.permission.presses } : {},
8607
+ ...v.permission.gaveUp === true ? { gaveUp: true } : {}
8589
8608
  }
8590
8609
  } : {},
8591
8610
  ...isObject8(v.provider) && typeof v.provider.why === "string" && typeof v.provider.since === "number" && typeof v.provider.tries === "number" ? { provider: { why: v.provider.why, since: v.provider.since, tries: v.provider.tries } } : {},
@@ -8597,12 +8616,21 @@ function parseRun(v) {
8597
8616
  feedback: typeof v.pr.feedback === "number" ? v.pr.feedback : 0,
8598
8617
  ...typeof v.pr.authorFeedback === "number" ? { authorFeedback: v.pr.authorFeedback } : {},
8599
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
+ } : {},
8600
8627
  ...v.pr.clear === true ? { clear: true } : {},
8601
8628
  ...typeof v.pr.ci === "string" ? { ci: v.pr.ci } : {},
8602
8629
  ...typeof v.pr.review === "string" ? { review: v.pr.review } : {},
8603
8630
  ...typeof v.pr.posted === "string" ? { posted: v.pr.posted } : {},
8604
8631
  ...Array.isArray(v.pr.failedHeads) ? { failedHeads: v.pr.failedHeads.filter((x) => typeof x === "string") } : {},
8605
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 } } : {},
8606
8634
  ...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
8607
8635
  ...conflicted ? { conflicted } : {},
8608
8636
  ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
@@ -10741,15 +10769,36 @@ var claudeAddDirFlag = (mainCheckout) => mainCheckout ? ` --add-dir=${shq(mainCh
10741
10769
  var shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
10742
10770
 
10743
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
+ };
10744
10782
  var defaultExec = (cmd, args, o) => {
10783
+ const timeoutMs = o?.timeoutMs ?? EXEC_TIMEOUT_MS;
10745
10784
  const r = spawnSync3(cmd, [...args], {
10746
10785
  cwd: o?.cwd,
10747
10786
  input: o?.input,
10748
10787
  env: o?.env ? { ...process.env, ...o.env } : undefined,
10749
10788
  encoding: "utf8",
10750
10789
  shell: false,
10751
- maxBuffer: 16 * 1024 * 1024
10790
+ maxBuffer: 16 * 1024 * 1024,
10791
+ timeout: timeoutMs,
10792
+ killSignal: "SIGKILL"
10752
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
+ }
10753
10802
  if (r.error) {
10754
10803
  return { status: 127, stdout: r.stdout ?? "", stderr: `${cmd}: ${r.error.message}` };
10755
10804
  }
@@ -11243,6 +11292,8 @@ var orcaLauncher = (o) => `#!/bin/sh
11243
11292
  ${execLine(agentShellCommand(o))}
11244
11293
  `;
11245
11294
  var orcaCommand = (launcher) => `sh ${shq2(launcher)}; exit`;
11295
+ var orcaRequestId = (env, text) => str8(obj(obj(env?.error)?.data)?.orchestrationRequestId) ?? text.match(/--retry-request ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i)?.[1] ?? null;
11296
+ var ESC = "\x1B";
11246
11297
  var GONE = /exit|clos|gone|dead|stopp|kill/i;
11247
11298
  var orcaTerminalLive = (status, stdout) => {
11248
11299
  const env = parseOrcaJson(stdout);
@@ -11327,6 +11378,7 @@ function orcaRunner(exec, deps = {}) {
11327
11378
  }
11328
11379
  return env;
11329
11380
  };
11381
+ const retries = new Map;
11330
11382
  return {
11331
11383
  name: "orca",
11332
11384
  makesWorktree: true,
@@ -11378,10 +11430,44 @@ function orcaRunner(exec, deps = {}) {
11378
11430
  return r.status === 0 ? r.stdout : "";
11379
11431
  },
11380
11432
  send(h, text) {
11381
- const r = exec("orca", ["terminal", "send", "--terminal", h.id, "--text", text, "--enter"]);
11382
- if (r.status !== 0) {
11383
- throw new Error(`orca terminal send ${h.id} failed: ${r.stderr.trim() || `exit ${r.status}`}`);
11433
+ const key = `${h.id}
11434
+ ${text}`;
11435
+ const attempt = (retry) => {
11436
+ const r = exec("orca", [
11437
+ "terminal",
11438
+ "send",
11439
+ "--terminal",
11440
+ h.id,
11441
+ "--text",
11442
+ text,
11443
+ "--enter",
11444
+ ...retry ? ["--retry-request", retry] : [],
11445
+ "--json"
11446
+ ]);
11447
+ const env = parseOrcaJson(r.stdout);
11448
+ const ok = r.status === 0 && env?.ok !== false;
11449
+ return { r, env, ok, id: ok ? null : orcaRequestId(env, `${r.stderr}
11450
+ ${r.stdout}`) };
11451
+ };
11452
+ const prior = retries.get(key);
11453
+ let a = attempt(prior);
11454
+ if (!a.ok && a.id && a.id !== prior)
11455
+ a = attempt(a.id);
11456
+ if (a.ok) {
11457
+ retries.delete(key);
11458
+ return;
11384
11459
  }
11460
+ if (a.id)
11461
+ retries.set(key, a.id);
11462
+ else
11463
+ retries.delete(key);
11464
+ const why = orcaError(a.env) ?? (a.r.stderr.trim() || `exit ${a.r.status}`);
11465
+ throw new Error(`orca terminal send ${h.id} failed: ${why}`);
11466
+ },
11467
+ sendKey(h, key) {
11468
+ if (!isRunnerKey(key))
11469
+ throw new Error(`orca terminal send ${h.id}: no key ${JSON.stringify(key)}`);
11470
+ orca(["terminal", "send", "--terminal", h.id, "--text", key === "esc" ? ESC : key, "--json"], `orca terminal send ${h.id} (key ${key})`);
11385
11471
  },
11386
11472
  stop(h) {
11387
11473
  exec("orca", ["terminal", "close", "--terminal", h.id]);
@@ -11449,7 +11535,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11449
11535
  name: flags.name,
11450
11536
  os: host.os ?? process.platform,
11451
11537
  runner: flags.runner,
11452
- version: host.version ?? "0.1.51" ?? "dev",
11538
+ version: host.version ?? "0.1.53" ?? "dev",
11453
11539
  running: live.length,
11454
11540
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11455
11541
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -11849,6 +11935,7 @@ function parsePrView(stdout) {
11849
11935
  feedback: 0,
11850
11936
  authorFeedback: 0,
11851
11937
  draft: o.isDraft === true,
11938
+ updatedAt: typeof o.updatedAt === "string" ? o.updatedAt : "",
11852
11939
  ...mock ? { mock } : {},
11853
11940
  ...typeof o.headRefOid === "string" && o.headRefOid ? { head: o.headRefOid } : {},
11854
11941
  ...typeof mergeCommit === "string" && mergeCommit ? { mergeCommit } : {}
@@ -11897,20 +11984,24 @@ function prFeedback(exec, repoPath, url, author) {
11897
11984
  }
11898
11985
  return total;
11899
11986
  }
11900
- function prView(exec, repoPath, url) {
11987
+ function prView(exec, repoPath, url, last) {
11901
11988
  try {
11902
11989
  const r = exec("gh", [
11903
11990
  "pr",
11904
11991
  "view",
11905
11992
  url,
11906
11993
  "--json",
11907
- "state,author,statusCheckRollup,reviewDecision,comments,reviews,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body"
11994
+ "state,author,statusCheckRollup,reviewDecision,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body,updatedAt"
11908
11995
  ], { cwd: repoPath });
11909
11996
  const view = r.status === 0 ? parsePrView(r.stdout) : null;
11910
11997
  if (!view || view.state !== "OPEN")
11911
11998
  return view;
11912
- const feedback = prFeedback(exec, repoPath, url, view.author);
11913
- 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;
11914
12005
  } catch {
11915
12006
  return null;
11916
12007
  }
@@ -12454,6 +12545,10 @@ function permissionsOf(s) {
12454
12545
  const p = s;
12455
12546
  return p && typeof p.openPermission === "function" && typeof p.clearPermission === "function" && typeof p.decisionOf === "function" ? s : null;
12456
12547
  }
12548
+ function resumesOf(s) {
12549
+ const r = s;
12550
+ return r && typeof r.resumeParked === "function" ? s : null;
12551
+ }
12457
12552
  var FAIL_LOG_MAX = 2000;
12458
12553
  var UNREACHABLE3 = "SLEDS unreachable";
12459
12554
  function keysIn(states, state) {
@@ -12638,6 +12733,9 @@ function profileOf(r) {
12638
12733
  const atOnce = num5(p.atOnce);
12639
12734
  if (atOnce !== undefined && atOnce >= 1)
12640
12735
  out.atOnce = Math.round(atOnce);
12736
+ const seatAtOnce = num5(r.seatAtOnce);
12737
+ if (seatAtOnce !== undefined && Number.isInteger(seatAtOnce) && seatAtOnce >= 1)
12738
+ out.seatAtOnce = seatAtOnce;
12641
12739
  if (typeof p.mcpStrict === "boolean")
12642
12740
  out.mcpStrict = p.mcpStrict;
12643
12741
  const holdCpu = num5(p.holdCpu);
@@ -12942,26 +13040,28 @@ function sledsSource(client, o) {
12942
13040
  });
12943
13041
  if (r?.status !== 409)
12944
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";
12945
13051
  if (claim.kind === "draft") {
12946
- try {
12947
- const route = await routeOf(claim.item);
12948
- const filed = Array.isArray(route?.nodes) && route.nodes.length > 0;
12949
- if (filed || isObject8(route?.proposed))
12950
- return "done";
12951
- if (isObject8(route?.draftParked))
12952
- return "parked";
12953
- } 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";
12954
13057
  return "lost";
12955
13058
  }
12956
- try {
12957
- const route = await routeOf(claim.item);
12958
- const nodes = Array.isArray(route?.nodes) ? route.nodes.filter(isObject8) : [];
12959
- const state = nodes.find((n) => n.key === claim.node)?.state;
12960
- if (state === "parked")
12961
- return "parked";
12962
- if (state === "pr" || state === "landed" || state === "skipped")
12963
- return "done";
12964
- } 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";
12965
13065
  return "lost";
12966
13066
  },
12967
13067
  async report(claim, outcome, by) {
@@ -13100,6 +13200,18 @@ function sledsSource(client, o) {
13100
13200
  throw new Error(serverMessage(r));
13101
13201
  return r.json.cleared === true;
13102
13202
  },
13203
+ async resumeParked(runId, why, engine, leaseMin) {
13204
+ const r = await client.postFull("/proxy/run", { runId, engine, leaseMin, resumed: { why } });
13205
+ if (!r)
13206
+ throw new Error(`${UNREACHABLE3} — the resume not recorded`);
13207
+ if (r.status >= 300 || !r.json || r.json.ok === false)
13208
+ throw new Error(serverMessage(r));
13209
+ const leaseUntil = typeof r.json.leaseUntil === "number" ? r.json.leaseUntil : undefined;
13210
+ return {
13211
+ resumed: r.json.resumed === true,
13212
+ ...leaseUntil !== undefined ? { leaseUntil } : {}
13213
+ };
13214
+ },
13103
13215
  async decisionOf(item, decisionId) {
13104
13216
  const route = await routeOf(item);
13105
13217
  const decisions = Array.isArray(route?.decisions) ? route.decisions.filter(isObject8) : [];
@@ -13445,6 +13557,7 @@ function fastForwardUntouched(exec, path, base, log) {
13445
13557
  log?.(`${path}: fast-forwarded to origin/${base}`);
13446
13558
  return true;
13447
13559
  }
13560
+ var INSTALL_TIMEOUT_MS = 15 * 60000;
13448
13561
  var MANAGERS = new Set(["pnpm", "npm", "yarn", "bun"]);
13449
13562
  var INSTALL_VERBS = new Set(["install", "i", "ci"]);
13450
13563
  function installArgv(command) {
@@ -13536,7 +13649,7 @@ function ensureInstalled(exec, path, fs, o = {}) {
13536
13649
  return null;
13537
13650
  const [cmd, ...args] = argv;
13538
13651
  o.log?.(`${path}: no node_modules — running ${argv.join(" ")}`);
13539
- const r = exec(cmd, args, { cwd: path });
13652
+ const r = exec(cmd, args, { cwd: path, timeoutMs: INSTALL_TIMEOUT_MS });
13540
13653
  const ok = r.status === 0;
13541
13654
  if (!ok)
13542
13655
  o.log?.(`${path}: ${argv.join(" ")} failed (exit ${r.status}) — the session starts anyway: ${(r.stderr || r.stdout).trim().split(`
@@ -13692,6 +13805,7 @@ async function tick(deps) {
13692
13805
  deps.providerHold = d.providerHold;
13693
13806
  deps.draining = d.draining;
13694
13807
  deps.atOnce = d.atOnce;
13808
+ deps.seatAtOnceNoted = d.seatAtOnceNoted;
13695
13809
  deps.runner = d.runner;
13696
13810
  deps.runnerRefused = d.runnerRefused;
13697
13811
  }
@@ -13763,6 +13877,10 @@ async function tickOnce(d) {
13763
13877
  try {
13764
13878
  open = runner.status(run.handle) === "running";
13765
13879
  } catch {}
13880
+ if (open && await answeredInSession(d, runner, run)) {
13881
+ keep.push(run);
13882
+ continue;
13883
+ }
13766
13884
  if (open || liveAnswerer(run))
13767
13885
  keep.push(run);
13768
13886
  else
@@ -14278,6 +14396,10 @@ function permissionKey(answer) {
14278
14396
  const m = /^\s*(\d|esc)\s*·/i.exec(answer);
14279
14397
  return m ? m[1].toLowerCase() : null;
14280
14398
  }
14399
+ function dialogPrint(dialog) {
14400
+ return [dialog.text, ...dialog.options.map((o) => `${o.key} · ${o.label}`)].join(`
14401
+ `);
14402
+ }
14281
14403
  async function settlePermission(d, runner, run, shown) {
14282
14404
  const perms = permissionsOf(d.source);
14283
14405
  if (!perms || !run.runId)
@@ -14287,7 +14409,21 @@ async function settlePermission(d, runner, run, shown) {
14287
14409
  const dialog = runner.readsSession ? permissionDialog(shown) : null;
14288
14410
  const p = run.permission;
14289
14411
  if (p) {
14290
- const gone = runner.readsSession && (p.onScreen ? !dialog : (run.seenAt ?? 0) > p.at);
14412
+ const gone = runner.readsSession && (p.onScreen ? !dialog || p.dialog !== undefined && dialogPrint(dialog) !== p.dialog : (run.seenAt ?? 0) > (p.pressedAt ?? p.at));
14413
+ const answered = () => {
14414
+ run.permission = undefined;
14415
+ run.answeredAt = d.now();
14416
+ if (markFile)
14417
+ clearPermissionMark(markFile);
14418
+ };
14419
+ const next = (held) => dialog ? settlePermission(d, runner, run, shown) : held;
14420
+ if (p.gaveUp) {
14421
+ if (!gone)
14422
+ return true;
14423
+ d.log(`${at}: the permission dialog was answered in the terminal`);
14424
+ answered();
14425
+ return next(false);
14426
+ }
14291
14427
  let now;
14292
14428
  try {
14293
14429
  now = await perms.decisionOf(run.item, p.decisionId);
@@ -14296,14 +14432,42 @@ async function settlePermission(d, runner, run, shown) {
14296
14432
  return true;
14297
14433
  }
14298
14434
  if (now?.state === "answered" && now.answer) {
14299
- if (gone)
14300
- d.log(`${at}: the permission was answered after its dialog closed — not pressed`);
14301
- else if (!pressAnswer(d, runner, run, p.options, now.answer))
14435
+ if (p.pressedAt === undefined) {
14436
+ if (gone)
14437
+ d.log(`${at}: the permission was answered after its dialog closed — not pressed`);
14438
+ else {
14439
+ const pressed = pressAnswer(d, runner, run, p.options, now.answer);
14440
+ if (!pressed)
14441
+ return true;
14442
+ if (pressed === "key") {
14443
+ run.permission = { ...p, pressedAt: d.now(), presses: 1 };
14444
+ return true;
14445
+ }
14446
+ }
14447
+ answered();
14448
+ return gone ? next(true) : true;
14449
+ }
14450
+ const key = permissionKey(now.answer) ?? now.answer;
14451
+ if (gone) {
14452
+ d.log(`${at}: the permission is answered — pressed ${key}; the session goes on`);
14453
+ answered();
14454
+ return next(false);
14455
+ }
14456
+ const presses = p.presses ?? 1;
14457
+ if (presses >= PRESS_TRIES) {
14458
+ const why = `pressed ${key} ${presses} times; the dialog stayed — answer it in the terminal`;
14459
+ try {
14460
+ await perms.clearPermission(run.runId, why, d.engine);
14461
+ } catch (e) {
14462
+ d.log(`${at}: could not clear the permission, trying next tick: ${message(e)}`);
14463
+ return true;
14464
+ }
14465
+ d.log(`${at}: ${why}`);
14466
+ run.permission = { ...p, gaveUp: true };
14302
14467
  return true;
14303
- run.permission = undefined;
14304
- run.answeredAt = d.now();
14305
- if (markFile)
14306
- clearPermissionMark(markFile);
14468
+ }
14469
+ if (pressAnswer(d, runner, run, p.options, now.answer) === "key")
14470
+ run.permission = { ...p, pressedAt: d.now(), presses: presses + 1 };
14307
14471
  return true;
14308
14472
  }
14309
14473
  if (now?.state !== "open" && now?.state !== "escalated") {
@@ -14320,7 +14484,7 @@ async function settlePermission(d, runner, run, shown) {
14320
14484
  }
14321
14485
  d.log(`${at}: the permission dialog closed before an answer — cleared`);
14322
14486
  run.permission = undefined;
14323
- return false;
14487
+ return next(false);
14324
14488
  }
14325
14489
  let mark = markFile ? readPermissionMark(markFile) : null;
14326
14490
  if (mark && markFile && (!isToolPermission(mark.message) || runner.readsSession && choicePicker(shown))) {
@@ -14346,13 +14510,14 @@ async function settlePermission(d, runner, run, shown) {
14346
14510
  decisionId: opened.decisionId,
14347
14511
  options,
14348
14512
  at: d.now(),
14349
- ...dialog ? { onScreen: true } : {}
14513
+ ...dialog ? { onScreen: true, dialog: dialogPrint(dialog) } : {}
14350
14514
  };
14351
14515
  if (markFile)
14352
14516
  clearPermissionMark(markFile);
14353
14517
  d.log(`${at}: stopped at a permission dialog — it waits for a person (${runner.watchHint(run.handle)})`);
14354
14518
  return true;
14355
14519
  }
14520
+ var PRESS_TRIES = 3;
14356
14521
  function pressAnswer(d, runner, run, options, answer) {
14357
14522
  const at = ref(run);
14358
14523
  const key = permissionKey(answer);
@@ -14363,13 +14528,15 @@ function pressAnswer(d, runner, run, options, answer) {
14363
14528
  runner.sendKey(run.handle, key);
14364
14529
  else
14365
14530
  runner.send(run.handle, key);
14531
+ if (runner.readsSession)
14532
+ return "key";
14366
14533
  d.log(`${at}: the permission is answered — pressed ${key}; the session goes on`);
14367
14534
  } else {
14368
14535
  runner.sendKey?.(run.handle, "esc");
14369
14536
  const note = runner.send(run.handle, answer);
14370
14537
  d.log(`${at}: the permission is answered in words — Esc, then typed in${note ? ` (${note})` : ""}`);
14371
14538
  }
14372
- return true;
14539
+ return "done";
14373
14540
  } catch (e) {
14374
14541
  d.log(`${at}: could not press the permission's answer, trying next tick: ${message(e)}`);
14375
14542
  return false;
@@ -14538,10 +14705,25 @@ async function rereadAtOnce(d) {
14538
14705
  try {
14539
14706
  next = await d.rereadAtOnce(profileOf2(d));
14540
14707
  } catch {}
14541
- if (next === null || next === d.atOnce)
14708
+ if (next !== null && next !== d.atOnce) {
14709
+ d.atOnce = next;
14710
+ d.log(`at once now ${next}, from your engine profile`);
14711
+ }
14712
+ await sayAtOnceDisagrees(d);
14713
+ }
14714
+ async function sayAtOnceDisagrees(d) {
14715
+ const seat = (await profileOf2(d)())?.seatAtOnce;
14716
+ if (seat === undefined)
14717
+ return;
14718
+ if (seat === d.atOnce) {
14719
+ d.seatAtOnceNoted = undefined;
14720
+ return;
14721
+ }
14722
+ const pair = `${d.atOnce}/${seat}`;
14723
+ if (d.seatAtOnceNoted === pair)
14542
14724
  return;
14543
- d.atOnce = next;
14544
- d.log(`at once now ${next}, from your engine profile`);
14725
+ d.seatAtOnceNoted = pair;
14726
+ d.log(seat < d.atOnce ? `at once ${d.atOnce} on the engine, but this seat allows ${seat} — the server refuses a claim past ${seat} on it; set it in Settings` : `at once ${d.atOnce} on the engine, but this seat allows ${seat} — the engine takes only ${d.atOnce}; set it in Settings`);
14545
14727
  }
14546
14728
  async function rereadRunner(d) {
14547
14729
  if (!d.rereadRunner)
@@ -14595,10 +14777,53 @@ function stopOpen(d, run, runner) {
14595
14777
  }
14596
14778
  }
14597
14779
  function holdParked(d, runner, run, question) {
14598
- run.parked = { question, at: d.now() };
14780
+ run.parked = { question, at: d.now(), ...run.seen ? { screen: run.seen } : {} };
14599
14781
  run.leaseUntil = undefined;
14600
14782
  setLane(d, runner, run.handle, "parked");
14601
14783
  }
14784
+ async function answeredInSession(d, runner, run) {
14785
+ const parked = run.parked;
14786
+ const resumes = resumesOf(d.source);
14787
+ if (!resumes || !runner.readsSession || !run.runId)
14788
+ return false;
14789
+ let print;
14790
+ try {
14791
+ print = fingerprint(runner.read(run.handle));
14792
+ } catch {
14793
+ return false;
14794
+ }
14795
+ if (print === parked.screen) {
14796
+ parked.still = true;
14797
+ return false;
14798
+ }
14799
+ const moved = parked.still === true;
14800
+ parked.screen = print;
14801
+ parked.still = false;
14802
+ if (!moved)
14803
+ return false;
14804
+ let r;
14805
+ try {
14806
+ r = await resumes.resumeParked(run.runId, "answered in the session", d.engine, d.leaseMin);
14807
+ } catch (e) {
14808
+ d.log(`${ref(run)}: the parked session moved on, but SLEDS did not hear it: ${message(e)}`);
14809
+ return false;
14810
+ }
14811
+ if (!r.resumed) {
14812
+ d.log(`${ref(run)}: the parked session moved on, but the node is no longer parked on its run`);
14813
+ return false;
14814
+ }
14815
+ stopAnswerer(d, run);
14816
+ const now = d.now();
14817
+ run.parked = undefined;
14818
+ run.seen = print;
14819
+ run.seenAt = now;
14820
+ run.seenTicks = 0;
14821
+ run.answeredAt = now;
14822
+ run.leaseUntil = r.leaseUntil ?? now + d.leaseMin * 60000;
14823
+ setLane(d, runner, run.handle, "working");
14824
+ d.log(`${ref(run)}: answered in the session — out of Needs you, running again`);
14825
+ return true;
14826
+ }
14602
14827
  function setLane(d, runner, h, lane) {
14603
14828
  try {
14604
14829
  runner.lane?.(h, lane);
@@ -15173,6 +15398,8 @@ function ciNudge(url, failing) {
15173
15398
  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.`;
15174
15399
  }
15175
15400
  var SELF_NUDGES = 2;
15401
+ var FEEDBACK_REREAD_MS = 10 * 60000;
15402
+ var CONFLICT_RECHECK_MS = 10 * 60000;
15176
15403
  function reviewNudge(url, count) {
15177
15404
  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.`;
15178
15405
  }
@@ -15213,9 +15440,12 @@ async function judgeLook(d, run, view) {
15213
15440
  }
15214
15441
  async function follow(d, state, run, claim, runner, finished, attention, changes, decided) {
15215
15442
  const pr = run.pr;
15216
- const view = d.prView(d.exec, run.repoPath, pr.url);
15443
+ const last = pr.feedbackRead && d.now() - pr.feedbackRead.at < FEEDBACK_REREAD_MS ? pr.feedbackRead : undefined;
15444
+ const view = d.prView(d.exec, run.repoPath, pr.url, last);
15217
15445
  if (!view)
15218
15446
  return true;
15447
+ if (view.read && view.read !== last)
15448
+ pr.feedbackRead = { ...view.read, at: d.now() };
15219
15449
  await judgeLook(d, run, view);
15220
15450
  if (view.state === "MERGED") {
15221
15451
  await dropPermission(d, run, "the PR merged");
@@ -15269,6 +15499,7 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
15269
15499
  else if (view.mergeable === "MERGEABLE") {
15270
15500
  pr.resolving = undefined;
15271
15501
  pr.conflicted = undefined;
15502
+ pr.conflictCheck = undefined;
15272
15503
  }
15273
15504
  if (view.feedback > pr.feedback) {
15274
15505
  const what = `${view.feedback - pr.feedback} new review comment(s)`;
@@ -15508,10 +15739,15 @@ async function judgeConflict(d, state, run, view, attention, decided) {
15508
15739
  const pr = run.pr;
15509
15740
  if (!d.mergeTree)
15510
15741
  return;
15742
+ const check = pr.conflictCheck;
15743
+ if (view.head && check?.head === view.head && !pr.resolving && d.now() - check.at < CONFLICT_RECHECK_MS)
15744
+ return;
15511
15745
  const base = view.base || d.defaultBranch(d.exec, run.repoPath);
15512
15746
  const mt = d.mergeTree(d.exec, run.repoPath, base, run.branch);
15513
15747
  if (!mt)
15514
15748
  return;
15749
+ if (view.head)
15750
+ pr.conflictCheck = { head: view.head, at: d.now() };
15515
15751
  if (pr.resolving === mt.sha && mt.files.length > 0 && !sessionRuns(d, run.handle)) {
15516
15752
  pr.resolving = undefined;
15517
15753
  const what = `${pr.url} still conflicts with ${base} on ${mt.files.join(", ")} after its session had the brief`;
@@ -17174,7 +17410,7 @@ async function fetchVersion(o) {
17174
17410
  const platform = o.platform ?? process.platform;
17175
17411
  const arch = o.arch ?? process.arch;
17176
17412
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
17177
- const running = o.running ?? "0.1.51";
17413
+ const running = o.running ?? "0.1.53";
17178
17414
  if (!publicKey)
17179
17415
  return { ok: false, why: "no release key yet" };
17180
17416
  if (!VERSION_RE2.test(o.version))
@@ -18758,7 +18994,7 @@ function engineClient(o, org) {
18758
18994
  return fetchClient({
18759
18995
  ...src,
18760
18996
  timeoutMs: ENGINE_TIMEOUT_MS,
18761
- version: "0.1.51"
18997
+ version: "0.1.53"
18762
18998
  });
18763
18999
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
18764
19000
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -18823,10 +19059,10 @@ function engineToken(o, org) {
18823
19059
  }
18824
19060
  var withToken = (token, now) => token ? { token, tokenNow: now } : {};
18825
19061
  function engineDeps(o, flags, source, runner, stateName) {
18826
- const exec = guardGit(o.exec ?? defaultExec);
19062
+ const log = o.log ?? console.log;
19063
+ const exec = logTimeouts(guardGit(o.exec ?? defaultExec), log);
18827
19064
  const cwd = o.cwd ?? process.cwd();
18828
19065
  const exists = o.exists ?? realExists;
18829
- const log = o.log ?? console.log;
18830
19066
  const stateDir = engineStateDir(o, flags.org, flags.seat);
18831
19067
  const file = statePath(stateDir, stateName);
18832
19068
  const mcpOrg = flags.org && engineOrg(o, flags.org).seated ? flags.org : undefined;
@@ -19049,7 +19285,7 @@ async function driveEngine(o, flags, deps, until = {}) {
19049
19285
  runner: deps.runner.name,
19050
19286
  atOnce: flags.atOnce,
19051
19287
  interval: flags.interval,
19052
- cliVersion: "0.1.51"
19288
+ cliVersion: "0.1.53"
19053
19289
  };
19054
19290
  const beat = (checkedAt) => {
19055
19291
  const m = deps.machine;
@@ -19241,7 +19477,7 @@ async function drive(o, flags, deps, until, beat) {
19241
19477
  var SEAT_CHANGED_EXIT = 3;
19242
19478
  function engineRunner(o, flags) {
19243
19479
  const runnerFor = o.runnerFor ?? runnerFor2;
19244
- return runnerFor(flags.runner, guardGit(o.exec ?? defaultExec), {
19480
+ return runnerFor(flags.runner, logTimeouts(guardGit(o.exec ?? defaultExec), o.log ?? console.log), {
19245
19481
  ...flags.runnerCmd ? { command: flags.runnerCmd } : {}
19246
19482
  });
19247
19483
  }
@@ -20906,7 +21142,7 @@ function engineClient2(o, org) {
20906
21142
  return fetchClient({
20907
21143
  ...src,
20908
21144
  timeoutMs: ENGINE_TIMEOUT_MS2,
20909
- version: "0.1.51"
21145
+ version: "0.1.53"
20910
21146
  });
20911
21147
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
20912
21148
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -20993,10 +21229,10 @@ function engineCredential(o) {
20993
21229
  })?.kind;
20994
21230
  }
20995
21231
  function engineDeps2(o, flags, source, runner, stateName) {
20996
- const exec = guardGit(o.exec ?? defaultExec);
21232
+ const log = o.log ?? console.log;
21233
+ const exec = logTimeouts(guardGit(o.exec ?? defaultExec), log);
20997
21234
  const cwd = o.cwd ?? process.cwd();
20998
21235
  const exists = o.exists ?? realExists2;
20999
- const log = o.log ?? console.log;
21000
21236
  const stateDir = engineStateDir2(o, flags.org, flags.seat);
21001
21237
  const file = statePath(stateDir, stateName);
21002
21238
  const mcpOrg = flags.org && engineOrg2(o, flags.org).seated ? flags.org : undefined;
@@ -21232,7 +21468,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21232
21468
  runner: deps.runner.name,
21233
21469
  atOnce: flags.atOnce,
21234
21470
  interval: flags.interval,
21235
- cliVersion: "0.1.51"
21471
+ cliVersion: "0.1.53"
21236
21472
  };
21237
21473
  const beat = (checkedAt) => {
21238
21474
  const m = deps.machine;
@@ -21424,7 +21660,7 @@ async function drive2(o, flags, deps, until, beat) {
21424
21660
  var SEAT_CHANGED_EXIT2 = 3;
21425
21661
  function engineRunner2(o, flags) {
21426
21662
  const runnerFor = o.runnerFor ?? runnerFor2;
21427
- return runnerFor(flags.runner, guardGit(o.exec ?? defaultExec), {
21663
+ return runnerFor(flags.runner, logTimeouts(guardGit(o.exec ?? defaultExec), o.log ?? console.log), {
21428
21664
  ...flags.runnerCmd ? { command: flags.runnerCmd } : {}
21429
21665
  });
21430
21666
  }
@@ -23320,7 +23556,7 @@ function contextFor(deps) {
23320
23556
  ...src,
23321
23557
  fetchFn: deps.fetchFn,
23322
23558
  timeoutMs: deps.timeoutMs,
23323
- version: "0.1.51"
23559
+ version: "0.1.53"
23324
23560
  }), deps.home, now) : null;
23325
23561
  return {
23326
23562
  channel: "code",
@@ -25834,14 +26070,16 @@ function routeReader(o, org) {
25834
26070
  read: (k) => readOrSay(client, `/proxy/route?q=${encodeURIComponent(k)}`, k)
25835
26071
  };
25836
26072
  }
25837
- var SLED_USAGE = "usage: sleds sled --json | sleds sled top <KEY> | sleds sled order <KEY>…";
26073
+ var SLED_USAGE = "usage: sleds sled --json | sleds sled top <KEY> | sleds sled order <KEY>… | sleds sled off <KEY> [--json]";
25838
26074
  async function sledCmd(o) {
25839
26075
  const log = o.log ?? console.log;
25840
26076
  const error = o.error ?? console.error;
25841
26077
  const top = o.argv[0] === "top" && o.argv.length === 2 && !o.argv[1]?.startsWith("-");
25842
26078
  const keys = o.argv.slice(1);
25843
26079
  const order = o.argv[0] === "order" && keys.length > 0 && keys.every((k) => k.trim() && !k.startsWith("-"));
25844
- if (!top && !order && (o.argv.length !== 1 || o.argv[0] !== "--json")) {
26080
+ const offArgs = o.argv.slice(1).filter((a) => a !== "--json");
26081
+ const off = o.argv[0] === "off" && offArgs.length === 1 && o.argv.length <= 3 && !!offArgs[0]?.trim() && !offArgs[0].startsWith("-");
26082
+ if (!top && !order && !off && (o.argv.length !== 1 || o.argv[0] !== "--json")) {
25845
26083
  error(SLED_USAGE);
25846
26084
  return 2;
25847
26085
  }
@@ -25854,6 +26092,8 @@ async function sledCmd(o) {
25854
26092
  return sledTop(client, o.argv[1], log, error);
25855
26093
  if (order)
25856
26094
  return sledOrder(client, keys, log, error);
26095
+ if (off)
26096
+ return sledOff(client, offArgs[0], o.argv.includes("--json"), log, error);
25857
26097
  try {
25858
26098
  log(JSON.stringify(await readOrSay(client, "/proxy/sled", "the sled")));
25859
26099
  return 0;
@@ -25888,6 +26128,34 @@ async function sledOrder(client, keys, log, error) {
25888
26128
  log(`${typeof r.json?.ordered === "number" ? r.json.ordered : keys.length} ordered on your sled`);
25889
26129
  return 0;
25890
26130
  }
26131
+ async function sledOff(client, key, json, log, error) {
26132
+ const r = await client.postFull("/proxy/sled/off", { project: key });
26133
+ if (!r) {
26134
+ error(`${key}: SLEDS unreachable — the sled is unchanged`);
26135
+ return 1;
26136
+ }
26137
+ if (r.status >= 300 || r.json?.ok === false) {
26138
+ error(typeof r.json?.error === "string" ? r.json.error : `${key}: HTTP ${r.status}`);
26139
+ return 1;
26140
+ }
26141
+ if (json) {
26142
+ log(JSON.stringify(r.json));
26143
+ return 0;
26144
+ }
26145
+ const k = typeof r.json?.key === "string" ? r.json.key : key;
26146
+ const why = typeof r.json?.why === "string" && r.json.why.trim() ? r.json.why : null;
26147
+ if (r.json?.changed === false) {
26148
+ error(why ?? `${k} was not on a sled`);
26149
+ return 1;
26150
+ }
26151
+ log(`${k} is off your sled — back in the pool`);
26152
+ const running = typeof r.json?.running === "number" ? r.json.running : 0;
26153
+ if (running > 0)
26154
+ log(`${running} running node${running === 1 ? "" : "s"} stopped`);
26155
+ if (why)
26156
+ log(why);
26157
+ return 0;
26158
+ }
25891
26159
 
25892
26160
  // src/commands/run.ts
25893
26161
  init_config();
@@ -27406,7 +27674,7 @@ var realPidAlive4 = (pid) => {
27406
27674
  };
27407
27675
  function machineClient(home, env) {
27408
27676
  const src = meterSource(home, undefined, { env });
27409
- return src ? fetchClient({ ...src, version: "0.1.51" }) : null;
27677
+ return src ? fetchClient({ ...src, version: "0.1.53" }) : null;
27410
27678
  }
27411
27679
  async function noteAnswer2(home, a, now) {
27412
27680
  try {
@@ -27876,15 +28144,29 @@ var readConfig2 = (home) => {
27876
28144
 
27877
28145
  // src/runners/exec.ts
27878
28146
  import { spawn as spawn3, spawnSync as spawnSync10 } from "node:child_process";
28147
+ var EXEC_TIMEOUT_MS2 = 2 * 60000;
28148
+ var EXEC_TIMEOUT_STATUS2 = 124;
28149
+ var timedOut2 = (r) => r.error?.code === "ETIMEDOUT";
27879
28150
  var defaultExec4 = (cmd, args, o) => {
28151
+ const timeoutMs = o?.timeoutMs ?? EXEC_TIMEOUT_MS2;
27880
28152
  const r = spawnSync10(cmd, [...args], {
27881
28153
  cwd: o?.cwd,
27882
28154
  input: o?.input,
27883
28155
  env: o?.env ? { ...process.env, ...o.env } : undefined,
27884
28156
  encoding: "utf8",
27885
28157
  shell: false,
27886
- maxBuffer: 16 * 1024 * 1024
28158
+ maxBuffer: 16 * 1024 * 1024,
28159
+ timeout: timeoutMs,
28160
+ killSignal: "SIGKILL"
27887
28161
  });
28162
+ if (timedOut2(r)) {
28163
+ return {
28164
+ status: EXEC_TIMEOUT_STATUS2,
28165
+ stdout: r.stdout ?? "",
28166
+ stderr: `${cmd}: timed out after ${timeoutMs / 1000}s and was killed${r.stderr ? `
28167
+ ${r.stderr}` : ""}`
28168
+ };
28169
+ }
27888
28170
  if (r.error) {
27889
28171
  return { status: 127, stdout: r.stdout ?? "", stderr: `${cmd}: ${r.error.message}` };
27890
28172
  }
@@ -28026,6 +28308,7 @@ commands:
28026
28308
  sled --json What is on your sled (mine with each route, proposed, sent), for a front end.
28027
28309
  sleds sled top KEY put KEY first on your sled; the engine picks it next
28028
28310
  sleds sled order KEY\u2026 those items in this order, in the places they hold; the rest stay
28311
+ sleds sled off KEY take KEY off your sled, back in the pool; its running nodes stop
28029
28312
  route <key> Print an item's route: its nodes, what each waits on, what is ready, where each
28030
28313
  run can be watched.
28031
28314
  --dot Graphviz instead: \`sleds route QW-142 --dot | dot -Tpng -o route.png\`
@@ -28251,7 +28534,7 @@ var main = async (argv) => {
28251
28534
  now: Date.now(),
28252
28535
  stderr: (line) => console.error(line),
28253
28536
  isTTY: !!process.stderr.isTTY,
28254
- version: "0.1.51"
28537
+ version: "0.1.53"
28255
28538
  });
28256
28539
  switch (cmd) {
28257
28540
  case "update":
@@ -28484,7 +28767,7 @@ ${renderHelpText(argv[1])}`);
28484
28767
  case "version":
28485
28768
  case "--version":
28486
28769
  case "-v":
28487
- console.log("sleds 0.1.51");
28770
+ console.log("sleds 0.1.53");
28488
28771
  return 0;
28489
28772
  default:
28490
28773
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
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",