@sleds/cli 0.1.50 → 0.1.52

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 +492 -65
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -769,6 +769,14 @@ var init_verbs = __esm(() => {
769
769
  app: "Work › Moving (the runs on each sled) · Record › Receipts once they land",
770
770
  how: "Newest first: id, status, route, cost. Hand-offs meant for you are listed first."
771
771
  },
772
+ {
773
+ id: "metrics",
774
+ group: "meter",
775
+ want: "See how a workspace or the whole org ships — DORA and batch size for a period",
776
+ mcp: "metrics {workspace?, range?, from?, to?} — range 2w · 1m · 3m · 6m · 1y, or from/to days",
777
+ app: "Metrics",
778
+ how: "Deploy frequency, lead time, change failure rate, time to recover and production deploys by batch size, each against the same length just before. A workspace gives one line; no workspace gives the org, one line per workspace you see."
779
+ },
772
780
  {
773
781
  id: "status",
774
782
  group: "meter",
@@ -1616,6 +1624,128 @@ var init_format = __esm(() => {
1616
1624
  OFFLINE_LINE = `${PREFIX}offline`;
1617
1625
  });
1618
1626
 
1627
+ // ../../packages/shared/src/item/outcome.ts
1628
+ var OUTCOME_WINDOW_DAYS = 14, OUTCOME_WINDOW_MS;
1629
+ var init_outcome = __esm(() => {
1630
+ OUTCOME_WINDOW_MS = OUTCOME_WINDOW_DAYS * 86400000;
1631
+ });
1632
+
1633
+ // ../../packages/shared/src/deploys/metrics.ts
1634
+ var WEEK_MS;
1635
+ var init_metrics = __esm(() => {
1636
+ WEEK_MS = 7 * 24 * 60 * 60 * 1000;
1637
+ });
1638
+
1639
+ // ../../packages/shared/src/deploys/batchSize.ts
1640
+ var TIERS;
1641
+ var init_batchSize = __esm(() => {
1642
+ init_outcome();
1643
+ init_metrics();
1644
+ TIERS = ["S", "M", "L", "G"];
1645
+ });
1646
+
1647
+ // ../../packages/shared/src/deploys/period.ts
1648
+ function periodChange(cur, prev, better) {
1649
+ if (cur == null || prev == null)
1650
+ return { pct: null, dir: "flat", tone: "neutral" };
1651
+ if (prev === 0)
1652
+ return { pct: null, dir: cur > 0 ? "up" : cur < 0 ? "down" : "flat", tone: "neutral" };
1653
+ const pct = (cur - prev) / Math.abs(prev) * 100;
1654
+ if (Math.abs(pct) < FLAT_PCT)
1655
+ return { pct, dir: "flat", tone: "neutral" };
1656
+ const dir = pct > 0 ? "up" : "down";
1657
+ return { pct, dir, tone: dir === better ? "success" : "error" };
1658
+ }
1659
+ var DAY_MS2, FLAT_PCT = 0.5;
1660
+ var init_period = __esm(() => {
1661
+ DAY_MS2 = 24 * 60 * 60 * 1000;
1662
+ });
1663
+
1664
+ // ../../packages/shared/src/deploys/report.ts
1665
+ function duration(ms) {
1666
+ const min = ms / 60000;
1667
+ if (min < 60)
1668
+ return `${Math.round(min)}m`;
1669
+ const hours = min / 60;
1670
+ if (hours < 48)
1671
+ return `${hours.toFixed(1)}h`;
1672
+ return `${(hours / 24).toFixed(1)}d`;
1673
+ }
1674
+ function change(pair, better) {
1675
+ const { current, previous } = pair;
1676
+ if (current == null)
1677
+ return "";
1678
+ if (previous == null)
1679
+ return " (none before)";
1680
+ const c = periodChange(current, previous, better);
1681
+ if (c.dir === "flat")
1682
+ return " (flat)";
1683
+ if (c.pct == null)
1684
+ return " (none before)";
1685
+ return ` (${c.pct > 0 ? "+" : "−"}${Math.round(Math.abs(c.pct))}%)`;
1686
+ }
1687
+ function timeFigure(label, pair) {
1688
+ if (pair.current == null)
1689
+ return `${label} none`;
1690
+ return `${label} ${duration(pair.current)}${change(pair, "down")}`;
1691
+ }
1692
+ function rateFigure(pair) {
1693
+ const { current, previous } = pair;
1694
+ if (current == null)
1695
+ return "failure rate none";
1696
+ const pct = Math.round(current * 100);
1697
+ if (previous == null)
1698
+ return `failure rate ${pct}% (none before)`;
1699
+ const pts = pct - Math.round(previous * 100);
1700
+ return `failure rate ${pct}% (${pts === 0 ? "flat" : `${pts > 0 ? "+" : "−"}${Math.abs(pts)} pts`})`;
1701
+ }
1702
+ function batchFigure(pair) {
1703
+ if (total(pair.current) === 0)
1704
+ return "batch none";
1705
+ const before = total(pair.previous) === 0 ? "none before" : `was ${tiers(pair.previous)}`;
1706
+ return `batch ${tiers(pair.current)} (${before})`;
1707
+ }
1708
+ function lineText(l) {
1709
+ const head = `${l.name} (${l.slug})`;
1710
+ if (l.production == null)
1711
+ return `${head} · no production environment set, so no deploy metrics`;
1712
+ const deploys = `${l.deploys.current} deploy${l.deploys.current === 1 ? "" : "s"}${change(l.deploys, "up")}`;
1713
+ return [
1714
+ `${head} · ${l.production}: ${deploys}`,
1715
+ timeFigure("lead time", l.leadTime),
1716
+ rateFigure(l.failRate),
1717
+ timeFigure("time to recover", l.recover),
1718
+ batchFigure(l.batch)
1719
+ ].join(" · ");
1720
+ }
1721
+ function metricsText(r) {
1722
+ const sameYear = r.from.slice(0, 4) === r.to.slice(0, 4);
1723
+ const span = `${dayLabel(r.from, !sameYear)} – ${dayLabel(r.to, true)}`;
1724
+ const days = `${r.days} day${r.days === 1 ? "" : "s"}`;
1725
+ const header = `${r.scope === "org" ? "Org" : "Workspace"} · ${span} (${days}), against the ${days} before`;
1726
+ const lines = r.lines.length ? r.lines.map(lineText) : [r.scope === "org" ? "No workspaces in this org." : "No workspace."];
1727
+ return [header, ...lines].join(`
1728
+ `);
1729
+ }
1730
+ var DAY_MS3, METRICS_RANGES, tiers = (c) => TIERS.map((t) => `${t}${c[t]}`).join(" "), total = (c) => TIERS.reduce((n, t) => n + c[t], 0), dayLabel = (day, year) => new Date(`${day}T00:00:00Z`).toLocaleDateString("en-US", {
1731
+ month: "short",
1732
+ day: "numeric",
1733
+ ...year ? { year: "numeric" } : {},
1734
+ timeZone: "UTC"
1735
+ });
1736
+ var init_report = __esm(() => {
1737
+ init_batchSize();
1738
+ init_period();
1739
+ DAY_MS3 = 24 * 60 * 60 * 1000;
1740
+ METRICS_RANGES = [
1741
+ { key: "2w", days: 14 },
1742
+ { key: "1m", days: 30 },
1743
+ { key: "3m", days: 91 },
1744
+ { key: "6m", days: 182 },
1745
+ { key: "1y", days: 365 }
1746
+ ];
1747
+ });
1748
+
1619
1749
  // ../../packages/shared/src/guidance/model.ts
1620
1750
  var CARD_FIELDS;
1621
1751
  var init_model = __esm(() => {
@@ -2120,7 +2250,7 @@ var read = (title) => ({
2120
2250
  destructiveHint: false,
2121
2251
  idempotentHint: idempotent,
2122
2252
  openWorldHint: false
2123
- }), change = (title, idempotent = false) => ({
2253
+ }), change2 = (title, idempotent = false) => ({
2124
2254
  title,
2125
2255
  readOnlyHint: false,
2126
2256
  destructiveHint: true,
@@ -2130,6 +2260,7 @@ var read = (title) => ({
2130
2260
  var init_annotations = __esm(() => {
2131
2261
  TOOL_ANNOTATIONS = {
2132
2262
  usage: read("Seat usage"),
2263
+ metrics: read("Delivery metrics"),
2133
2264
  jobs: read("Overnight jobs"),
2134
2265
  status: read("Where is it"),
2135
2266
  touches: read("What it touches"),
@@ -2158,9 +2289,9 @@ var init_annotations = __esm(() => {
2158
2289
  buddy: add("Pair with a buddy"),
2159
2290
  pickup: add("Pick up a hand-off"),
2160
2291
  reopen: add("Reopen an item", true),
2161
- update: change("Change an item", true),
2162
- archive: change("Archive an item", true),
2163
- withdraw: change("Withdraw a hand-off", true)
2292
+ update: change2("Change an item", true),
2293
+ archive: change2("Archive an item", true),
2294
+ withdraw: change2("Withdraw a hand-off", true)
2164
2295
  };
2165
2296
  });
2166
2297
 
@@ -2421,19 +2552,23 @@ var init_plan2 = __esm(() => {
2421
2552
  });
2422
2553
 
2423
2554
  // ../../packages/shared/src/mcp/sled.ts
2424
- function section(label, rows, now) {
2555
+ function section(label, rows, now, count = rows.length) {
2425
2556
  if (rows.length === 0)
2426
2557
  return null;
2427
- return `${label} (${rows.length}):
2558
+ const total = Math.max(count, rows.length);
2559
+ const more = total > rows.length ? `
2560
+
2561
+ …and ${total - rows.length} more` : "";
2562
+ return `${label} (${total}):
2428
2563
 
2429
2564
  ${rows.map((r) => statusLine(r, now)).join(`
2430
2565
 
2431
- `)}`;
2566
+ `)}${more}`;
2432
2567
  }
2433
2568
  function sledText(s, now) {
2434
2569
  const parts = [
2435
2570
  section("On your sled", s.mine, now),
2436
- section("Proposed to you", s.proposed, now),
2571
+ section("Proposed to you", s.proposed, now, s.proposedCount),
2437
2572
  section("Sent to you", s.sent, now)
2438
2573
  ].filter((p) => p !== null);
2439
2574
  if (parts.length === 0)
@@ -2706,6 +2841,60 @@ async function usageTool(ctx) {
2706
2841
  return { text: formatMeter(m, (ctx.now ?? Date.now)()).join(`
2707
2842
  `) };
2708
2843
  }
2844
+ function asMetricsReport(j) {
2845
+ if (!j || j.scope !== "workspace" && j.scope !== "org")
2846
+ return null;
2847
+ if (typeof j.from !== "string" || typeof j.to !== "string" || num3(j.days) === undefined)
2848
+ return null;
2849
+ if (!Array.isArray(j.lines) || !j.lines.every(isObject4))
2850
+ return null;
2851
+ return j;
2852
+ }
2853
+ async function metricsTool(ctx, args) {
2854
+ if (!ctx.client)
2855
+ return NOT_SET_UP;
2856
+ const qs = new URLSearchParams;
2857
+ for (const k of ["workspace", "range", "from", "to"]) {
2858
+ const v = str5(args[k])?.trim();
2859
+ if (v)
2860
+ qs.set(k, v);
2861
+ }
2862
+ const query = qs.toString();
2863
+ const path = query ? `/proxy/metrics?${query}` : "/proxy/metrics";
2864
+ const noReport = { text: "SLEDS answered without a metrics report.", isError: true };
2865
+ if (!ctx.client.getFull) {
2866
+ const j = await ctx.client.get(path);
2867
+ if (!j)
2868
+ return UNREACHABLE;
2869
+ const report = asMetricsReport(j);
2870
+ return report ? { text: metricsText(report) } : noReport;
2871
+ }
2872
+ const r = await ctx.client.getFull(path);
2873
+ if ("error" in r)
2874
+ return {
2875
+ text: `Control plane unreachable: ${r.error} → try again in a moment.`,
2876
+ isError: true
2877
+ };
2878
+ const why = errorMessage(r.json);
2879
+ if (r.status === 200) {
2880
+ const report = asMetricsReport(r.json);
2881
+ return report ? { text: metricsText(report) } : noReport;
2882
+ }
2883
+ const workspace = qs.get("workspace");
2884
+ if (r.status === 404)
2885
+ return {
2886
+ text: workspace ? `No workspace "${workspace}" that this seat sees. Leave workspace out for the whole org, one line per workspace.` : `${why ?? "Not found"} (HTTP 404).`,
2887
+ isError: true
2888
+ };
2889
+ if (r.status === 400)
2890
+ return { text: why ?? "Bad request (HTTP 400).", isError: true };
2891
+ if (r.status >= 500)
2892
+ return {
2893
+ text: `${why ?? "Control plane unreachable"} (HTTP ${r.status}) → try once more in a moment, then move on and say so.`,
2894
+ isError: true
2895
+ };
2896
+ return { text: `${why ?? "SLEDS refused it"} (HTTP ${r.status}).`, isError: true };
2897
+ }
2709
2898
  function bandLine(band) {
2710
2899
  if (!isObject4(band))
2711
2900
  return null;
@@ -3328,7 +3517,12 @@ async function sledTool(ctx, args) {
3328
3517
  return UNREACHABLE;
3329
3518
  const rows = (k) => Array.isArray(r[k]) ? r[k].filter(isObject4) : [];
3330
3519
  return {
3331
- text: sledText({ mine: rows("mine"), proposed: rows("proposed"), sent: rows("sent") }, (ctx.now ?? Date.now)())
3520
+ text: sledText({
3521
+ mine: rows("mine"),
3522
+ proposed: rows("proposed"),
3523
+ ...typeof r.proposedCount === "number" ? { proposedCount: r.proposedCount } : {},
3524
+ sent: rows("sent")
3525
+ }, (ctx.now ?? Date.now)())
3332
3526
  };
3333
3527
  }
3334
3528
  function checkWarningsOf(ctx, drafts) {
@@ -4300,6 +4494,8 @@ async function callTool(name, args, ctx) {
4300
4494
  switch (name) {
4301
4495
  case "usage":
4302
4496
  return usageTool(ctx);
4497
+ case "metrics":
4498
+ return metricsTool(ctx, args);
4303
4499
  case "estimate":
4304
4500
  return estimateTool(ctx, args);
4305
4501
  case "intake":
@@ -4439,6 +4635,7 @@ async function handleLine(line, ctx) {
4439
4635
  }
4440
4636
  var PROTOCOL_VERSION = "2025-06-18", SERVER_INFO, SERVER_INSTRUCTIONS, MCP_TIMEOUT_MS = 5000, sourceSchema, specSchema, TOOLS, VIEW_TOOLS, NOT_SET_UP, UNREACHABLE, isObject4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num3 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, str5 = (v) => typeof v === "string" ? v : undefined, errorMessage = (j) => isObject4(j?.error) ? str5(j.error.message) : str5(j?.error), proposalText = (t) => (t ?? "").replace(/\n*Nothing runs until a person says yes:[^\n]*$/, ""), PROPOSED_NEXT = (key) => `Nothing runs until a person approves it: Approve in the SLEDS app or on the item page, or \`sleds route approve ${key}\`. If you are a planning session, stop here.`, strList = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : undefined, RELATION_HOWS, UI_RESOURCES, response = (id, result) => JSON.stringify({ jsonrpc: "2.0", id, result }), errorResponse = (id, code, message) => JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
4441
4637
  var init_server = __esm(() => {
4638
+ init_report();
4442
4639
  init_worth();
4443
4640
  init_intake();
4444
4641
  init_model();
@@ -4506,6 +4703,32 @@ var init_server = __esm(() => {
4506
4703
  description: "The SLEDS meter for this seat: built today, month against the ceiling, window and reset, allowance, queued/running/delivered jobs, the last wall, and the seat's own Pro/Max limit bars.",
4507
4704
  inputSchema: { type: "object", properties: {} }
4508
4705
  },
4706
+ {
4707
+ name: "metrics",
4708
+ description: "DORA and batch-size metrics for a period, against the same length just before it: deploy frequency, lead time, change failure rate, time to recover and production deploys by batch size. For one workspace, or — with no `workspace` — the whole org, one line per workspace. Only the workspaces this seat sees. The same days the Metrics page counts.",
4709
+ inputSchema: {
4710
+ type: "object",
4711
+ properties: {
4712
+ workspace: {
4713
+ type: "string",
4714
+ description: "The workspace's slug. Leave it out for the whole org."
4715
+ },
4716
+ range: {
4717
+ type: "string",
4718
+ enum: METRICS_RANGES.map((r) => r.key),
4719
+ description: "A preset period ending today; default 2w. Leave it out when giving from."
4720
+ },
4721
+ from: {
4722
+ type: "string",
4723
+ description: "First day of a custom period, YYYY-MM-DD (UTC). At most 366 days."
4724
+ },
4725
+ to: {
4726
+ type: "string",
4727
+ description: "Last day of a custom period, YYYY-MM-DD, included; default today."
4728
+ }
4729
+ }
4730
+ }
4731
+ },
4509
4732
  {
4510
4733
  name: "estimate",
4511
4734
  description: "Call this BEFORE starting any build that will take more than a few minutes — before the first line of work, not after. Sizes the task: the org estimator's cost and time band with confidence and a spend cap, never a point figure. Pass a stable `source` (kind · project · doc · item) so the item has a home on the sled and the later `actual` finds it. Recorded, so actuals calibrate the next estimate.",
@@ -5443,12 +5666,6 @@ var init_recommend = __esm(() => {
5443
5666
  var init_profile2 = __esm(() => {
5444
5667
  init_profile();
5445
5668
  });
5446
- // ../../packages/shared/src/item/outcome.ts
5447
- var OUTCOME_WINDOW_DAYS = 14, OUTCOME_WINDOW_MS;
5448
- var init_outcome = __esm(() => {
5449
- OUTCOME_WINDOW_MS = OUTCOME_WINDOW_DAYS * 86400000;
5450
- });
5451
-
5452
5669
  // ../../packages/shared/src/slack/mention.ts
5453
5670
  var MENTION_HELP;
5454
5671
  var init_mention = __esm(() => {
@@ -5472,11 +5689,20 @@ var init_copy = __esm(() => {
5472
5689
  });
5473
5690
 
5474
5691
  // ../../packages/shared/src/slack/agent.ts
5692
+ var REPLY_RULES;
5475
5693
  var init_agent = __esm(() => {
5476
5694
  init_intake();
5477
5695
  init_brief();
5478
5696
  init_copy();
5479
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
+ `);
5480
5706
  });
5481
5707
 
5482
5708
  // ../../packages/shared/src/slack/channel.ts
@@ -6534,30 +6760,12 @@ var init_deploys = __esm(() => {
6534
6760
  SERVICE_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/;
6535
6761
  });
6536
6762
 
6537
- // ../../packages/shared/src/deploys/metrics.ts
6538
- var WEEK_MS;
6539
- var init_metrics = __esm(() => {
6540
- WEEK_MS = 7 * 24 * 60 * 60 * 1000;
6541
- });
6542
-
6543
- // ../../packages/shared/src/deploys/period.ts
6544
- var DAY_MS2;
6545
- var init_period = __esm(() => {
6546
- DAY_MS2 = 24 * 60 * 60 * 1000;
6547
- });
6548
-
6549
6763
  // ../../packages/shared/src/deploys/stability.ts
6550
6764
  var init_stability = __esm(() => {
6551
6765
  init_outcome();
6552
6766
  init_metrics();
6553
6767
  });
6554
6768
 
6555
- // ../../packages/shared/src/deploys/batchSize.ts
6556
- var init_batchSize = __esm(() => {
6557
- init_outcome();
6558
- init_metrics();
6559
- });
6560
-
6561
6769
  // ../../packages/shared/src/newrelic/newrelic.ts
6562
6770
  var init_newrelic = () => {};
6563
6771
 
@@ -7968,6 +8176,7 @@ var init_src = __esm(() => {
7968
8176
  init_period();
7969
8177
  init_stability();
7970
8178
  init_batchSize();
8179
+ init_report();
7971
8180
  init_sentry();
7972
8181
  init_newrelic();
7973
8182
  init_musher();
@@ -8345,7 +8554,13 @@ function parseParked(v) {
8345
8554
  ...typeof a.effort === "string" ? { effort: a.effort } : {}
8346
8555
  };
8347
8556
  }
8348
- 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
+ };
8349
8564
  }
8350
8565
  function parseRun(v) {
8351
8566
  if (!isObject8(v))
@@ -8385,7 +8600,10 @@ function parseRun(v) {
8385
8600
  decisionId: v.permission.decisionId,
8386
8601
  options: Array.isArray(v.permission.options) ? v.permission.options.filter((x) => typeof x === "string") : [],
8387
8602
  at: v.permission.at,
8388
- ...v.permission.onScreen === true ? { onScreen: true } : {}
8603
+ ...v.permission.onScreen === true ? { onScreen: true } : {},
8604
+ ...typeof v.permission.pressedAt === "number" ? { pressedAt: v.permission.pressedAt } : {},
8605
+ ...typeof v.permission.presses === "number" ? { presses: v.permission.presses } : {},
8606
+ ...v.permission.gaveUp === true ? { gaveUp: true } : {}
8389
8607
  }
8390
8608
  } : {},
8391
8609
  ...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 } } : {},
@@ -8692,6 +8910,7 @@ init_metrics();
8692
8910
  init_period();
8693
8911
  init_stability();
8694
8912
  init_batchSize();
8913
+ init_report();
8695
8914
  init_sentry();
8696
8915
  init_newrelic();
8697
8916
  init_musher();
@@ -11042,6 +11261,8 @@ var orcaLauncher = (o) => `#!/bin/sh
11042
11261
  ${execLine(agentShellCommand(o))}
11043
11262
  `;
11044
11263
  var orcaCommand = (launcher) => `sh ${shq2(launcher)}; exit`;
11264
+ 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;
11265
+ var ESC = "\x1B";
11045
11266
  var GONE = /exit|clos|gone|dead|stopp|kill/i;
11046
11267
  var orcaTerminalLive = (status, stdout) => {
11047
11268
  const env = parseOrcaJson(stdout);
@@ -11126,6 +11347,7 @@ function orcaRunner(exec, deps = {}) {
11126
11347
  }
11127
11348
  return env;
11128
11349
  };
11350
+ const retries = new Map;
11129
11351
  return {
11130
11352
  name: "orca",
11131
11353
  makesWorktree: true,
@@ -11177,10 +11399,51 @@ function orcaRunner(exec, deps = {}) {
11177
11399
  return r.status === 0 ? r.stdout : "";
11178
11400
  },
11179
11401
  send(h, text) {
11180
- const r = exec("orca", ["terminal", "send", "--terminal", h.id, "--text", text, "--enter"]);
11181
- if (r.status !== 0) {
11182
- throw new Error(`orca terminal send ${h.id} failed: ${r.stderr.trim() || `exit ${r.status}`}`);
11402
+ const key = `${h.id}
11403
+ ${text}`;
11404
+ const attempt = (retry) => {
11405
+ const r = exec("orca", [
11406
+ "terminal",
11407
+ "send",
11408
+ "--terminal",
11409
+ h.id,
11410
+ "--text",
11411
+ text,
11412
+ "--enter",
11413
+ ...retry ? ["--retry-request", retry] : [],
11414
+ "--json"
11415
+ ]);
11416
+ const env = parseOrcaJson(r.stdout);
11417
+ const ok = r.status === 0 && env?.ok !== false;
11418
+ return { r, env, ok, id: ok ? null : orcaRequestId(env, `${r.stderr}
11419
+ ${r.stdout}`) };
11420
+ };
11421
+ const prior = retries.get(key);
11422
+ let a = attempt(prior);
11423
+ if (!a.ok && a.id && a.id !== prior)
11424
+ a = attempt(a.id);
11425
+ if (a.ok) {
11426
+ retries.delete(key);
11427
+ return;
11428
+ }
11429
+ if (a.id)
11430
+ retries.set(key, a.id);
11431
+ else
11432
+ retries.delete(key);
11433
+ const why = orcaError(a.env) ?? (a.r.stderr.trim() || `exit ${a.r.status}`);
11434
+ throw new Error(`orca terminal send ${h.id} failed: ${why}`);
11435
+ },
11436
+ sendKey(h, key) {
11437
+ if (!isRunnerKey(key))
11438
+ 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;
11183
11444
  }
11445
+ send(["--text", key]);
11446
+ send(["--enter"]);
11184
11447
  },
11185
11448
  stop(h) {
11186
11449
  exec("orca", ["terminal", "close", "--terminal", h.id]);
@@ -11248,7 +11511,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11248
11511
  name: flags.name,
11249
11512
  os: host.os ?? process.platform,
11250
11513
  runner: flags.runner,
11251
- version: host.version ?? "0.1.50" ?? "dev",
11514
+ version: host.version ?? "0.1.52" ?? "dev",
11252
11515
  running: live.length,
11253
11516
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11254
11517
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -12253,6 +12516,10 @@ function permissionsOf(s) {
12253
12516
  const p = s;
12254
12517
  return p && typeof p.openPermission === "function" && typeof p.clearPermission === "function" && typeof p.decisionOf === "function" ? s : null;
12255
12518
  }
12519
+ function resumesOf(s) {
12520
+ const r = s;
12521
+ return r && typeof r.resumeParked === "function" ? s : null;
12522
+ }
12256
12523
  var FAIL_LOG_MAX = 2000;
12257
12524
  var UNREACHABLE3 = "SLEDS unreachable";
12258
12525
  function keysIn(states, state) {
@@ -12437,6 +12704,9 @@ function profileOf(r) {
12437
12704
  const atOnce = num5(p.atOnce);
12438
12705
  if (atOnce !== undefined && atOnce >= 1)
12439
12706
  out.atOnce = Math.round(atOnce);
12707
+ const seatAtOnce = num5(r.seatAtOnce);
12708
+ if (seatAtOnce !== undefined && Number.isInteger(seatAtOnce) && seatAtOnce >= 1)
12709
+ out.seatAtOnce = seatAtOnce;
12440
12710
  if (typeof p.mcpStrict === "boolean")
12441
12711
  out.mcpStrict = p.mcpStrict;
12442
12712
  const holdCpu = num5(p.holdCpu);
@@ -12899,6 +13169,18 @@ function sledsSource(client, o) {
12899
13169
  throw new Error(serverMessage(r));
12900
13170
  return r.json.cleared === true;
12901
13171
  },
13172
+ async resumeParked(runId, why, engine, leaseMin) {
13173
+ const r = await client.postFull("/proxy/run", { runId, engine, leaseMin, resumed: { why } });
13174
+ if (!r)
13175
+ throw new Error(`${UNREACHABLE3} — the resume not recorded`);
13176
+ if (r.status >= 300 || !r.json || r.json.ok === false)
13177
+ throw new Error(serverMessage(r));
13178
+ const leaseUntil = typeof r.json.leaseUntil === "number" ? r.json.leaseUntil : undefined;
13179
+ return {
13180
+ resumed: r.json.resumed === true,
13181
+ ...leaseUntil !== undefined ? { leaseUntil } : {}
13182
+ };
13183
+ },
12902
13184
  async decisionOf(item, decisionId) {
12903
13185
  const route = await routeOf(item);
12904
13186
  const decisions = Array.isArray(route?.decisions) ? route.decisions.filter(isObject8) : [];
@@ -13491,6 +13773,7 @@ async function tick(deps) {
13491
13773
  deps.providerHold = d.providerHold;
13492
13774
  deps.draining = d.draining;
13493
13775
  deps.atOnce = d.atOnce;
13776
+ deps.seatAtOnceNoted = d.seatAtOnceNoted;
13494
13777
  deps.runner = d.runner;
13495
13778
  deps.runnerRefused = d.runnerRefused;
13496
13779
  }
@@ -13562,6 +13845,10 @@ async function tickOnce(d) {
13562
13845
  try {
13563
13846
  open = runner.status(run.handle) === "running";
13564
13847
  } catch {}
13848
+ if (open && await answeredInSession(d, runner, run)) {
13849
+ keep.push(run);
13850
+ continue;
13851
+ }
13565
13852
  if (open || liveAnswerer(run))
13566
13853
  keep.push(run);
13567
13854
  else
@@ -14086,7 +14373,20 @@ async function settlePermission(d, runner, run, shown) {
14086
14373
  const dialog = runner.readsSession ? permissionDialog(shown) : null;
14087
14374
  const p = run.permission;
14088
14375
  if (p) {
14089
- const gone = runner.readsSession && (p.onScreen ? !dialog : (run.seenAt ?? 0) > p.at);
14376
+ const gone = runner.readsSession && (p.onScreen ? !dialog : (run.seenAt ?? 0) > (p.pressedAt ?? p.at));
14377
+ const answered = () => {
14378
+ run.permission = undefined;
14379
+ run.answeredAt = d.now();
14380
+ if (markFile)
14381
+ clearPermissionMark(markFile);
14382
+ };
14383
+ if (p.gaveUp) {
14384
+ if (!gone)
14385
+ return true;
14386
+ d.log(`${at}: the permission dialog was answered in the terminal`);
14387
+ answered();
14388
+ return false;
14389
+ }
14090
14390
  let now;
14091
14391
  try {
14092
14392
  now = await perms.decisionOf(run.item, p.decisionId);
@@ -14095,14 +14395,42 @@ async function settlePermission(d, runner, run, shown) {
14095
14395
  return true;
14096
14396
  }
14097
14397
  if (now?.state === "answered" && now.answer) {
14098
- if (gone)
14099
- d.log(`${at}: the permission was answered after its dialog closed — not pressed`);
14100
- else if (!pressAnswer(d, runner, run, p.options, now.answer))
14398
+ if (p.pressedAt === undefined) {
14399
+ if (gone)
14400
+ d.log(`${at}: the permission was answered after its dialog closed — not pressed`);
14401
+ else {
14402
+ const pressed = pressAnswer(d, runner, run, p.options, now.answer);
14403
+ if (!pressed)
14404
+ return true;
14405
+ if (pressed === "key") {
14406
+ run.permission = { ...p, pressedAt: d.now(), presses: 1 };
14407
+ return true;
14408
+ }
14409
+ }
14410
+ answered();
14101
14411
  return true;
14102
- run.permission = undefined;
14103
- run.answeredAt = d.now();
14104
- if (markFile)
14105
- clearPermissionMark(markFile);
14412
+ }
14413
+ const key = permissionKey(now.answer) ?? now.answer;
14414
+ if (gone) {
14415
+ d.log(`${at}: the permission is answered — pressed ${key}; the session goes on`);
14416
+ answered();
14417
+ return false;
14418
+ }
14419
+ const presses = p.presses ?? 1;
14420
+ if (presses >= PRESS_TRIES) {
14421
+ const why = `pressed ${key} ${presses} times; the dialog stayed — answer it in the terminal`;
14422
+ try {
14423
+ await perms.clearPermission(run.runId, why, d.engine);
14424
+ } catch (e) {
14425
+ d.log(`${at}: could not clear the permission, trying next tick: ${message(e)}`);
14426
+ return true;
14427
+ }
14428
+ d.log(`${at}: ${why}`);
14429
+ run.permission = { ...p, gaveUp: true };
14430
+ return true;
14431
+ }
14432
+ if (pressAnswer(d, runner, run, p.options, now.answer) === "key")
14433
+ run.permission = { ...p, pressedAt: d.now(), presses: presses + 1 };
14106
14434
  return true;
14107
14435
  }
14108
14436
  if (now?.state !== "open" && now?.state !== "escalated") {
@@ -14152,6 +14480,7 @@ async function settlePermission(d, runner, run, shown) {
14152
14480
  d.log(`${at}: stopped at a permission dialog — it waits for a person (${runner.watchHint(run.handle)})`);
14153
14481
  return true;
14154
14482
  }
14483
+ var PRESS_TRIES = 3;
14155
14484
  function pressAnswer(d, runner, run, options, answer) {
14156
14485
  const at = ref(run);
14157
14486
  const key = permissionKey(answer);
@@ -14162,13 +14491,15 @@ function pressAnswer(d, runner, run, options, answer) {
14162
14491
  runner.sendKey(run.handle, key);
14163
14492
  else
14164
14493
  runner.send(run.handle, key);
14494
+ if (runner.readsSession)
14495
+ return "key";
14165
14496
  d.log(`${at}: the permission is answered — pressed ${key}; the session goes on`);
14166
14497
  } else {
14167
14498
  runner.sendKey?.(run.handle, "esc");
14168
14499
  const note = runner.send(run.handle, answer);
14169
14500
  d.log(`${at}: the permission is answered in words — Esc, then typed in${note ? ` (${note})` : ""}`);
14170
14501
  }
14171
- return true;
14502
+ return "done";
14172
14503
  } catch (e) {
14173
14504
  d.log(`${at}: could not press the permission's answer, trying next tick: ${message(e)}`);
14174
14505
  return false;
@@ -14337,10 +14668,25 @@ async function rereadAtOnce(d) {
14337
14668
  try {
14338
14669
  next = await d.rereadAtOnce(profileOf2(d));
14339
14670
  } catch {}
14340
- if (next === null || next === d.atOnce)
14671
+ if (next !== null && next !== d.atOnce) {
14672
+ d.atOnce = next;
14673
+ d.log(`at once now ${next}, from your engine profile`);
14674
+ }
14675
+ await sayAtOnceDisagrees(d);
14676
+ }
14677
+ async function sayAtOnceDisagrees(d) {
14678
+ const seat = (await profileOf2(d)())?.seatAtOnce;
14679
+ if (seat === undefined)
14341
14680
  return;
14342
- d.atOnce = next;
14343
- d.log(`at once now ${next}, from your engine profile`);
14681
+ if (seat === d.atOnce) {
14682
+ d.seatAtOnceNoted = undefined;
14683
+ return;
14684
+ }
14685
+ const pair = `${d.atOnce}/${seat}`;
14686
+ if (d.seatAtOnceNoted === pair)
14687
+ return;
14688
+ d.seatAtOnceNoted = pair;
14689
+ 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`);
14344
14690
  }
14345
14691
  async function rereadRunner(d) {
14346
14692
  if (!d.rereadRunner)
@@ -14394,10 +14740,53 @@ function stopOpen(d, run, runner) {
14394
14740
  }
14395
14741
  }
14396
14742
  function holdParked(d, runner, run, question) {
14397
- run.parked = { question, at: d.now() };
14743
+ run.parked = { question, at: d.now(), ...run.seen ? { screen: run.seen } : {} };
14398
14744
  run.leaseUntil = undefined;
14399
14745
  setLane(d, runner, run.handle, "parked");
14400
14746
  }
14747
+ async function answeredInSession(d, runner, run) {
14748
+ const parked = run.parked;
14749
+ const resumes = resumesOf(d.source);
14750
+ if (!resumes || !runner.readsSession || !run.runId)
14751
+ return false;
14752
+ let print;
14753
+ try {
14754
+ print = fingerprint(runner.read(run.handle));
14755
+ } catch {
14756
+ return false;
14757
+ }
14758
+ if (print === parked.screen) {
14759
+ parked.still = true;
14760
+ return false;
14761
+ }
14762
+ const moved = parked.still === true;
14763
+ parked.screen = print;
14764
+ parked.still = false;
14765
+ if (!moved)
14766
+ return false;
14767
+ let r;
14768
+ try {
14769
+ r = await resumes.resumeParked(run.runId, "answered in the session", d.engine, d.leaseMin);
14770
+ } catch (e) {
14771
+ d.log(`${ref(run)}: the parked session moved on, but SLEDS did not hear it: ${message(e)}`);
14772
+ return false;
14773
+ }
14774
+ if (!r.resumed) {
14775
+ d.log(`${ref(run)}: the parked session moved on, but the node is no longer parked on its run`);
14776
+ return false;
14777
+ }
14778
+ stopAnswerer(d, run);
14779
+ const now = d.now();
14780
+ run.parked = undefined;
14781
+ run.seen = print;
14782
+ run.seenAt = now;
14783
+ run.seenTicks = 0;
14784
+ run.answeredAt = now;
14785
+ run.leaseUntil = r.leaseUntil ?? now + d.leaseMin * 60000;
14786
+ setLane(d, runner, run.handle, "working");
14787
+ d.log(`${ref(run)}: answered in the session — out of Needs you, running again`);
14788
+ return true;
14789
+ }
14401
14790
  function setLane(d, runner, h, lane) {
14402
14791
  try {
14403
14792
  runner.lane?.(h, lane);
@@ -16973,7 +17362,7 @@ async function fetchVersion(o) {
16973
17362
  const platform = o.platform ?? process.platform;
16974
17363
  const arch = o.arch ?? process.arch;
16975
17364
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
16976
- const running = o.running ?? "0.1.50";
17365
+ const running = o.running ?? "0.1.52";
16977
17366
  if (!publicKey)
16978
17367
  return { ok: false, why: "no release key yet" };
16979
17368
  if (!VERSION_RE2.test(o.version))
@@ -18557,7 +18946,7 @@ function engineClient(o, org) {
18557
18946
  return fetchClient({
18558
18947
  ...src,
18559
18948
  timeoutMs: ENGINE_TIMEOUT_MS,
18560
- version: "0.1.50"
18949
+ version: "0.1.52"
18561
18950
  });
18562
18951
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
18563
18952
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -18848,7 +19237,7 @@ async function driveEngine(o, flags, deps, until = {}) {
18848
19237
  runner: deps.runner.name,
18849
19238
  atOnce: flags.atOnce,
18850
19239
  interval: flags.interval,
18851
- cliVersion: "0.1.50"
19240
+ cliVersion: "0.1.52"
18852
19241
  };
18853
19242
  const beat = (checkedAt) => {
18854
19243
  const m = deps.machine;
@@ -20705,7 +21094,7 @@ function engineClient2(o, org) {
20705
21094
  return fetchClient({
20706
21095
  ...src,
20707
21096
  timeoutMs: ENGINE_TIMEOUT_MS2,
20708
- version: "0.1.50"
21097
+ version: "0.1.52"
20709
21098
  });
20710
21099
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
20711
21100
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -21031,7 +21420,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21031
21420
  runner: deps.runner.name,
21032
21421
  atOnce: flags.atOnce,
21033
21422
  interval: flags.interval,
21034
- cliVersion: "0.1.50"
21423
+ cliVersion: "0.1.52"
21035
21424
  };
21036
21425
  const beat = (checkedAt) => {
21037
21426
  const m = deps.machine;
@@ -23119,7 +23508,7 @@ function contextFor(deps) {
23119
23508
  ...src,
23120
23509
  fetchFn: deps.fetchFn,
23121
23510
  timeoutMs: deps.timeoutMs,
23122
- version: "0.1.50"
23511
+ version: "0.1.52"
23123
23512
  }), deps.home, now) : null;
23124
23513
  return {
23125
23514
  channel: "code",
@@ -25633,14 +26022,16 @@ function routeReader(o, org) {
25633
26022
  read: (k) => readOrSay(client, `/proxy/route?q=${encodeURIComponent(k)}`, k)
25634
26023
  };
25635
26024
  }
25636
- var SLED_USAGE = "usage: sleds sled --json | sleds sled top <KEY> | sleds sled order <KEY>…";
26025
+ var SLED_USAGE = "usage: sleds sled --json | sleds sled top <KEY> | sleds sled order <KEY>… | sleds sled off <KEY> [--json]";
25637
26026
  async function sledCmd(o) {
25638
26027
  const log = o.log ?? console.log;
25639
26028
  const error = o.error ?? console.error;
25640
26029
  const top = o.argv[0] === "top" && o.argv.length === 2 && !o.argv[1]?.startsWith("-");
25641
26030
  const keys = o.argv.slice(1);
25642
26031
  const order = o.argv[0] === "order" && keys.length > 0 && keys.every((k) => k.trim() && !k.startsWith("-"));
25643
- if (!top && !order && (o.argv.length !== 1 || o.argv[0] !== "--json")) {
26032
+ const offArgs = o.argv.slice(1).filter((a) => a !== "--json");
26033
+ const off = o.argv[0] === "off" && offArgs.length === 1 && o.argv.length <= 3 && !!offArgs[0]?.trim() && !offArgs[0].startsWith("-");
26034
+ if (!top && !order && !off && (o.argv.length !== 1 || o.argv[0] !== "--json")) {
25644
26035
  error(SLED_USAGE);
25645
26036
  return 2;
25646
26037
  }
@@ -25653,6 +26044,8 @@ async function sledCmd(o) {
25653
26044
  return sledTop(client, o.argv[1], log, error);
25654
26045
  if (order)
25655
26046
  return sledOrder(client, keys, log, error);
26047
+ if (off)
26048
+ return sledOff(client, offArgs[0], o.argv.includes("--json"), log, error);
25656
26049
  try {
25657
26050
  log(JSON.stringify(await readOrSay(client, "/proxy/sled", "the sled")));
25658
26051
  return 0;
@@ -25687,6 +26080,31 @@ async function sledOrder(client, keys, log, error) {
25687
26080
  log(`${typeof r.json?.ordered === "number" ? r.json.ordered : keys.length} ordered on your sled`);
25688
26081
  return 0;
25689
26082
  }
26083
+ async function sledOff(client, key, json, log, error) {
26084
+ const r = await client.postFull("/proxy/sled/off", { project: key });
26085
+ if (!r) {
26086
+ error(`${key}: SLEDS unreachable — the sled is unchanged`);
26087
+ return 1;
26088
+ }
26089
+ if (r.status >= 300 || r.json?.ok === false) {
26090
+ error(typeof r.json?.error === "string" ? r.json.error : `${key}: HTTP ${r.status}`);
26091
+ return 1;
26092
+ }
26093
+ if (json) {
26094
+ log(JSON.stringify(r.json));
26095
+ return 0;
26096
+ }
26097
+ const k = typeof r.json?.key === "string" ? r.json.key : key;
26098
+ if (r.json?.changed === false) {
26099
+ log(`${k} was not on a sled`);
26100
+ return 0;
26101
+ }
26102
+ log(`${k} is off your sled — back in the pool`);
26103
+ const running = typeof r.json?.running === "number" ? r.json.running : 0;
26104
+ if (running > 0)
26105
+ log(`${running} running node${running === 1 ? "" : "s"} stopped`);
26106
+ return 0;
26107
+ }
25690
26108
 
25691
26109
  // src/commands/run.ts
25692
26110
  init_config();
@@ -27205,7 +27623,7 @@ var realPidAlive4 = (pid) => {
27205
27623
  };
27206
27624
  function machineClient(home, env) {
27207
27625
  const src = meterSource(home, undefined, { env });
27208
- return src ? fetchClient({ ...src, version: "0.1.50" }) : null;
27626
+ return src ? fetchClient({ ...src, version: "0.1.52" }) : null;
27209
27627
  }
27210
27628
  async function noteAnswer2(home, a, now) {
27211
27629
  try {
@@ -27825,6 +28243,7 @@ commands:
27825
28243
  sled --json What is on your sled (mine with each route, proposed, sent), for a front end.
27826
28244
  sleds sled top KEY put KEY first on your sled; the engine picks it next
27827
28245
  sleds sled order KEY\u2026 those items in this order, in the places they hold; the rest stay
28246
+ sleds sled off KEY take KEY off your sled, back in the pool; its running nodes stop
27828
28247
  route <key> Print an item's route: its nodes, what each waits on, what is ready, where each
27829
28248
  run can be watched.
27830
28249
  --dot Graphviz instead: \`sleds route QW-142 --dot | dot -Tpng -o route.png\`
@@ -28050,7 +28469,7 @@ var main = async (argv) => {
28050
28469
  now: Date.now(),
28051
28470
  stderr: (line) => console.error(line),
28052
28471
  isTTY: !!process.stderr.isTTY,
28053
- version: "0.1.50"
28472
+ version: "0.1.52"
28054
28473
  });
28055
28474
  switch (cmd) {
28056
28475
  case "update":
@@ -28283,7 +28702,7 @@ ${renderHelpText(argv[1])}`);
28283
28702
  case "version":
28284
28703
  case "--version":
28285
28704
  case "-v":
28286
- console.log("sleds 0.1.50");
28705
+ console.log("sleds 0.1.52");
28287
28706
  return 0;
28288
28707
  default:
28289
28708
  console.error(`unknown command: ${cmd}
@@ -28304,10 +28723,18 @@ var isMain = () => {
28304
28723
  return false;
28305
28724
  }
28306
28725
  };
28726
+ var drained = (stream) => new Promise((resolve) => {
28727
+ stream.once("error", () => resolve());
28728
+ stream.write("", () => resolve());
28729
+ });
28730
+ var exitDrained = async (code) => {
28731
+ await Promise.all([drained(process.stdout), drained(process.stderr)]);
28732
+ process.exit(code);
28733
+ };
28307
28734
  if (isMain()) {
28308
- main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
28735
+ main(process.argv.slice(2)).then((code) => exitDrained(code), (err) => {
28309
28736
  console.error(err instanceof Error ? err.message : String(err));
28310
- process.exit(1);
28737
+ return exitDrained(1);
28311
28738
  });
28312
28739
  }
28313
28740
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
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",