@sleds/cli 0.1.54 → 0.1.56

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 +225 -73
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -2565,9 +2565,18 @@ ${rows.map((r) => statusLine(r, now)).join(`
2565
2565
 
2566
2566
  `)}${more}`;
2567
2567
  }
2568
+ function mineSection(rows, now, count) {
2569
+ if (rows.length === 0)
2570
+ return null;
2571
+ return `On your sled (${count ?? rows.length}):
2572
+
2573
+ ${rows.map((r) => statusLine(r, now)).join(`
2574
+
2575
+ `)}`;
2576
+ }
2568
2577
  function sledText(s, now) {
2569
2578
  const parts = [
2570
- section("On your sled", s.mine, now),
2579
+ mineSection(s.mine, now, s.mineCount),
2571
2580
  section("Proposed to you", s.proposed, now, s.proposedCount),
2572
2581
  section("Sent to you", s.sent, now)
2573
2582
  ].filter((p) => p !== null);
@@ -3519,6 +3528,7 @@ async function sledTool(ctx, args) {
3519
3528
  return {
3520
3529
  text: sledText({
3521
3530
  mine: rows("mine"),
3531
+ ...typeof r.mineCount === "number" ? { mineCount: r.mineCount } : {},
3522
3532
  proposed: rows("proposed"),
3523
3533
  ...typeof r.proposedCount === "number" ? { proposedCount: r.proposedCount } : {},
3524
3534
  sent: rows("sent")
@@ -8558,6 +8568,7 @@ function parseParked(v) {
8558
8568
  question: v.question,
8559
8569
  at: v.at,
8560
8570
  ...typeof v.words === "string" ? { words: v.words, ...v.still === true ? { still: true } : {} } : {},
8571
+ ...typeof v.seatFull === "string" && v.seatFull ? { seatFull: v.seatFull } : {},
8561
8572
  ...answerer ? { answerer } : {}
8562
8573
  };
8563
8574
  }
@@ -11535,7 +11546,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11535
11546
  name: flags.name,
11536
11547
  os: host.os ?? process.platform,
11537
11548
  runner: flags.runner,
11538
- version: host.version ?? "0.1.54" ?? "dev",
11549
+ version: host.version ?? "0.1.56" ?? "dev",
11539
11550
  running: live.length,
11540
11551
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11541
11552
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -11936,6 +11947,7 @@ function parsePrView(stdout) {
11936
11947
  authorFeedback: 0,
11937
11948
  draft: o.isDraft === true,
11938
11949
  updatedAt: typeof o.updatedAt === "string" ? o.updatedAt : "",
11950
+ queued: typeof o.autoMergeRequest === "object" && o.autoMergeRequest !== null,
11939
11951
  ...mock ? { mock } : {},
11940
11952
  ...typeof o.headRefOid === "string" && o.headRefOid ? { head: o.headRefOid } : {},
11941
11953
  ...typeof mergeCommit === "string" && mergeCommit ? { mergeCommit } : {}
@@ -11984,6 +11996,27 @@ function prFeedback(exec, repoPath, url, author) {
11984
11996
  }
11985
11997
  return total;
11986
11998
  }
11999
+ var MERGE_QUEUE_QUERY = "query($url: URI!) { resource(url: $url) { ... on PullRequest { mergeQueueEntry { state } } } }";
12000
+ function prInMergeQueue(exec, repoPath, url) {
12001
+ try {
12002
+ const r = exec("gh", [
12003
+ "api",
12004
+ "graphql",
12005
+ "-f",
12006
+ `query=${MERGE_QUEUE_QUERY}`,
12007
+ "-f",
12008
+ `url=${url}`,
12009
+ "--jq",
12010
+ ".data.resource.mergeQueueEntry != null"
12011
+ ], { cwd: repoPath });
12012
+ if (r.status !== 0)
12013
+ return null;
12014
+ const out = r.stdout.trim();
12015
+ return out === "true" ? true : out === "false" ? false : null;
12016
+ } catch {
12017
+ return null;
12018
+ }
12019
+ }
11987
12020
  function prView(exec, repoPath, url, last) {
11988
12021
  try {
11989
12022
  const r = exec("gh", [
@@ -11991,11 +12024,13 @@ function prView(exec, repoPath, url, last) {
11991
12024
  "view",
11992
12025
  url,
11993
12026
  "--json",
11994
- "state,author,statusCheckRollup,reviewDecision,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body,updatedAt"
12027
+ "state,author,statusCheckRollup,reviewDecision,headRefOid,mergeable,baseRefName,mergeCommit,isDraft,body,updatedAt,autoMergeRequest"
11995
12028
  ], { cwd: repoPath });
11996
- const view = r.status === 0 ? parsePrView(r.stdout) : null;
11997
- if (!view || view.state !== "OPEN")
11998
- return view;
12029
+ const parsed = r.status === 0 ? parsePrView(r.stdout) : null;
12030
+ if (!parsed || parsed.state !== "OPEN")
12031
+ return parsed;
12032
+ const ready = !parsed.queued && parsed.failing.length === 0 && parsed.pending === 0 && parsed.reviewDecision === "APPROVED";
12033
+ const view = ready && prInMergeQueue(exec, repoPath, url) === true ? { ...parsed, queued: true } : parsed;
11999
12034
  let read = view.updatedAt && last?.updatedAt === view.updatedAt ? last : undefined;
12000
12035
  if (!read) {
12001
12036
  const f = prFeedback(exec, repoPath, url, view.author);
@@ -13207,9 +13242,12 @@ function sledsSource(client, o) {
13207
13242
  if (r.status >= 300 || !r.json || r.json.ok === false)
13208
13243
  throw new Error(serverMessage(r));
13209
13244
  const leaseUntil = typeof r.json.leaseUntil === "number" ? r.json.leaseUntil : undefined;
13245
+ const resumed = r.json.resumed === true;
13246
+ const seatFull = !resumed && typeof r.json.seatFull === "string" ? r.json.seatFull : "";
13210
13247
  return {
13211
- resumed: r.json.resumed === true,
13212
- ...leaseUntil !== undefined ? { leaseUntil } : {}
13248
+ resumed,
13249
+ ...leaseUntil !== undefined ? { leaseUntil } : {},
13250
+ ...seatFull ? { seatFull } : {}
13213
13251
  };
13214
13252
  },
13215
13253
  async decisionOf(item, decisionId) {
@@ -13880,14 +13918,18 @@ async function tickOnce(d) {
13880
13918
  try {
13881
13919
  open = runner.status(run.handle) === "running";
13882
13920
  } catch {}
13883
- if (open && await answeredInSession(d, runner, run)) {
13884
- keep.push(run);
13921
+ if (!open && !liveAnswerer(run)) {
13922
+ d.log(`${ref(run)}: the parked session closed — its answer will start a new one`);
13885
13923
  continue;
13886
13924
  }
13887
- if (open || liveAnswerer(run))
13888
- keep.push(run);
13889
- else
13890
- d.log(`${ref(run)}: the parked session closed — its answer will start a new one`);
13925
+ if (await parkedBeat(d, run, claim) === "done") {
13926
+ d.log(`${ref(run)}: no longer waits for an answer — its node was skipped`);
13927
+ stopAnswerer(d, run);
13928
+ continue;
13929
+ }
13930
+ if (open)
13931
+ await answeredInSession(d, runner, run);
13932
+ keep.push(run);
13891
13933
  continue;
13892
13934
  }
13893
13935
  if (run.pr) {
@@ -14084,7 +14126,25 @@ async function tickOnce(d) {
14084
14126
  state.decisions = kept;
14085
14127
  save();
14086
14128
  }
14087
- const items = await d.source.read();
14129
+ let items;
14130
+ try {
14131
+ items = await d.source.read();
14132
+ } catch (e) {
14133
+ if (e instanceof SeatTokenError)
14134
+ throw e;
14135
+ save();
14136
+ return {
14137
+ started: [],
14138
+ finished,
14139
+ ...counts(state),
14140
+ attention,
14141
+ done: false,
14142
+ items: [],
14143
+ changes,
14144
+ decisions: decided,
14145
+ readFailed: message(e)
14146
+ };
14147
+ }
14088
14148
  const takeNone = seat.gone;
14089
14149
  for (const it of items)
14090
14150
  if (it.draft && !takeNone && !startsHeld(d))
@@ -14325,18 +14385,23 @@ async function tickOnce(d) {
14325
14385
  started.push({ item: claim.item, node: claim.node, watch, watchTarget: watchTarget(handle) });
14326
14386
  }
14327
14387
  const done = items.length > 0 && items.every((i) => i.landed) && state.runs.length === 0;
14328
- const following = state.runs.filter((r) => r.pr).length;
14329
- const parked = state.runs.filter((r) => r.parked).length;
14330
14388
  return {
14331
14389
  started,
14332
14390
  finished,
14333
- running: state.runs.length - following - parked,
14334
- following,
14391
+ ...counts(state),
14335
14392
  attention,
14336
14393
  done,
14337
14394
  items,
14338
14395
  changes,
14339
- decisions: decided,
14396
+ decisions: decided
14397
+ };
14398
+ }
14399
+ function counts(state) {
14400
+ const following = state.runs.filter((r) => r.pr).length;
14401
+ const parked = state.runs.filter((r) => r.parked).length;
14402
+ return {
14403
+ running: state.runs.length - following - parked,
14404
+ following,
14340
14405
  followed: state.runs.flatMap((r) => r.pr ? [{ item: r.item, node: r.node, url: r.pr.url, ...r.pr.ci ? { ci: r.pr.ci } : {} }] : [])
14341
14406
  };
14342
14407
  }
@@ -14810,15 +14875,20 @@ async function answeredInSession(d, runner, run) {
14810
14875
  return false;
14811
14876
  }
14812
14877
  const words = wordsPrint(shown);
14813
- if (words === parked.words) {
14814
- parked.still = true;
14815
- return false;
14878
+ const waiting = parked.seatFull !== undefined;
14879
+ if (waiting) {
14880
+ parked.words = words;
14881
+ } else {
14882
+ if (words === parked.words) {
14883
+ parked.still = true;
14884
+ return false;
14885
+ }
14886
+ const moved = parked.still === true;
14887
+ parked.words = words;
14888
+ parked.still = false;
14889
+ if (!moved)
14890
+ return false;
14816
14891
  }
14817
- const moved = parked.still === true;
14818
- parked.words = words;
14819
- parked.still = false;
14820
- if (!moved)
14821
- return false;
14822
14892
  let r;
14823
14893
  try {
14824
14894
  r = await resumes.resumeParked(run.runId, "answered in the session", d.engine, d.leaseMin);
@@ -14826,7 +14896,14 @@ async function answeredInSession(d, runner, run) {
14826
14896
  d.log(`${ref(run)}: the parked session moved on, but SLEDS did not hear it: ${message(e)}`);
14827
14897
  return false;
14828
14898
  }
14899
+ if (!r.resumed && r.seatFull) {
14900
+ if (!waiting)
14901
+ d.log(`${ref(run)}: answered in the session, but ${r.seatFull} — stays in Needs you until a slot frees`);
14902
+ parked.seatFull = r.seatFull;
14903
+ return false;
14904
+ }
14829
14905
  if (!r.resumed) {
14906
+ parked.seatFull = undefined;
14830
14907
  d.log(`${ref(run)}: the parked session moved on, but the node is no longer parked on its run`);
14831
14908
  return false;
14832
14909
  }
@@ -15203,6 +15280,14 @@ async function endUnposted(d, answers, a, runner, at, over) {
15203
15280
  return false;
15204
15281
  }
15205
15282
  }
15283
+ async function parkedBeat(d, run, claim) {
15284
+ try {
15285
+ return await d.source.heartbeat(claim);
15286
+ } catch (e) {
15287
+ d.log(`${ref(run)}: heartbeat failed, retrying next tick: ${message(e)}`);
15288
+ return "running";
15289
+ }
15290
+ }
15206
15291
  function stopAnswerer(d, run) {
15207
15292
  const a = liveAnswerer(run);
15208
15293
  if (!a)
@@ -15538,16 +15623,22 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
15538
15623
  pr.failing = view.failing;
15539
15624
  pr.feedback = Math.max(pr.feedback, view.feedback);
15540
15625
  pr.authorFeedback = Math.max(pr.authorFeedback ?? 0, view.authorFeedback) || undefined;
15541
- const clear = view.failing.length === 0 && view.pending === 0 && view.reviewDecision === "APPROVED";
15626
+ const queued = view.queued;
15627
+ if (queued && !pr.queued)
15628
+ d.log(`${ref(run)}: ${pr.url} is queued to merge`);
15629
+ else if (!queued && pr.queued)
15630
+ d.log(`${ref(run)}: ${pr.url} left the merge queue`);
15631
+ pr.queued = queued || undefined;
15632
+ const clear = view.failing.length === 0 && view.pending === 0 && view.reviewDecision === "APPROVED" && !queued;
15542
15633
  if (clear && !pr.clear) {
15543
15634
  d.log(`${ref(run)}: ${pr.url} is green and approved — ready for your merge call`);
15544
15635
  attention.push({ ...at, what: "green and approved: ready for a merge" });
15545
15636
  }
15546
15637
  pr.clear = clear;
15547
15638
  const prRead = d.source.prRead;
15548
- const posted = `${ci}|${review}|${clear}`;
15639
+ const posted = `${ci}|${review}|${clear}|${queued}`;
15549
15640
  if (prRead && posted !== pr.posted) {
15550
- const read = { ci, ...review ? { review } : {}, clear };
15641
+ const read = { ci, ...review ? { review } : {}, clear, queued };
15551
15642
  if (await prRead.call(d.source, run.item, run.node, read))
15552
15643
  pr.posted = posted;
15553
15644
  }
@@ -16305,6 +16396,41 @@ async function settleEvent(d, events, state, dec, attention, decided) {
16305
16396
 
16306
16397
  // src/commands/engine.ts
16307
16398
  init_machine();
16399
+
16400
+ // src/engine/sledsBreaker.ts
16401
+ var SLEDS_DOWN_ERROR = "SledsDownError";
16402
+ function why(e) {
16403
+ const name = e instanceof Error ? e.name : "";
16404
+ return name === "TimeoutError" || name === "AbortError" ? "timed out" : "could not be reached";
16405
+ }
16406
+ function sledsBreaker(fetchFn = fetch) {
16407
+ let down = null;
16408
+ let pass = 0;
16409
+ return {
16410
+ async fetch(input, init) {
16411
+ if (down !== null) {
16412
+ const e = new Error(`skipped — SLEDS ${down} earlier this tick`);
16413
+ e.name = SLEDS_DOWN_ERROR;
16414
+ throw e;
16415
+ }
16416
+ const started = pass;
16417
+ try {
16418
+ return await fetchFn(input, init);
16419
+ } catch (e) {
16420
+ if (started === pass && down === null)
16421
+ down = why(e);
16422
+ throw e;
16423
+ }
16424
+ },
16425
+ reset() {
16426
+ down = null;
16427
+ pass++;
16428
+ },
16429
+ tripped: () => down
16430
+ };
16431
+ }
16432
+
16433
+ // src/commands/engine.ts
16308
16434
  init_state();
16309
16435
  init_meter();
16310
16436
 
@@ -17428,7 +17554,7 @@ async function fetchVersion(o) {
17428
17554
  const platform = o.platform ?? process.platform;
17429
17555
  const arch = o.arch ?? process.arch;
17430
17556
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
17431
- const running = o.running ?? "0.1.54";
17557
+ const running = o.running ?? "0.1.56";
17432
17558
  if (!publicKey)
17433
17559
  return { ok: false, why: "no release key yet" };
17434
17560
  if (!VERSION_RE2.test(o.version))
@@ -19003,7 +19129,7 @@ async function withProfile(client, flags, given, home, retry) {
19003
19129
  }
19004
19130
  var NOT_SET_UP2 = "SLEDS is not set up on this machine. Run `sleds init` first.";
19005
19131
  var ENGINE_TIMEOUT_MS = 15000;
19006
- function engineClient(o, org) {
19132
+ function engineClient(o, org, breaker) {
19007
19133
  if (o.client)
19008
19134
  return o.client;
19009
19135
  const env = o.env ?? process.env;
@@ -19011,8 +19137,9 @@ function engineClient(o, org) {
19011
19137
  if (src)
19012
19138
  return fetchClient({
19013
19139
  ...src,
19140
+ ...breaker ? { fetchFn: breaker.fetch } : {},
19014
19141
  timeoutMs: ENGINE_TIMEOUT_MS,
19015
- version: "0.1.54"
19142
+ version: "0.1.56"
19016
19143
  });
19017
19144
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
19018
19145
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -19030,9 +19157,9 @@ function engineOrg(o, flag) {
19030
19157
  known.delete(slug);
19031
19158
  return { slug, seated: Boolean(cfg.orgs?.[slug]), sole: known.size === 0 };
19032
19159
  }
19033
- function orgClient(o, flag) {
19160
+ function orgClient(o, flag, breaker) {
19034
19161
  const org = engineOrg(o, flag);
19035
- return engineClient(o, org.seated ? org.slug ?? undefined : flag);
19162
+ return engineClient(o, org.seated ? org.slug ?? undefined : flag, breaker);
19036
19163
  }
19037
19164
  function realPidAlive2(pid) {
19038
19165
  try {
@@ -19303,7 +19430,7 @@ async function driveEngine(o, flags, deps, until = {}) {
19303
19430
  runner: deps.runner.name,
19304
19431
  atOnce: flags.atOnce,
19305
19432
  interval: flags.interval,
19306
- cliVersion: "0.1.54"
19433
+ cliVersion: "0.1.56"
19307
19434
  };
19308
19435
  const beat = (checkedAt) => {
19309
19436
  const m = deps.machine;
@@ -19384,35 +19511,42 @@ async function drive(o, flags, deps, until, beat) {
19384
19511
  await until.beforeFirstTick?.();
19385
19512
  for (;; ) {
19386
19513
  let report = null;
19514
+ let why;
19515
+ until.breaker?.reset();
19387
19516
  try {
19388
19517
  report = await tick(deps);
19518
+ why = report.readFailed;
19389
19519
  } catch (e) {
19520
+ why = e instanceof Error ? e.message : String(e);
19521
+ }
19522
+ if (why !== undefined) {
19390
19523
  down++;
19391
- const why = e instanceof Error ? e.message : String(e);
19392
19524
  if (down === 1 || flags.once)
19393
19525
  event({
19394
19526
  kind: "sleds-down",
19395
19527
  text: why,
19396
19528
  ...flags.once ? {} : { aside: `· retrying every ${flags.interval} s` }
19397
19529
  });
19398
- if (flags.once)
19530
+ if (flags.once && !report)
19399
19531
  return 1;
19400
19532
  }
19401
19533
  beat(now());
19402
- const leave = await until.afterTick?.(report !== null);
19534
+ const leave = await until.afterTick?.(report !== null && !report.readFailed);
19403
19535
  if (typeof leave === "number") {
19404
19536
  if (statusShown)
19405
19537
  write("\r\x1B[2K");
19406
19538
  return leave;
19407
19539
  }
19408
19540
  if (report) {
19409
- if (down > 0)
19410
- event({
19411
- kind: "sleds-back",
19412
- text: "back",
19413
- aside: `after ${down} failed ${down === 1 ? "read" : "reads"}`
19414
- });
19415
- down = 0;
19541
+ if (!report.readFailed) {
19542
+ if (down > 0)
19543
+ event({
19544
+ kind: "sleds-back",
19545
+ text: "back",
19546
+ aside: `after ${down} failed ${down === 1 ? "read" : "reads"}`
19547
+ });
19548
+ down = 0;
19549
+ }
19416
19550
  running = report.running;
19417
19551
  if (report.seatChanged || report.tokenChanged) {
19418
19552
  if (statusShown)
@@ -19462,6 +19596,8 @@ async function drive(o, flags, deps, until, beat) {
19462
19596
  pr: a.url
19463
19597
  });
19464
19598
  }
19599
+ if (flags.once && report.readFailed)
19600
+ return 1;
19465
19601
  if (until.done && report.done) {
19466
19602
  if (statusShown)
19467
19603
  write("\r\x1B[2K");
@@ -21151,7 +21287,7 @@ async function withProfile2(client, flags, given, home, retry) {
21151
21287
  }
21152
21288
  var NOT_SET_UP3 = "SLEDS is not set up on this machine. Run `sleds init` first.";
21153
21289
  var ENGINE_TIMEOUT_MS2 = 15000;
21154
- function engineClient2(o, org) {
21290
+ function engineClient2(o, org, breaker) {
21155
21291
  if (o.client)
21156
21292
  return o.client;
21157
21293
  const env = o.env ?? process.env;
@@ -21159,8 +21295,9 @@ function engineClient2(o, org) {
21159
21295
  if (src)
21160
21296
  return fetchClient({
21161
21297
  ...src,
21298
+ ...breaker ? { fetchFn: breaker.fetch } : {},
21162
21299
  timeoutMs: ENGINE_TIMEOUT_MS2,
21163
- version: "0.1.54"
21300
+ version: "0.1.56"
21164
21301
  });
21165
21302
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
21166
21303
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -21178,9 +21315,9 @@ function engineOrg2(o, flag) {
21178
21315
  known.delete(slug);
21179
21316
  return { slug, seated: Boolean(cfg.orgs?.[slug]), sole: known.size === 0 };
21180
21317
  }
21181
- function orgClient2(o, flag) {
21318
+ function orgClient2(o, flag, breaker) {
21182
21319
  const org = engineOrg2(o, flag);
21183
- return engineClient2(o, org.seated ? org.slug ?? undefined : flag);
21320
+ return engineClient2(o, org.seated ? org.slug ?? undefined : flag, breaker);
21184
21321
  }
21185
21322
  async function jiraWritesAtStart(gate, epic) {
21186
21323
  const no = await gate.ask({
@@ -21486,7 +21623,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21486
21623
  runner: deps.runner.name,
21487
21624
  atOnce: flags.atOnce,
21488
21625
  interval: flags.interval,
21489
- cliVersion: "0.1.54"
21626
+ cliVersion: "0.1.56"
21490
21627
  };
21491
21628
  const beat = (checkedAt) => {
21492
21629
  const m = deps.machine;
@@ -21567,35 +21704,42 @@ async function drive2(o, flags, deps, until, beat) {
21567
21704
  await until.beforeFirstTick?.();
21568
21705
  for (;; ) {
21569
21706
  let report = null;
21707
+ let why;
21708
+ until.breaker?.reset();
21570
21709
  try {
21571
21710
  report = await tick(deps);
21711
+ why = report.readFailed;
21572
21712
  } catch (e) {
21713
+ why = e instanceof Error ? e.message : String(e);
21714
+ }
21715
+ if (why !== undefined) {
21573
21716
  down++;
21574
- const why = e instanceof Error ? e.message : String(e);
21575
21717
  if (down === 1 || flags.once)
21576
21718
  event({
21577
21719
  kind: "sleds-down",
21578
21720
  text: why,
21579
21721
  ...flags.once ? {} : { aside: `· retrying every ${flags.interval} s` }
21580
21722
  });
21581
- if (flags.once)
21723
+ if (flags.once && !report)
21582
21724
  return 1;
21583
21725
  }
21584
21726
  beat(now());
21585
- const leave = await until.afterTick?.(report !== null);
21727
+ const leave = await until.afterTick?.(report !== null && !report.readFailed);
21586
21728
  if (typeof leave === "number") {
21587
21729
  if (statusShown)
21588
21730
  write("\r\x1B[2K");
21589
21731
  return leave;
21590
21732
  }
21591
21733
  if (report) {
21592
- if (down > 0)
21593
- event({
21594
- kind: "sleds-back",
21595
- text: "back",
21596
- aside: `after ${down} failed ${down === 1 ? "read" : "reads"}`
21597
- });
21598
- down = 0;
21734
+ if (!report.readFailed) {
21735
+ if (down > 0)
21736
+ event({
21737
+ kind: "sleds-back",
21738
+ text: "back",
21739
+ aside: `after ${down} failed ${down === 1 ? "read" : "reads"}`
21740
+ });
21741
+ down = 0;
21742
+ }
21599
21743
  running = report.running;
21600
21744
  if (report.seatChanged || report.tokenChanged) {
21601
21745
  if (statusShown)
@@ -21645,6 +21789,8 @@ async function drive2(o, flags, deps, until, beat) {
21645
21789
  pr: a.url
21646
21790
  });
21647
21791
  }
21792
+ if (flags.once && report.readFailed)
21793
+ return 1;
21648
21794
  if (until.done && report.done) {
21649
21795
  if (statusShown)
21650
21796
  write("\r\x1B[2K");
@@ -21720,17 +21866,22 @@ var engineCmd = async (o) => {
21720
21866
  error(kept.refused);
21721
21867
  return 1;
21722
21868
  }
21869
+ const breaker = sledsBreaker(o.fetch);
21723
21870
  let client;
21724
21871
  let pickup;
21725
21872
  if (!flags.jira && (o.client || !o.source)) {
21726
- const c = orgClient2(o, given);
21873
+ const c = orgClient2(o, given, breaker);
21727
21874
  if ("error" in c) {
21728
21875
  error(c.error);
21729
21876
  return 1;
21730
21877
  }
21731
21878
  client = c;
21879
+ const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
21732
21880
  const p = await withProfile2(c, flags, parsed.given, o.home, {
21733
- sleep: o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)))
21881
+ sleep: async (ms) => {
21882
+ await sleep(ms);
21883
+ breaker.reset();
21884
+ }
21734
21885
  });
21735
21886
  flags = p.flags;
21736
21887
  pickup = p.pickup;
@@ -21766,7 +21917,7 @@ var engineCmd = async (o) => {
21766
21917
  error(missing);
21767
21918
  return 1;
21768
21919
  }
21769
- const c = o.client ?? orgClient2(o, given);
21920
+ const c = o.client ?? orgClient2(o, given, breaker);
21770
21921
  if ("error" in c) {
21771
21922
  error(`--jira asks SLEDS before every write to Jira (spec 39), so it needs a seat: ${c.error}`);
21772
21923
  return 1;
@@ -21899,7 +22050,7 @@ var engineCmd = async (o) => {
21899
22050
  };
21900
22051
  }
21901
22052
  try {
21902
- return await driveEngine2(o, flags, deps, { afterTick, beforeFirstTick });
22053
+ return await driveEngine2(o, flags, deps, { afterTick, beforeFirstTick, breaker });
21903
22054
  } finally {
21904
22055
  watch.stop();
21905
22056
  }
@@ -22671,6 +22822,7 @@ function engineStatus(i) {
22671
22822
  failing: r.pr.failing,
22672
22823
  ...opt("review", r.pr.review || undefined),
22673
22824
  mergeable: r.pr.clear === true,
22825
+ queued: r.pr.queued === true,
22674
22826
  ...opt("conflict", conflictOf(r, i.state.decisions, i.watchOf))
22675
22827
  });
22676
22828
  continue;
@@ -22748,7 +22900,7 @@ function statusLines(s, now) {
22748
22900
  lines.push(` ${r.item}/${r.node} ${r.title} · ${r.answerer ? `parked, the decider answers${r.answerer.model ? ` on ${r.answerer.model}` : ""}${r.answerer.effort ? ` · ${r.answerer.effort}` : ""}` : r.parked ? "parked, waiting for your answer" : ago(r.startedAt)} · watch: ${r.watch}`);
22749
22901
  lines.push(`following ${s.following.length}`);
22750
22902
  for (const f of s.following) {
22751
- lines.push(` ${f.item}/${f.node} ${f.pr}${f.checks ? ` checks ${f.checks}` : ""}${f.review ? ` · ${f.review}` : ""}${f.mergeable ? " · ready for your merge" : ""}`);
22903
+ lines.push(` ${f.item}/${f.node} ${f.pr}${f.checks ? ` checks ${f.checks}` : ""}${f.review ? ` · ${f.review}` : ""}${f.mergeable ? " · ready for your merge" : ""}${f.queued ? " · queued to merge" : ""}`);
22752
22904
  const c = f.conflict;
22753
22905
  if (c)
22754
22906
  lines.push(` conflict on ${c.files.join(", ")}${c.cause ? ` · ${c.cause}` : ""} · ${CONFLICT_STAGE[c.stage]} · since ${ago(c.since)} ago${c.watch ? ` · watch: ${c.watch}` : ""}`);
@@ -23574,7 +23726,7 @@ function contextFor(deps) {
23574
23726
  ...src,
23575
23727
  fetchFn: deps.fetchFn,
23576
23728
  timeoutMs: deps.timeoutMs,
23577
- version: "0.1.54"
23729
+ version: "0.1.56"
23578
23730
  }), deps.home, now) : null;
23579
23731
  return {
23580
23732
  channel: "code",
@@ -26299,7 +26451,7 @@ function parseSeatRevokeArgs(argv) {
26299
26451
  }
26300
26452
  return { box, yes, json };
26301
26453
  }
26302
- function why(j, status) {
26454
+ function why2(j, status) {
26303
26455
  if (typeof j?.reason === "string")
26304
26456
  return j.reason;
26305
26457
  if (typeof j?.error === "string")
@@ -26337,7 +26489,7 @@ async function seatRevokeCmd(o) {
26337
26489
  return ok ? 0 : 1;
26338
26490
  }
26339
26491
  if (!ok) {
26340
- error(clean(`nothing revoked — ${why(r.json, r.status)}`));
26492
+ error(clean(`nothing revoked — ${why2(r.json, r.status)}`));
26341
26493
  return 1;
26342
26494
  }
26343
26495
  const label = typeof r.json?.label === "string" ? r.json.label : a.box ? `box:${a.box}` : "";
@@ -27692,7 +27844,7 @@ var realPidAlive4 = (pid) => {
27692
27844
  };
27693
27845
  function machineClient(home, env) {
27694
27846
  const src = meterSource(home, undefined, { env });
27695
- return src ? fetchClient({ ...src, version: "0.1.54" }) : null;
27847
+ return src ? fetchClient({ ...src, version: "0.1.56" }) : null;
27696
27848
  }
27697
27849
  async function noteAnswer2(home, a, now) {
27698
27850
  try {
@@ -28552,7 +28704,7 @@ var main = async (argv) => {
28552
28704
  now: Date.now(),
28553
28705
  stderr: (line) => console.error(line),
28554
28706
  isTTY: !!process.stderr.isTTY,
28555
- version: "0.1.54"
28707
+ version: "0.1.56"
28556
28708
  });
28557
28709
  switch (cmd) {
28558
28710
  case "update":
@@ -28785,7 +28937,7 @@ ${renderHelpText(argv[1])}`);
28785
28937
  case "version":
28786
28938
  case "--version":
28787
28939
  case "-v":
28788
- console.log("sleds 0.1.54");
28940
+ console.log("sleds 0.1.56");
28789
28941
  return 0;
28790
28942
  default:
28791
28943
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
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",