@sleds/cli 0.1.56 → 0.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/sleds.mjs +444 -67
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -64,7 +64,7 @@ sleds engine the long-running loop over every route Run
64
64
  sleds engine --jira EPIC-7 the same loop over a Jira epic's Blocks links, no SLEDS route needed
65
65
  ```
66
66
 
67
- For each ready node the engine makes a worktree, starts an agent session on the node's branch (default `route/{item}-{node}`), and follows its PR to the merge: CI failures and review feedback go back to the session; a merge lands the node, closes the session, removes the worktree and, on Jira, moves the issue to Done. Stopping the engine leaves its sessions running — the next start picks them up from its state file. `sleds route run` keeps its own state file, so it can run beside a `sleds engine` on the same box.
67
+ For each ready node the engine makes a worktree, starts an agent session on the node's branch (default `route/{item}-{node}`; an item synced from Jira or Linear carries its key after the SLEDS key — `route/sl-2091-WORK-123-a`, worktree `sleds-sl-2091-WORK-123-a` — and the session starts its commit subjects and PR title with it, so the ticket's Development panel shows the work), and follows its PR to the merge: CI failures and review feedback go back to the session; a merge lands the node, closes the session, removes the worktree and, on Jira, moves the issue to Done. Stopping the engine leaves its sessions running — the next start picks them up from its state file. `sleds route run` keeps its own state file, so it can run beside a `sleds engine` on the same box.
68
68
 
69
69
  Where each node runs is `--runner`:
70
70
 
package/dist/sleds.mjs CHANGED
@@ -6013,6 +6013,36 @@ var init_fieldMap = __esm(() => {
6013
6013
  ]);
6014
6014
  });
6015
6015
 
6016
+ // ../../packages/shared/src/markdown/markdown.ts
6017
+ var init_markdown3 = () => {};
6018
+
6019
+ // ../../packages/shared/src/tracker/adf.ts
6020
+ var BLOCK_TYPES;
6021
+ var init_adf = __esm(() => {
6022
+ init_markdown3();
6023
+ BLOCK_TYPES = new Set([
6024
+ "paragraph",
6025
+ "heading",
6026
+ "bulletList",
6027
+ "orderedList",
6028
+ "taskList",
6029
+ "codeBlock",
6030
+ "blockquote",
6031
+ "panel",
6032
+ "rule",
6033
+ "table",
6034
+ "mediaSingle",
6035
+ "mediaGroup",
6036
+ "blockCard",
6037
+ "embedCard",
6038
+ "expand",
6039
+ "nestedExpand",
6040
+ "layoutSection",
6041
+ "layoutColumn",
6042
+ "decisionList"
6043
+ ]);
6044
+ });
6045
+
6016
6046
  // ../../packages/shared/src/help/whatsNew.ts
6017
6047
  var SEPT_30 = "/whats-new/2026-09-30", WHATS_NEW;
6018
6048
  var init_whatsNew = __esm(() => {
@@ -7059,11 +7089,21 @@ function planTakes(o) {
7059
7089
  }
7060
7090
  return out;
7061
7091
  }
7062
- function nodeBranch(item, node) {
7063
- return `route/${slugify(`${item}-${node}`, 60) || "node"}`;
7092
+ function trackerTicket(item, ticket) {
7093
+ const t = ticket?.trim();
7094
+ if (!t || !/^[A-Za-z][A-Za-z0-9]*-\d+$/.test(t) || t.toUpperCase() === item.toUpperCase())
7095
+ return;
7096
+ return t.toUpperCase();
7097
+ }
7098
+ function nodeBranch(item, node, ticket) {
7099
+ const t = trackerTicket(item, ticket);
7100
+ if (!t)
7101
+ return `route/${slugify(`${item}-${node}`, 60) || "node"}`;
7102
+ const n = slugify(node, 60);
7103
+ return `route/${[slugify(item, 60), t, n === t.toLowerCase() ? "" : n].filter(Boolean).join("-")}`;
7064
7104
  }
7065
- function sessionName(item, node) {
7066
- return nodeBranch(item, node).replace(/^route\//, "sleds-");
7105
+ function sessionName(item, node, ticket) {
7106
+ return nodeBranch(item, node, ticket).replace(/^route\//, "sleds-");
7067
7107
  }
7068
7108
  function fill(template, values) {
7069
7109
  return template.replace(/\{([A-Za-z_]+)\}/g, (whole, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : whole);
@@ -7093,7 +7133,7 @@ function branchFor(template, claim) {
7093
7133
  slug: slugify(claim.title, 40),
7094
7134
  ticket: one(claim.ticket ?? "")
7095
7135
  }));
7096
- return name || nodeBranch(claim.item, claim.node);
7136
+ return name || nodeBranch(claim.item, claim.node, claim.ticket);
7097
7137
  }
7098
7138
  function prTitleFor(template, claim) {
7099
7139
  return fill(template, {
@@ -7167,11 +7207,16 @@ function nodePrompt(o) {
7167
7207
  const plan = planBlock(o.plan, o.upstream ?? []);
7168
7208
  const visual = visualSurface(claim.pack);
7169
7209
  const deviations = planLines(o.plan).length ? ", and where you departed from the plan and why" : "";
7210
+ const ticket = trackerTicket(claim.item, claim.ticket);
7211
+ const title = o.prTitle ? ` and title it exactly \`${o.prTitle}\` (the repo's CI checks it)` : ticket ? ` and start its title with \`${ticket}\`` : "";
7170
7212
  const lines = [
7171
7213
  `You are node ${claim.node} of ${claim.item}'s route — "${claim.title}" — started by ${o.engine}.`,
7172
7214
  "",
7173
7215
  `- Work on branch \`${o.branch}\` from the latest \`origin/${base}\` (create it if this checkout is not on it). One branch, one PR.`,
7174
- `- Open the PR against \`${base}\` with \`gh pr create${visual ? " --draft" : ""}\`${o.prTitle ? ` and title it exactly \`${o.prTitle}\` (the repo's CI checks it)` : ""}. Its body carries \`SLEDS-Node: ${claim.node}\` on the line under \`SLEDS-Source:\` when the pack names a source.`,
7216
+ ...ticket ? [
7217
+ `- Start every commit subject with \`${ticket}\` — ${claim.item} is synced from its tracker, which shows the branch, commits and PR on the ticket by that key.`
7218
+ ] : [],
7219
+ `- Open the PR against \`${base}\` with \`gh pr create${visual ? " --draft" : ""}\`${title}. Its body carries \`SLEDS-Node: ${claim.node}\` on the line under \`SLEDS-Source:\` when the pack names a source.`,
7175
7220
  ...claim.pack.includes(PR_HEADER_OPEN) ? [
7176
7221
  `- The PR body starts with the pack's PR header block (\`${PR_HEADER_OPEN}\` … \`${PR_HEADER_CLOSE}\`, in its part 5), above everything else. The pack wrote it before your work existed: when you make a mock (a screenshot or an artifact) or write a doc, put its link on that part's line in place of \`None\` — not only lower in the body (SL-1746). When you edit the body later, keep it on top.`
7177
7222
  ] : [],
@@ -7529,7 +7574,8 @@ var init_engine = __esm(() => {
7529
7574
  };
7530
7575
  VERDICT_SHAPE = [
7531
7576
  "- **Verdict first.** The first line of `md` (and of `why`) is the verdict on its own, in plain words: what to do, or what is so. No file paths, line numbers or links in it.",
7532
- "- Then at most 3 short points, one line each, as a `-` list. Cites go in `cites`, not in the prose."
7577
+ "- Then at most 3 short points, one line each, as a `-` list. Cites go in `cites`, not in the prose.",
7578
+ "- **One cite per span, no prose in it.** Code is `code:<path>:<line>`, the path from the repo root (`code:apps/web/src/App.tsx:42`); cite each line on its own, the cards merge them by file."
7533
7579
  ];
7534
7580
  DECIDER_RULES = [
7535
7581
  "- **Text after the input prompt was never sent.** On a session's screen, whatever sits after its last input prompt (`>`, `❯`, `›`) is a draft in the input box: nobody sent it. It is not a person's words, an instruction or a decision — never read it, or cite it, as one.",
@@ -7537,6 +7583,9 @@ var init_engine = __esm(() => {
7537
7583
  "- **No third repeat.** When the decision log shows this node already asked this question (or one the same in substance) and it was answered twice, a third answer of the same kind will not move it. Escalate, saying it keeps coming back, or release it: tell the session to stop waiting and finish with what it has (open its PR, or land and say what is owed)."
7538
7584
  ];
7539
7585
  });
7586
+
7587
+ // ../../packages/shared/src/route/sources.ts
7588
+ var init_sources = () => {};
7540
7589
  // ../../packages/shared/src/cowork/otlp.ts
7541
7590
  var OTLP_KEEP;
7542
7591
  var init_otlp = __esm(() => {
@@ -8177,6 +8226,7 @@ var init_src = __esm(() => {
8177
8226
  init_statusMap();
8178
8227
  init_confluence();
8179
8228
  init_fieldMap();
8229
+ init_adf();
8180
8230
  init_whatsNew();
8181
8231
  init_cheatSheet();
8182
8232
  init_pack2();
@@ -8194,6 +8244,7 @@ var init_src = __esm(() => {
8194
8244
  init_doneChecks();
8195
8245
  init_routeFix();
8196
8246
  init_engine();
8247
+ init_sources();
8197
8248
  init_otlp();
8198
8249
  init_session();
8199
8250
  init_effort();
@@ -8596,6 +8647,7 @@ function parseRun(v) {
8596
8647
  ...typeof v.mcpConfig === "string" ? { mcpConfig: v.mcpConfig } : {},
8597
8648
  ...v.onRunToken === true ? { onRunToken: true } : {},
8598
8649
  ...isObject8(v.group) && typeof v.group.key === "string" && typeof v.group.title === "string" ? { group: { key: v.group.key, title: v.group.title } } : {},
8650
+ ...typeof v.ticket === "string" ? { ticket: v.ticket } : {},
8599
8651
  branch,
8600
8652
  repoPath,
8601
8653
  handle,
@@ -8645,7 +8697,14 @@ function parseRun(v) {
8645
8697
  ...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
8646
8698
  ...conflicted ? { conflicted } : {},
8647
8699
  ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
8648
- ...typeof v.pr.look === "string" ? { look: v.pr.look } : {}
8700
+ ...typeof v.pr.look === "string" ? { look: v.pr.look } : {},
8701
+ ...isObject8(v.pr.instructed) && Array.isArray(v.pr.instructed.ids) && (v.pr.instructed.how === "told" || v.pr.instructed.how === "restarted") ? {
8702
+ instructed: {
8703
+ ids: v.pr.instructed.ids.filter((x) => typeof x === "string"),
8704
+ how: v.pr.instructed.how
8705
+ }
8706
+ } : {},
8707
+ ...typeof v.pr.findings === "string" ? { findings: v.pr.findings } : {}
8649
8708
  }
8650
8709
  } : {}
8651
8710
  };
@@ -8922,6 +8981,7 @@ init_template();
8922
8981
  init_statusMap();
8923
8982
  init_confluence();
8924
8983
  init_fieldMap();
8984
+ init_adf();
8925
8985
  init_whatsNew();
8926
8986
  init_cheatSheet();
8927
8987
  init_pack2();
@@ -8939,6 +8999,7 @@ init_route();
8939
8999
  init_doneChecks();
8940
9000
  init_routeFix();
8941
9001
  init_engine();
9002
+ init_sources();
8942
9003
  init_otlp();
8943
9004
  init_session();
8944
9005
  init_effort();
@@ -11390,6 +11451,26 @@ function orcaRunner(exec, deps = {}) {
11390
11451
  return env;
11391
11452
  };
11392
11453
  const retries = new Map;
11454
+ const terminalIn = (cwd, o) => {
11455
+ const launcher = launcherPath2(o.promptFile);
11456
+ writeFile(launcher, orcaLauncher(o));
11457
+ const shown = orca([
11458
+ "terminal",
11459
+ "create",
11460
+ "--worktree",
11461
+ `path:${cwd}`,
11462
+ "--title",
11463
+ o.name,
11464
+ "--command",
11465
+ orcaCommand(launcher),
11466
+ "--json"
11467
+ ], `orca terminal create ${o.name} (worktree ${cwd})`);
11468
+ const terminal = parseOrcaTerminal(shown);
11469
+ if (!terminal) {
11470
+ throw new Error(`orca terminal create ${o.name} started in ${cwd} but returned no terminal handle`);
11471
+ }
11472
+ return terminal;
11473
+ };
11393
11474
  return {
11394
11475
  name: "orca",
11395
11476
  makesWorktree: true,
@@ -11411,27 +11492,14 @@ function orcaRunner(exec, deps = {}) {
11411
11492
  if (!cwd) {
11412
11493
  throw new Error(`orca worktree create ${o.name} returned no worktree path; the agent was not started`);
11413
11494
  }
11414
- const launcher = launcherPath2(o.promptFile);
11415
- writeFile(launcher, orcaLauncher(o));
11416
- const shown = orca([
11417
- "terminal",
11418
- "create",
11419
- "--worktree",
11420
- `path:${cwd}`,
11421
- "--title",
11422
- o.name,
11423
- "--command",
11424
- orcaCommand(launcher),
11425
- "--json"
11426
- ], `orca terminal create ${o.name} (worktree ${cwd})`);
11427
- const terminal = parseOrcaTerminal(shown);
11428
- if (!terminal) {
11429
- throw new Error(`orca terminal create ${o.name} started in ${cwd} but returned no terminal handle`);
11430
- }
11495
+ const terminal = terminalIn(cwd, o);
11431
11496
  const own = parseOrcaBranch(created);
11432
11497
  const h = { runner: "orca", id: terminal, cwd, ...own ? { own } : {} };
11433
11498
  return h;
11434
11499
  },
11500
+ reopen(h, o) {
11501
+ return { ...h, id: terminalIn(h.cwd, o) };
11502
+ },
11435
11503
  status(h) {
11436
11504
  const r = exec("orca", ["terminal", "show", "--terminal", h.id, "--json"]);
11437
11505
  return orcaTerminalLive(r.status, r.stdout) ? "running" : "exited";
@@ -11546,7 +11614,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11546
11614
  name: flags.name,
11547
11615
  os: host.os ?? process.platform,
11548
11616
  runner: flags.runner,
11549
- version: host.version ?? "0.1.56" ?? "dev",
11617
+ version: host.version ?? "0.1.58" ?? "dev",
11550
11618
  running: live.length,
11551
11619
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11552
11620
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -11838,7 +11906,7 @@ function noteEvent(line) {
11838
11906
 
11839
11907
  // src/engine/gh.ts
11840
11908
  init_src();
11841
- var isGhError = (r) => r !== null && ("error" in r);
11909
+ var isGhError = (r) => r !== null && typeof r === "object" && ("error" in r);
11842
11910
  function prForBranch(exec, repoPath, branch) {
11843
11911
  try {
11844
11912
  const r = exec("gh", [
@@ -11880,6 +11948,74 @@ function prForBranch(exec, repoPath, branch) {
11880
11948
  return { error: e instanceof Error ? e.message : String(e) };
11881
11949
  }
11882
11950
  }
11951
+ function carriesKey(text, key) {
11952
+ const k = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11953
+ return new RegExp(`(^|[^A-Za-z0-9])${k}(?![0-9])`, "i").test(text);
11954
+ }
11955
+ var isTrackerKey = (key) => /^[A-Za-z][A-Za-z0-9]*-\d+$/.test(key);
11956
+ function otherWork(exec, repoPath, key, own) {
11957
+ const out = [];
11958
+ try {
11959
+ const r = exec("gh", [
11960
+ "pr",
11961
+ "list",
11962
+ "--state",
11963
+ "open",
11964
+ "--json",
11965
+ "url,title,headRefName,isCrossRepository,author",
11966
+ "--limit",
11967
+ "100"
11968
+ ], { cwd: repoPath });
11969
+ if (r.status !== 0)
11970
+ return { error: r.stderr.trim() || `gh pr list exited with status ${r.status}` };
11971
+ let rows;
11972
+ try {
11973
+ rows = JSON.parse(r.stdout);
11974
+ } catch {
11975
+ return { error: "gh pr list did not print JSON" };
11976
+ }
11977
+ if (!Array.isArray(rows))
11978
+ return { error: "gh pr list did not print a list" };
11979
+ for (const row of rows) {
11980
+ if (typeof row !== "object" || row === null)
11981
+ continue;
11982
+ const { url, title, headRefName, isCrossRepository, author } = row;
11983
+ if (typeof url !== "string" || !url)
11984
+ continue;
11985
+ const branch = typeof headRefName === "string" ? headRefName : "";
11986
+ const named = typeof title === "string" ? title : "";
11987
+ if (!carriesKey(branch, key) && !carriesKey(named, key))
11988
+ continue;
11989
+ if (isCrossRepository !== true && own(branch))
11990
+ continue;
11991
+ const login = typeof author === "object" && author !== null ? author.login : undefined;
11992
+ out.push({
11993
+ branch,
11994
+ pr: { url, title: named, ...typeof login === "string" && login ? { author: login } : {} }
11995
+ });
11996
+ }
11997
+ } catch (e) {
11998
+ return { error: e instanceof Error ? e.message : String(e) };
11999
+ }
12000
+ try {
12001
+ const r = exec("git", ["ls-remote", "--heads", "origin"], { cwd: repoPath });
12002
+ if (r.status === 0) {
12003
+ const listed = new Set(out.map((w) => w.branch));
12004
+ for (const line of r.stdout.split(`
12005
+ `)) {
12006
+ const branch = line.split("\t")[1]?.trim().replace(/^refs\/heads\//, "") ?? "";
12007
+ if (!branch || listed.has(branch) || own(branch) || !carriesKey(branch, key))
12008
+ continue;
12009
+ listed.add(branch);
12010
+ out.push({ branch });
12011
+ }
12012
+ }
12013
+ } catch {}
12014
+ return out;
12015
+ }
12016
+ function otherWorkLine(work) {
12017
+ return work.map((w) => w.pr ? `${w.pr.url}${w.pr.author ? ` by ${w.pr.author}` : ""} (${w.branch})` : `branch ${w.branch} (no PR yet)`).join("; ");
12018
+ }
11883
12019
  var URL_RE = /https:\/\/[^\s)>\]"']+/;
11884
12020
  var ARTIFACT_RE = /https:\/\/claude\.ai\/[^\s)>\]"']*artifact\/[^\s)>\]"']+/;
11885
12021
  function mockOf(body) {
@@ -11961,6 +12097,8 @@ function countFeedback(stdout, author) {
11961
12097
  const n = { all: 0, author: 0 };
11962
12098
  for (const line of stdout.split(`
11963
12099
  `)) {
12100
+ if (line.startsWith(BOT_LINE))
12101
+ continue;
11964
12102
  const [login = "", marked, hasBody] = line.trim().split("\t");
11965
12103
  if (!login || marked === "true")
11966
12104
  continue;
@@ -11972,12 +12110,14 @@ function countFeedback(stdout, author) {
11972
12110
  }
11973
12111
  return n;
11974
12112
  }
11975
- var FEEDBACK_JQ = `.[] | select(.user.type != "Bot") | [.user.login, ((.body // "") | contains("${ENGINE_MARK}") | tostring), ((.body // "") | length > 0 | tostring)] | @tsv`;
12113
+ var BOT_LINE = "bot\t";
12114
+ var FEEDBACK_JQ = `.[] | if .user.type == "Bot" then (select((.body // "") | length > 0) | "${BOT_LINE}" + ({by: .user.login, url: .html_url, md: .body, at: (.submitted_at // .created_at // "")} | tojson)) else ([.user.login, ((.body // "") | contains("${ENGINE_MARK}") | tostring), ((.body // "") | length > 0 | tostring)] | @tsv) end`;
11976
12115
  function prFeedback(exec, repoPath, url, author) {
11977
12116
  const ref = prRef(url);
11978
12117
  if (!ref)
11979
12118
  return null;
11980
12119
  const total = { all: 0, author: 0 };
12120
+ const bots = [];
11981
12121
  for (const path of [
11982
12122
  `repos/${ref.repo}/issues/${ref.number}/comments`,
11983
12123
  `repos/${ref.repo}/pulls/${ref.number}/reviews`,
@@ -11990,11 +12130,38 @@ function prFeedback(exec, repoPath, url, author) {
11990
12130
  const n = countFeedback(r.stdout, author);
11991
12131
  total.all += n.all;
11992
12132
  total.author += n.author;
12133
+ bots.push(...parseBotFindings(r.stdout));
11993
12134
  } catch {
11994
12135
  return null;
11995
12136
  }
11996
12137
  }
11997
- return total;
12138
+ const findings = bots.sort((a, b) => a.at.localeCompare(b.at)).slice(-FINDINGS_MAX).map(({ by, url, md }) => ({ by, url, md }));
12139
+ return { ...total, findings };
12140
+ }
12141
+ var FINDINGS_MAX = 20;
12142
+ var FINDING_MD_MAX = 2000;
12143
+ function parseBotFindings(stdout) {
12144
+ const out = [];
12145
+ for (const line of stdout.split(`
12146
+ `)) {
12147
+ if (!line.startsWith(BOT_LINE))
12148
+ continue;
12149
+ try {
12150
+ const o = JSON.parse(line.slice(BOT_LINE.length));
12151
+ if (typeof o.by !== "string" || typeof o.url !== "string" || typeof o.md !== "string")
12152
+ continue;
12153
+ const md = o.md.trim();
12154
+ if (!md || md.includes(ENGINE_MARK))
12155
+ continue;
12156
+ out.push({
12157
+ by: o.by,
12158
+ url: o.url,
12159
+ md: md.slice(0, FINDING_MD_MAX),
12160
+ at: typeof o.at === "string" ? o.at : ""
12161
+ });
12162
+ } catch {}
12163
+ }
12164
+ return out;
11998
12165
  }
11999
12166
  var MERGE_QUEUE_QUERY = "query($url: URI!) { resource(url: $url) { ... on PullRequest { mergeQueueEntry { state } } } }";
12000
12167
  function prInMergeQueue(exec, repoPath, url) {
@@ -12032,11 +12199,19 @@ function prView(exec, repoPath, url, last) {
12032
12199
  const ready = !parsed.queued && parsed.failing.length === 0 && parsed.pending === 0 && parsed.reviewDecision === "APPROVED";
12033
12200
  const view = ready && prInMergeQueue(exec, repoPath, url) === true ? { ...parsed, queued: true } : parsed;
12034
12201
  let read = view.updatedAt && last?.updatedAt === view.updatedAt ? last : undefined;
12202
+ let findings = null;
12035
12203
  if (!read) {
12036
12204
  const f = prFeedback(exec, repoPath, url, view.author);
12037
- read = f && { ...f, updatedAt: view.updatedAt };
12038
- }
12039
- return read ? { ...view, feedback: read.all, authorFeedback: read.author, read } : view;
12205
+ read = f && { all: f.all, author: f.author, updatedAt: view.updatedAt };
12206
+ findings = f?.findings ?? null;
12207
+ }
12208
+ return read ? {
12209
+ ...view,
12210
+ feedback: read.all,
12211
+ authorFeedback: read.author,
12212
+ read,
12213
+ ...findings ? { findings } : {}
12214
+ } : view;
12040
12215
  } catch {
12041
12216
  return null;
12042
12217
  }
@@ -12149,7 +12324,7 @@ function epicItem(epic, nodes) {
12149
12324
  landed: nodes.length > 0 && nodes.every((n) => n.category === "done")
12150
12325
  };
12151
12326
  }
12152
- var BLOCK_TYPES = new Set([
12327
+ var BLOCK_TYPES2 = new Set([
12153
12328
  "paragraph",
12154
12329
  "heading",
12155
12330
  "listItem",
@@ -12174,7 +12349,7 @@ function adfText(doc) {
12174
12349
  if (Array.isArray(n.content))
12175
12350
  for (const c of n.content)
12176
12351
  walk(c);
12177
- if (typeof n.type === "string" && BLOCK_TYPES.has(n.type)) {
12352
+ if (typeof n.type === "string" && BLOCK_TYPES2.has(n.type)) {
12178
12353
  const last = out[out.length - 1];
12179
12354
  if (last !== undefined && !last.endsWith(`
12180
12355
  `))
@@ -12584,6 +12759,32 @@ function resumesOf(s) {
12584
12759
  const r = s;
12585
12760
  return r && typeof r.resumeParked === "function" ? s : null;
12586
12761
  }
12762
+ function instructionsOf(s) {
12763
+ const i = s;
12764
+ return i && typeof i.instructions === "function" ? s : null;
12765
+ }
12766
+ function instructionsFrom(waiting) {
12767
+ if (!Array.isArray(waiting))
12768
+ return [];
12769
+ return waiting.filter(isObject8).flatMap((w) => {
12770
+ const id = str6(w.id);
12771
+ const md = str6(w.md);
12772
+ if (!id || !md)
12773
+ return [];
12774
+ const f = isObject8(w.finding) ? w.finding : null;
12775
+ const kind = f?.kind === "review" || f?.kind === "bot" ? f.kind : null;
12776
+ const url = f ? str6(f.url) : undefined;
12777
+ const by = f ? str6(f.by) : undefined;
12778
+ return [
12779
+ {
12780
+ id,
12781
+ md,
12782
+ by: str6(w.by) ?? "a person",
12783
+ ...kind ? { finding: { kind, ...url ? { url } : {}, ...by ? { by } : {} } } : {}
12784
+ }
12785
+ ];
12786
+ });
12787
+ }
12587
12788
  var FAIL_LOG_MAX = 2000;
12588
12789
  var UNREACHABLE3 = "SLEDS unreachable";
12589
12790
  function keysIn(states, state) {
@@ -12829,6 +13030,10 @@ function nextPickup(r, o) {
12829
13030
  continue;
12830
13031
  if (isPrMirror(row.source))
12831
13032
  continue;
13033
+ if (route.packable === false)
13034
+ continue;
13035
+ if (isObject8(route.elsewhere))
13036
+ continue;
12832
13037
  const states = isObject8(route.states) ? route.states : {};
12833
13038
  if (statesLanded(states) || keysIn(states, "running").length > 0)
12834
13039
  continue;
@@ -12858,6 +13063,8 @@ function armedDrafts(r, engine) {
12858
13063
  return false;
12859
13064
  if (route.drafting === true || route.proposed === true || route.draftParked)
12860
13065
  return false;
13066
+ if (isObject8(route.elsewhere))
13067
+ return false;
12861
13068
  if (strings(route.needsRoute).length === 0 || isPrMirror(row.source))
12862
13069
  return false;
12863
13070
  const states = isObject8(route.states) ? route.states : {};
@@ -13062,7 +13269,8 @@ function sledsSource(client, o) {
13062
13269
  ...str6(hit.answer) ? { answer: str6(hit.answer) } : {},
13063
13270
  ...runToken ? { runToken } : {},
13064
13271
  ...group ? { group } : {},
13065
- ...run ? { run } : {}
13272
+ ...run ? { run } : {},
13273
+ ...str6(hit.ticket) ? { ticket: str6(hit.ticket) } : {}
13066
13274
  };
13067
13275
  return { ok: true, claim };
13068
13276
  },
@@ -13085,7 +13293,7 @@ function sledsSource(client, o) {
13085
13293
  return "running";
13086
13294
  if (claim.kind === "draft") {
13087
13295
  const filed = Array.isArray(route.nodes) && route.nodes.length > 0;
13088
- if (filed || isObject8(route.proposed))
13296
+ if (filed || isObject8(route.proposed) || isObject8(route.elsewhere))
13089
13297
  return "done";
13090
13298
  if (isObject8(route.draftParked))
13091
13299
  return "parked";
@@ -13285,6 +13493,23 @@ function sledsSource(client, o) {
13285
13493
  return false;
13286
13494
  }
13287
13495
  },
13496
+ async instructions(item, node, o) {
13497
+ try {
13498
+ const r = await client.postFull("/proxy/prread", {
13499
+ project: item,
13500
+ node,
13501
+ instructions: {
13502
+ ...o.findings ? { findings: o.findings } : {},
13503
+ ...o.delivered ? { delivered: o.delivered } : {}
13504
+ }
13505
+ });
13506
+ if (!r || r.status < 200 || r.status >= 300 || !r.json)
13507
+ return null;
13508
+ return r.json.ok === false ? [] : instructionsFrom(r.json.waiting);
13509
+ } catch {
13510
+ return null;
13511
+ }
13512
+ },
13288
13513
  async answerDecision(a, engine, answer) {
13289
13514
  const r = await client.postFull("/proxy/run", { runId: a.runId, engine, answer });
13290
13515
  if (!r)
@@ -14252,8 +14477,8 @@ async function tickOnce(d) {
14252
14477
  await report({ kind: "release", why: repoPath.error });
14253
14478
  continue;
14254
14479
  }
14255
- const branch = d.branchOf ? d.branchOf(claim) : nodeBranch(claim.item, claim.node);
14256
- const name = sessionName(claim.item, claim.node);
14480
+ const branch = d.branchOf ? d.branchOf(claim) : nodeBranch(claim.item, claim.node, claim.ticket);
14481
+ const name = sessionName(claim.item, claim.node, claim.ticket);
14257
14482
  const found = d.prForBranch(d.exec, repoPath, branch);
14258
14483
  if (isGhError(found))
14259
14484
  d.log(`${at}: could not ask GitHub for ${branch}'s PR: ${found.error}`);
@@ -14263,6 +14488,12 @@ async function tickOnce(d) {
14263
14488
  await report({ kind: existing.state === "MERGED" ? "merged" : "pr", ref: existing.url });
14264
14489
  continue;
14265
14490
  }
14491
+ const duplicate = duplicateQuestion(d, claim, repoPath, "run");
14492
+ if (duplicate) {
14493
+ d.log(`${at}: parked — ${duplicate}`);
14494
+ await report({ kind: "park", question: duplicate });
14495
+ continue;
14496
+ }
14266
14497
  let start;
14267
14498
  let mcpConfig;
14268
14499
  try {
@@ -14348,6 +14579,7 @@ async function tickOnce(d) {
14348
14579
  ...mcpConfig ? { mcpConfig, ...claim.runToken ? { onRunToken: true } : {} } : {},
14349
14580
  ...claim.group ? { group: claim.group } : {},
14350
14581
  ...packBandOf(claim.pack) ? { band: packBandOf(claim.pack) } : {},
14582
+ ...claim.ticket ? { ticket: claim.ticket } : {},
14351
14583
  branch,
14352
14584
  repoPath,
14353
14585
  handle,
@@ -14473,7 +14705,7 @@ async function settlePermission(d, runner, run, shown) {
14473
14705
  if (!perms || !run.runId)
14474
14706
  return false;
14475
14707
  const at = ref(run);
14476
- const markFile = d.permissionMark?.(sessionName(run.item, run.node));
14708
+ const markFile = d.permissionMark?.(sessionName(run.item, run.node, run.ticket));
14477
14709
  const dialog = runner.readsSession ? permissionDialog(shown) : null;
14478
14710
  const p = run.permission;
14479
14711
  if (p) {
@@ -15322,6 +15554,25 @@ var safeWatch = (runner, h) => {
15322
15554
  return h.id;
15323
15555
  }
15324
15556
  };
15557
+ function duplicateQuestion(d, claim, repoPath, verb) {
15558
+ if (!d.otherWork || claim.answer)
15559
+ return null;
15560
+ const key = claim.ticket ?? claim.item;
15561
+ if (!isTrackerKey(key))
15562
+ return null;
15563
+ const prefix = nodeBranch(claim.item, "x").slice(0, -1);
15564
+ const template = d.branchOf?.(claim);
15565
+ const own = (b) => b.startsWith(prefix) || b === template;
15566
+ const found = d.otherWork(d.exec, repoPath, key, own);
15567
+ if (isGhError(found)) {
15568
+ d.log(`${ref(claim)}: could not look for other work on ${key}: ${found.error}`);
15569
+ return null;
15570
+ }
15571
+ if (!found.length)
15572
+ return null;
15573
+ const what = verb === "plan" ? "plan its route" : `run ${claim.node}`;
15574
+ return `${key} already has work outside this engine: ${otherWorkLine(found)}. Answer to ${what} anyway; if that work is the one to keep, skip the node or close the item instead.`;
15575
+ }
15325
15576
  async function startDraft(d, claim, why, state, started, finished) {
15326
15577
  const at = ref(claim);
15327
15578
  const report = async (outcome) => {
@@ -15338,6 +15589,12 @@ async function startDraft(d, claim, why, state, started, finished) {
15338
15589
  await report({ kind: "park", question: repoPath.error });
15339
15590
  return;
15340
15591
  }
15592
+ const duplicate = duplicateQuestion(d, claim, repoPath, "plan");
15593
+ if (duplicate) {
15594
+ d.log(`${at}: parked — ${duplicate}`);
15595
+ await report({ kind: "park", question: duplicate });
15596
+ return;
15597
+ }
15341
15598
  const name = sessionName(claim.item, DRAFT_NODE);
15342
15599
  const branch = nodeBranch(claim.item, DRAFT_NODE);
15343
15600
  let handle;
@@ -15503,6 +15760,30 @@ function ciNudge(url, failing) {
15503
15760
  var SELF_NUDGES = 2;
15504
15761
  var FEEDBACK_REREAD_MS = 10 * 60000;
15505
15762
  var CONFLICT_RECHECK_MS = 10 * 60000;
15763
+ var INSTRUCTION_READ_MS = 60000;
15764
+ function instructionText(i) {
15765
+ const from = i.finding ? ` about ${i.finding.kind === "review" ? "SLEDS's review" : `${i.finding.by ?? "a bot reviewer"}'s finding`}${i.finding.url ? ` (${i.finding.url})` : ""}` : "";
15766
+ return `From ${i.by}${from}:
15767
+
15768
+ ${i.md.trim()}`;
15769
+ }
15770
+ function instructionNudge(url, items) {
15771
+ const replies = items.some((i) => i.finding?.url) ? " Where one came from a finding on the PR, reply on that finding's thread with the fix or why not, and leave the thread for the reviewer to resolve." : "";
15772
+ return `Instructions for your PR (${url}), written on its item in SLEDS:
15773
+
15774
+ ${items.map(instructionText).join(`
15775
+
15776
+ ---
15777
+
15778
+ `)}
15779
+
15780
+ Do them on your own branch, run the repo's gate, and push to the same PR.${replies} End every comment and reply you post on the PR with the line ${ENGINE_MARK}.`;
15781
+ }
15782
+ function instructionPrompt(run, url, items) {
15783
+ return `You are picking up node ${run.node} of ${run.item}. Its PR is open: ${url}, on the branch ${run.branch}, checked out in this worktree. The session that opened it has ended. Read the PR first (gh pr view ${url} --comments, and the inline threads), then follow the instructions below. Never force-push, never merge, never open a second PR.
15784
+
15785
+ ${instructionNudge(url, items)}`;
15786
+ }
15506
15787
  function reviewNudge(url, count) {
15507
15788
  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.`;
15508
15789
  }
@@ -15620,6 +15901,7 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
15620
15901
  pr.selfNudges = selfNudges || undefined;
15621
15902
  }
15622
15903
  }
15904
+ await carryInstructions(d, run, runner, view, attention);
15623
15905
  pr.failing = view.failing;
15624
15906
  pr.feedback = Math.max(pr.feedback, view.feedback);
15625
15907
  pr.authorFeedback = Math.max(pr.authorFeedback ?? 0, view.authorFeedback) || undefined;
@@ -16141,22 +16423,37 @@ async function answerBaseConflict(d, events, state, dec, attention, decided) {
16141
16423
  return false;
16142
16424
  }
16143
16425
  function startResolver(d, run, e, answer) {
16426
+ const name = sessionName(run.item, `${run.node}-resolve`);
16427
+ const started = restartInWorktree(d, run, name, eventAnswerNudge(e, answer), e.base);
16428
+ if (started !== true) {
16429
+ if (started)
16430
+ d.log(`${ref(run)}: could not start a resolver session: ${started.error}`);
16431
+ return false;
16432
+ }
16433
+ const runner = runnerFor(d, run.handle);
16434
+ if (run.pr)
16435
+ run.pr.resolving = e.baseSha;
16436
+ d.log(`${ref(run)}: a resolver session has the conflict brief — ${safeWatch(runner, run.handle)}`);
16437
+ return true;
16438
+ }
16439
+ function restartInWorktree(d, run, name, prompt, base) {
16144
16440
  const runner = runnerFor(d, run.handle);
16145
- if (!runner || runner.makesWorktree || run.handle.cwd === run.repoPath)
16441
+ if (!runner || run.handle.cwd === run.repoPath)
16442
+ return false;
16443
+ if (runner.makesWorktree && !runner.reopen)
16146
16444
  return false;
16147
- const name = sessionName(run.item, `${run.node}-resolve`);
16148
16445
  try {
16149
16446
  runner.stop(run.handle);
16150
16447
  } catch {}
16151
16448
  try {
16152
16449
  const launch = nodeLaunchFor(d, ref(run));
16153
- run.handle = runner.start({
16450
+ const o = {
16154
16451
  name,
16155
16452
  repoPath: run.repoPath,
16156
16453
  worktree: run.handle.cwd,
16157
16454
  branch: run.branch,
16158
- base: e.base,
16159
- promptFile: d.writePrompt(name, eventAnswerNudge(e, answer)),
16455
+ ...base ? { base } : {},
16456
+ promptFile: d.writePrompt(name, prompt),
16160
16457
  permissionMode: d.permissionMode,
16161
16458
  ...launchOptions(launch),
16162
16459
  ...run.mcpConfig ? { mcpConfig: run.mcpConfig, ...d.strictMcp ? { strictMcp: true } : {} } : {},
@@ -16164,16 +16461,75 @@ function startResolver(d, run, e, answer) {
16164
16461
  ...hooksFor(d, name),
16165
16462
  ...mainCheckoutFor(d, run.handle.cwd),
16166
16463
  ...run.group ? { group: run.group } : {}
16167
- });
16464
+ };
16465
+ run.handle = runner.makesWorktree ? runner.reopen(run.handle, o) : runner.start(o);
16168
16466
  } catch (err) {
16169
- d.log(`${ref(run)}: could not start a resolver session: ${message(err)}`);
16170
- return false;
16467
+ return { error: message(err) };
16171
16468
  }
16172
- if (run.pr)
16173
- run.pr.resolving = e.baseSha;
16174
- d.log(`${ref(run)}: a resolver session has the conflict brief — ${safeWatch(runner, run.handle)}`);
16175
16469
  return true;
16176
16470
  }
16471
+ async function carryInstructions(d, run, runner, view, attention) {
16472
+ const source = instructionsOf(d.source);
16473
+ const pr = run.pr;
16474
+ if (!source)
16475
+ return;
16476
+ const findings = view.findings;
16477
+ const findingsKey = findings?.map((f) => f.url).join(" ");
16478
+ const newFindings = findings !== undefined && findingsKey !== pr.findings;
16479
+ const due = !pr.instructAt || d.now() - pr.instructAt >= INSTRUCTION_READ_MS;
16480
+ if (!due && !newFindings && !pr.instructed)
16481
+ return;
16482
+ const waiting = await source.instructions(run.item, run.node, {
16483
+ ...newFindings ? { findings } : {},
16484
+ ...pr.instructed ? { delivered: pr.instructed } : {}
16485
+ });
16486
+ if (!waiting)
16487
+ return;
16488
+ pr.instructAt = d.now();
16489
+ if (newFindings)
16490
+ pr.findings = findingsKey;
16491
+ const told = pr.instructed?.ids ?? [];
16492
+ pr.instructed = undefined;
16493
+ const fresh = waiting.filter((i) => !told.includes(i.id));
16494
+ if (fresh.length === 0)
16495
+ return;
16496
+ const at = { item: run.item, node: run.node, url: pr.url };
16497
+ const what = `${fresh.length} instruction${fresh.length === 1 ? "" : "s"} from the item`;
16498
+ let status;
16499
+ try {
16500
+ status = runner.status(run.handle);
16501
+ if (status === "running") {
16502
+ const note = runner.send(run.handle, instructionNudge(pr.url, fresh));
16503
+ d.log(`${ref(run)}: ${what} — told the session${note ? ` (${note})` : ""}`);
16504
+ pr.instructed = { ids: fresh.map((i) => i.id), how: "told" };
16505
+ }
16506
+ } catch {}
16507
+ if (!pr.instructed && status === "exited") {
16508
+ const name = sessionName(run.item, `${run.node}-instruct`);
16509
+ const started = restartInWorktree(d, run, name, instructionPrompt(run, pr.url, fresh));
16510
+ if (started === true) {
16511
+ d.log(`${ref(run)}: ${what} — its session had ended; a new one has them on ${run.branch}: ${safeWatch(runnerFor(d, run.handle) ?? d.runner, run.handle)}`);
16512
+ pr.instructed = { ids: fresh.map((i) => i.id), how: "restarted" };
16513
+ pr.selfNudges = undefined;
16514
+ } else if (started)
16515
+ d.log(`${ref(run)}: could not restart the session: ${started.error}`);
16516
+ }
16517
+ if (pr.instructed) {
16518
+ const after = await source.instructions(run.item, run.node, { delivered: pr.instructed });
16519
+ if (after)
16520
+ pr.instructed = undefined;
16521
+ return;
16522
+ }
16523
+ const said = `${ref(run)}:instruct:${fresh.map((i) => i.id).join(",")}`;
16524
+ if (d.refused?.get(said))
16525
+ return;
16526
+ d.refused?.set(said, "said");
16527
+ d.log(`${ref(run)}: ${what} could not reach a session (${status === "running" ? "the session takes no input" : "the runner cannot restart it"}) — needs a person: ${pr.url}`);
16528
+ attention.push({
16529
+ ...at,
16530
+ what: `${what} waiting — no session can take them; open the worktree ${run.handle.cwd}`
16531
+ });
16532
+ }
16177
16533
  async function startEventAnswerer(d, answers, state, dec, decided) {
16178
16534
  const at = ref(dec);
16179
16535
  const kind = dec.event.kind;
@@ -17323,7 +17679,7 @@ import {
17323
17679
  } from "node:fs";
17324
17680
 
17325
17681
  // ../../packages/shared/src/updates/releaseKey.ts
17326
- var RELEASE_PUBLIC_KEY = "";
17682
+ var RELEASE_PUBLIC_KEY = "MCowBQYDK2VwAyEADIZiQghghQlN6WoLDF9xLmqMeFSQL4crcCBT4wINNCc=";
17327
17683
 
17328
17684
  // ../../packages/shared/src/updates/verify.ts
17329
17685
  import { createPrivateKey, createPublicKey as createPublicKey2, sign, verify } from "node:crypto";
@@ -17554,7 +17910,7 @@ async function fetchVersion(o) {
17554
17910
  const platform = o.platform ?? process.platform;
17555
17911
  const arch = o.arch ?? process.arch;
17556
17912
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
17557
- const running = o.running ?? "0.1.56";
17913
+ const running = o.running ?? "0.1.58";
17558
17914
  if (!publicKey)
17559
17915
  return { ok: false, why: "no release key yet" };
17560
17916
  if (!VERSION_RE2.test(o.version))
@@ -18833,7 +19189,7 @@ function templateProblem(flag, template, jira = false) {
18833
19189
  return `--${flag} has ${unknown.map((f) => `{${f}}`).join(", ")}, which the engine does not fill — it knows ${known.map((f) => `{${f}}`).join(" ")}`;
18834
19190
  const perNode = jira ? ["node", "key", "ticket", "slug"] : ["node", "slug"];
18835
19191
  if (flag === "branch" && !used.some((f) => perNode.includes(f)))
18836
- return jira ? "--branch needs {node}, {key}, {ticket} or {slug}: without one every node of an item gets the same branch, and the second would find the first one's PR and stop" : "--branch needs {node} or {slug} on a SLEDS route: its nodes share the item's {key} and have no {ticket}, so without one every node gets the same branch and the second would find the first one's PR and stop";
19192
+ return jira ? "--branch needs {node}, {key}, {ticket} or {slug}: without one every node of an item gets the same branch, and the second would find the first one's PR and stop" : "--branch needs {node} or {slug} on a SLEDS route: its nodes share the item's {key} and {ticket}, so without one every node gets the same branch and the second would find the first one's PR and stop";
18837
19193
  return null;
18838
19194
  }
18839
19195
  function defaultEngineName(host) {
@@ -19139,7 +19495,7 @@ function engineClient(o, org, breaker) {
19139
19495
  ...src,
19140
19496
  ...breaker ? { fetchFn: breaker.fetch } : {},
19141
19497
  timeoutMs: ENGINE_TIMEOUT_MS,
19142
- version: "0.1.56"
19498
+ version: "0.1.58"
19143
19499
  });
19144
19500
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
19145
19501
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -19316,6 +19672,7 @@ function engineDeps(o, flags, source, runner, stateName) {
19316
19672
  defaultBranch,
19317
19673
  runnerOf: (name) => name === runner.name ? runner : restoreRunner(name, exec),
19318
19674
  prForBranch,
19675
+ otherWork,
19319
19676
  prView,
19320
19677
  mergeTree,
19321
19678
  prFiles,
@@ -19430,7 +19787,7 @@ async function driveEngine(o, flags, deps, until = {}) {
19430
19787
  runner: deps.runner.name,
19431
19788
  atOnce: flags.atOnce,
19432
19789
  interval: flags.interval,
19433
- cliVersion: "0.1.56"
19790
+ cliVersion: "0.1.58"
19434
19791
  };
19435
19792
  const beat = (checkedAt) => {
19436
19793
  const m = deps.machine;
@@ -20939,7 +21296,7 @@ function templateProblem2(flag, template, jira = false) {
20939
21296
  return `--${flag} has ${unknown.map((f) => `{${f}}`).join(", ")}, which the engine does not fill — it knows ${known.map((f) => `{${f}}`).join(" ")}`;
20940
21297
  const perNode = jira ? ["node", "key", "ticket", "slug"] : ["node", "slug"];
20941
21298
  if (flag === "branch" && !used.some((f) => perNode.includes(f)))
20942
- return jira ? "--branch needs {node}, {key}, {ticket} or {slug}: without one every node of an item gets the same branch, and the second would find the first one's PR and stop" : "--branch needs {node} or {slug} on a SLEDS route: its nodes share the item's {key} and have no {ticket}, so without one every node gets the same branch and the second would find the first one's PR and stop";
21299
+ return jira ? "--branch needs {node}, {key}, {ticket} or {slug}: without one every node of an item gets the same branch, and the second would find the first one's PR and stop" : "--branch needs {node} or {slug} on a SLEDS route: its nodes share the item's {key} and {ticket}, so without one every node gets the same branch and the second would find the first one's PR and stop";
20943
21300
  return null;
20944
21301
  }
20945
21302
  function defaultEngineName2(host) {
@@ -21297,7 +21654,7 @@ function engineClient2(o, org, breaker) {
21297
21654
  ...src,
21298
21655
  ...breaker ? { fetchFn: breaker.fetch } : {},
21299
21656
  timeoutMs: ENGINE_TIMEOUT_MS2,
21300
- version: "0.1.56"
21657
+ version: "0.1.58"
21301
21658
  });
21302
21659
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
21303
21660
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -21496,6 +21853,7 @@ function engineDeps2(o, flags, source, runner, stateName) {
21496
21853
  defaultBranch,
21497
21854
  runnerOf: (name) => name === runner.name ? runner : restoreRunner(name, exec),
21498
21855
  prForBranch,
21856
+ otherWork,
21499
21857
  prView,
21500
21858
  mergeTree,
21501
21859
  prFiles,
@@ -21623,7 +21981,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21623
21981
  runner: deps.runner.name,
21624
21982
  atOnce: flags.atOnce,
21625
21983
  interval: flags.interval,
21626
- cliVersion: "0.1.56"
21984
+ cliVersion: "0.1.58"
21627
21985
  };
21628
21986
  const beat = (checkedAt) => {
21629
21987
  const m = deps.machine;
@@ -23726,7 +24084,7 @@ function contextFor(deps) {
23726
24084
  ...src,
23727
24085
  fetchFn: deps.fetchFn,
23728
24086
  timeoutMs: deps.timeoutMs,
23729
- version: "0.1.56"
24087
+ version: "0.1.58"
23730
24088
  }), deps.home, now) : null;
23731
24089
  return {
23732
24090
  channel: "code",
@@ -23840,6 +24198,8 @@ When the person asks what is on their sled, what they hold, or what to pick up n
23840
24198
 
23841
24199
  Offer the next step in one line: take one (\`sled\` with \`take: "<key>"\`, or \`"<key>/B"\` for one node of its route), or read one whole first (\`item\`).
23842
24200
 
24201
+ An item already being built outside the engine (by hand, in another app) is marked so no engine plans or runs it: \`sleds route elsewhere <key> --note "<why>"\`. It stays on the sled and lands from its PR.
24202
+
23843
24203
  A key SLEDS cannot find may live in another org: the connector reads the org of the seat it signed in with. Say which org that is, and that the item has to be read from a seat in its own org. Do not report it as missing or done.
23844
24204
 
23845
24205
  ## Finish — leave what you learned
@@ -25521,12 +25881,13 @@ init_src();
25521
25881
  import { readFileSync as readFileSync29 } from "node:fs";
25522
25882
  import { hostname as hostname12 } from "node:os";
25523
25883
  var ARM_USAGE = "usage: sleds route arm <key> [--off] [--json] [--name <engine>] [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--mode <permission-mode>] [--model <id>] [--effort <e>] [--branch '<template>'] [--pr-title '<template>'] [--repos-dir <dir>]";
25524
- var USAGE2 = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route propose <key> --file <nodes.json> | sleds route approve <key> | sleds route redraft <key> | sleds route single <key> | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | --ready | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
25884
+ var USAGE2 = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route propose <key> --file <nodes.json> | sleds route approve <key> | sleds route redraft <key> | sleds route single <key> | sleds route elsewhere <key> [--note <text>] | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | --ready | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
25525
25885
  var ANSWER_USAGE = "usage: sleds route answer <key>/<node> <answer…> | sleds route answer <key>/<node> --stdin [--json]";
25526
25886
  var PROPOSE_USAGE = "usage: sleds route propose <key> (--file <nodes.json> | --stdin) [--name <engine>] [--json]";
25527
25887
  var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--json]";
25528
25888
  var REDRAFT_USAGE = "usage: sleds route redraft <key> [--json]";
25529
25889
  var SINGLE_USAGE = "usage: sleds route single <key> [--json]";
25890
+ var ELSEWHERE_USAGE = "usage: sleds route elsewhere <key> [--note <text>] [--json]";
25530
25891
  var DECISION_USAGE = "usage: sleds route decision <key> <id> --seen | sleds route decision <key> <id> (--answer | --overrule) (<answer…> | --stdin) | sleds route decision <key> <id> [--answer (<answer…> | --stdin)] --route accept|keep|redraft | sleds route decision <key> <id> --keep-route | sleds route decision <key> <id> --apply [--answer (<answer…> | --stdin)] | sleds route decision <key> <id> --ready [--json]";
25531
25892
  var ROUTE_VERDICTS = ["accept", "keep", "redraft"];
25532
25893
  function nodesFromJson(text) {
@@ -25579,6 +25940,8 @@ function parseVerbArgs(argv, allowed) {
25579
25940
  out.file = value;
25580
25941
  if (flag === "name")
25581
25942
  out.name = value;
25943
+ if (flag === "note")
25944
+ out.note = value;
25582
25945
  i++;
25583
25946
  } else if (a.startsWith("-"))
25584
25947
  return { error: `unknown flag ${a}` };
@@ -25811,10 +26174,13 @@ async function redraftCmd(o, argv) {
25811
26174
  async function singleCmd(o, argv) {
25812
26175
  return oneVerb(o, argv, SINGLE_USAGE, { single: true }, "nothing changed", (key) => singleLine(key));
25813
26176
  }
25814
- async function oneVerb(o, argv, usage, body, unreachable, line) {
26177
+ async function elsewhereCmd(o, argv) {
26178
+ return oneVerb(o, argv, ELSEWHERE_USAGE, (a) => ({ elsewhere: true, ...a.note ? { note: a.note } : {} }), "nothing marked", (key) => elsewhereLine(key), ["note"]);
26179
+ }
26180
+ async function oneVerb(o, argv, usage, body, unreachable, line, flags = []) {
25815
26181
  const log = o.log ?? console.log;
25816
26182
  const error = o.error ?? console.error;
25817
- const a = parseVerbArgs(argv, ["json"]);
26183
+ const a = parseVerbArgs(argv, ["json", ...flags]);
25818
26184
  if ("error" in a) {
25819
26185
  error(a.error);
25820
26186
  error(usage);
@@ -25825,7 +26191,10 @@ async function oneVerb(o, argv, usage, body, unreachable, line) {
25825
26191
  error(client.error);
25826
26192
  return 1;
25827
26193
  }
25828
- const r = await client.postFull("/proxy/route", { project: a.key, ...body });
26194
+ const r = await client.postFull("/proxy/route", {
26195
+ project: a.key,
26196
+ ...typeof body === "function" ? body(a) : body
26197
+ });
25829
26198
  if (!r) {
25830
26199
  error(`${a.key}: SLEDS unreachable — ${unreachable}`);
25831
26200
  return 1;
@@ -25839,6 +26208,9 @@ function redraftLine(key) {
25839
26208
  function singleLine(key) {
25840
26209
  return `${key} runs as one node — it is not planned again`;
25841
26210
  }
26211
+ function elsewhereLine(key) {
26212
+ return `${key} marked built elsewhere — no engine runs it`;
26213
+ }
25842
26214
  function answeredLine(key, route, held) {
25843
26215
  const nodes = Array.isArray(held) && held.length ? ` (${held.join(", ")})` : "";
25844
26216
  if (route === "accept")
@@ -26106,6 +26478,8 @@ var routeCmd = async (o) => {
26106
26478
  return redraftCmd(o, o.argv.slice(1));
26107
26479
  if (o.argv[0] === "single")
26108
26480
  return singleCmd(o, o.argv.slice(1));
26481
+ if (o.argv[0] === "elsewhere")
26482
+ return elsewhereCmd(o, o.argv.slice(1));
26109
26483
  if (o.argv[0] === "decision")
26110
26484
  return decisionCmd(o, o.argv.slice(1));
26111
26485
  if (o.argv[0] === "decisions")
@@ -27844,7 +28218,7 @@ var realPidAlive4 = (pid) => {
27844
28218
  };
27845
28219
  function machineClient(home, env) {
27846
28220
  const src = meterSource(home, undefined, { env });
27847
- return src ? fetchClient({ ...src, version: "0.1.56" }) : null;
28221
+ return src ? fetchClient({ ...src, version: "0.1.58" }) : null;
27848
28222
  }
27849
28223
  async function noteAnswer2(home, a, now) {
27850
28224
  try {
@@ -28494,7 +28868,10 @@ commands:
28494
28868
  approve <key> a person's yes to a planned route: files it and, under
28495
28869
  Pickup: Auto, arms it; --file <nodes.json> approves an
28496
28870
  edited set instead (--json)
28497
- run <key> run the engine for this one item until its route lands
28871
+ elsewhere <key> built outside the engine: no engine plans or runs it, it
28872
+ stays on your sled and lands from its PR; --note "<why>"
28873
+ goes on the thread (--json)
28874
+ run <key> run the engine for this one item until its route lands
28498
28875
  --at-once <n> sessions at once (default 1; one per seat \u2014 tick seats
28499
28876
  when you press Run to run more than one)
28500
28877
  --runner tmux|headless|command|orca where each node runs (default tmux, any
@@ -28704,7 +29081,7 @@ var main = async (argv) => {
28704
29081
  now: Date.now(),
28705
29082
  stderr: (line) => console.error(line),
28706
29083
  isTTY: !!process.stderr.isTTY,
28707
- version: "0.1.56"
29084
+ version: "0.1.58"
28708
29085
  });
28709
29086
  switch (cmd) {
28710
29087
  case "update":
@@ -28937,7 +29314,7 @@ ${renderHelpText(argv[1])}`);
28937
29314
  case "version":
28938
29315
  case "--version":
28939
29316
  case "-v":
28940
- console.log("sleds 0.1.56");
29317
+ console.log("sleds 0.1.58");
28941
29318
  return 0;
28942
29319
  default:
28943
29320
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
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",