@sleds/cli 0.1.57 → 0.1.59
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.
- package/README.md +1 -1
- package/dist/sleds.mjs +410 -65
- 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
|
@@ -7089,11 +7089,21 @@ function planTakes(o) {
|
|
|
7089
7089
|
}
|
|
7090
7090
|
return out;
|
|
7091
7091
|
}
|
|
7092
|
-
function
|
|
7093
|
-
|
|
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("-")}`;
|
|
7094
7104
|
}
|
|
7095
|
-
function sessionName(item, node) {
|
|
7096
|
-
return nodeBranch(item, node).replace(/^route\//, "sleds-");
|
|
7105
|
+
function sessionName(item, node, ticket) {
|
|
7106
|
+
return nodeBranch(item, node, ticket).replace(/^route\//, "sleds-");
|
|
7097
7107
|
}
|
|
7098
7108
|
function fill(template, values) {
|
|
7099
7109
|
return template.replace(/\{([A-Za-z_]+)\}/g, (whole, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : whole);
|
|
@@ -7123,7 +7133,7 @@ function branchFor(template, claim) {
|
|
|
7123
7133
|
slug: slugify(claim.title, 40),
|
|
7124
7134
|
ticket: one(claim.ticket ?? "")
|
|
7125
7135
|
}));
|
|
7126
|
-
return name || nodeBranch(claim.item, claim.node);
|
|
7136
|
+
return name || nodeBranch(claim.item, claim.node, claim.ticket);
|
|
7127
7137
|
}
|
|
7128
7138
|
function prTitleFor(template, claim) {
|
|
7129
7139
|
return fill(template, {
|
|
@@ -7197,11 +7207,16 @@ function nodePrompt(o) {
|
|
|
7197
7207
|
const plan = planBlock(o.plan, o.upstream ?? []);
|
|
7198
7208
|
const visual = visualSurface(claim.pack);
|
|
7199
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}\`` : "";
|
|
7200
7212
|
const lines = [
|
|
7201
7213
|
`You are node ${claim.node} of ${claim.item}'s route — "${claim.title}" — started by ${o.engine}.`,
|
|
7202
7214
|
"",
|
|
7203
7215
|
`- Work on branch \`${o.branch}\` from the latest \`origin/${base}\` (create it if this checkout is not on it). One branch, one PR.`,
|
|
7204
|
-
|
|
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.`,
|
|
7205
7220
|
...claim.pack.includes(PR_HEADER_OPEN) ? [
|
|
7206
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.`
|
|
7207
7222
|
] : [],
|
|
@@ -7559,7 +7574,8 @@ var init_engine = __esm(() => {
|
|
|
7559
7574
|
};
|
|
7560
7575
|
VERDICT_SHAPE = [
|
|
7561
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.",
|
|
7562
|
-
"- 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."
|
|
7563
7579
|
];
|
|
7564
7580
|
DECIDER_RULES = [
|
|
7565
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.",
|
|
@@ -7567,6 +7583,9 @@ var init_engine = __esm(() => {
|
|
|
7567
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)."
|
|
7568
7584
|
];
|
|
7569
7585
|
});
|
|
7586
|
+
|
|
7587
|
+
// ../../packages/shared/src/route/sources.ts
|
|
7588
|
+
var init_sources = () => {};
|
|
7570
7589
|
// ../../packages/shared/src/cowork/otlp.ts
|
|
7571
7590
|
var OTLP_KEEP;
|
|
7572
7591
|
var init_otlp = __esm(() => {
|
|
@@ -8225,6 +8244,7 @@ var init_src = __esm(() => {
|
|
|
8225
8244
|
init_doneChecks();
|
|
8226
8245
|
init_routeFix();
|
|
8227
8246
|
init_engine();
|
|
8247
|
+
init_sources();
|
|
8228
8248
|
init_otlp();
|
|
8229
8249
|
init_session();
|
|
8230
8250
|
init_effort();
|
|
@@ -8627,6 +8647,7 @@ function parseRun(v) {
|
|
|
8627
8647
|
...typeof v.mcpConfig === "string" ? { mcpConfig: v.mcpConfig } : {},
|
|
8628
8648
|
...v.onRunToken === true ? { onRunToken: true } : {},
|
|
8629
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 } : {},
|
|
8630
8651
|
branch,
|
|
8631
8652
|
repoPath,
|
|
8632
8653
|
handle,
|
|
@@ -8676,7 +8697,14 @@ function parseRun(v) {
|
|
|
8676
8697
|
...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
|
|
8677
8698
|
...conflicted ? { conflicted } : {},
|
|
8678
8699
|
...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
|
|
8679
|
-
...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 } : {}
|
|
8680
8708
|
}
|
|
8681
8709
|
} : {}
|
|
8682
8710
|
};
|
|
@@ -8971,6 +8999,7 @@ init_route();
|
|
|
8971
8999
|
init_doneChecks();
|
|
8972
9000
|
init_routeFix();
|
|
8973
9001
|
init_engine();
|
|
9002
|
+
init_sources();
|
|
8974
9003
|
init_otlp();
|
|
8975
9004
|
init_session();
|
|
8976
9005
|
init_effort();
|
|
@@ -11422,6 +11451,26 @@ function orcaRunner(exec, deps = {}) {
|
|
|
11422
11451
|
return env;
|
|
11423
11452
|
};
|
|
11424
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
|
+
};
|
|
11425
11474
|
return {
|
|
11426
11475
|
name: "orca",
|
|
11427
11476
|
makesWorktree: true,
|
|
@@ -11443,27 +11492,14 @@ function orcaRunner(exec, deps = {}) {
|
|
|
11443
11492
|
if (!cwd) {
|
|
11444
11493
|
throw new Error(`orca worktree create ${o.name} returned no worktree path; the agent was not started`);
|
|
11445
11494
|
}
|
|
11446
|
-
const
|
|
11447
|
-
writeFile(launcher, orcaLauncher(o));
|
|
11448
|
-
const shown = orca([
|
|
11449
|
-
"terminal",
|
|
11450
|
-
"create",
|
|
11451
|
-
"--worktree",
|
|
11452
|
-
`path:${cwd}`,
|
|
11453
|
-
"--title",
|
|
11454
|
-
o.name,
|
|
11455
|
-
"--command",
|
|
11456
|
-
orcaCommand(launcher),
|
|
11457
|
-
"--json"
|
|
11458
|
-
], `orca terminal create ${o.name} (worktree ${cwd})`);
|
|
11459
|
-
const terminal = parseOrcaTerminal(shown);
|
|
11460
|
-
if (!terminal) {
|
|
11461
|
-
throw new Error(`orca terminal create ${o.name} started in ${cwd} but returned no terminal handle`);
|
|
11462
|
-
}
|
|
11495
|
+
const terminal = terminalIn(cwd, o);
|
|
11463
11496
|
const own = parseOrcaBranch(created);
|
|
11464
11497
|
const h = { runner: "orca", id: terminal, cwd, ...own ? { own } : {} };
|
|
11465
11498
|
return h;
|
|
11466
11499
|
},
|
|
11500
|
+
reopen(h, o) {
|
|
11501
|
+
return { ...h, id: terminalIn(h.cwd, o) };
|
|
11502
|
+
},
|
|
11467
11503
|
status(h) {
|
|
11468
11504
|
const r = exec("orca", ["terminal", "show", "--terminal", h.id, "--json"]);
|
|
11469
11505
|
return orcaTerminalLive(r.status, r.stdout) ? "running" : "exited";
|
|
@@ -11578,7 +11614,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
|
|
|
11578
11614
|
name: flags.name,
|
|
11579
11615
|
os: host.os ?? process.platform,
|
|
11580
11616
|
runner: flags.runner,
|
|
11581
|
-
version: host.version ?? "0.1.
|
|
11617
|
+
version: host.version ?? "0.1.59" ?? "dev",
|
|
11582
11618
|
running: live.length,
|
|
11583
11619
|
items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
|
|
11584
11620
|
...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
|
|
@@ -11870,7 +11906,7 @@ function noteEvent(line) {
|
|
|
11870
11906
|
|
|
11871
11907
|
// src/engine/gh.ts
|
|
11872
11908
|
init_src();
|
|
11873
|
-
var isGhError = (r) => r !== null && ("error" in r);
|
|
11909
|
+
var isGhError = (r) => r !== null && typeof r === "object" && ("error" in r);
|
|
11874
11910
|
function prForBranch(exec, repoPath, branch) {
|
|
11875
11911
|
try {
|
|
11876
11912
|
const r = exec("gh", [
|
|
@@ -11912,6 +11948,74 @@ function prForBranch(exec, repoPath, branch) {
|
|
|
11912
11948
|
return { error: e instanceof Error ? e.message : String(e) };
|
|
11913
11949
|
}
|
|
11914
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
|
+
}
|
|
11915
12019
|
var URL_RE = /https:\/\/[^\s)>\]"']+/;
|
|
11916
12020
|
var ARTIFACT_RE = /https:\/\/claude\.ai\/[^\s)>\]"']*artifact\/[^\s)>\]"']+/;
|
|
11917
12021
|
function mockOf(body) {
|
|
@@ -11993,6 +12097,8 @@ function countFeedback(stdout, author) {
|
|
|
11993
12097
|
const n = { all: 0, author: 0 };
|
|
11994
12098
|
for (const line of stdout.split(`
|
|
11995
12099
|
`)) {
|
|
12100
|
+
if (line.startsWith(BOT_LINE))
|
|
12101
|
+
continue;
|
|
11996
12102
|
const [login = "", marked, hasBody] = line.trim().split("\t");
|
|
11997
12103
|
if (!login || marked === "true")
|
|
11998
12104
|
continue;
|
|
@@ -12004,12 +12110,14 @@ function countFeedback(stdout, author) {
|
|
|
12004
12110
|
}
|
|
12005
12111
|
return n;
|
|
12006
12112
|
}
|
|
12007
|
-
var
|
|
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`;
|
|
12008
12115
|
function prFeedback(exec, repoPath, url, author) {
|
|
12009
12116
|
const ref = prRef(url);
|
|
12010
12117
|
if (!ref)
|
|
12011
12118
|
return null;
|
|
12012
12119
|
const total = { all: 0, author: 0 };
|
|
12120
|
+
const bots = [];
|
|
12013
12121
|
for (const path of [
|
|
12014
12122
|
`repos/${ref.repo}/issues/${ref.number}/comments`,
|
|
12015
12123
|
`repos/${ref.repo}/pulls/${ref.number}/reviews`,
|
|
@@ -12022,11 +12130,38 @@ function prFeedback(exec, repoPath, url, author) {
|
|
|
12022
12130
|
const n = countFeedback(r.stdout, author);
|
|
12023
12131
|
total.all += n.all;
|
|
12024
12132
|
total.author += n.author;
|
|
12133
|
+
bots.push(...parseBotFindings(r.stdout));
|
|
12025
12134
|
} catch {
|
|
12026
12135
|
return null;
|
|
12027
12136
|
}
|
|
12028
12137
|
}
|
|
12029
|
-
|
|
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;
|
|
12030
12165
|
}
|
|
12031
12166
|
var MERGE_QUEUE_QUERY = "query($url: URI!) { resource(url: $url) { ... on PullRequest { mergeQueueEntry { state } } } }";
|
|
12032
12167
|
function prInMergeQueue(exec, repoPath, url) {
|
|
@@ -12064,11 +12199,19 @@ function prView(exec, repoPath, url, last) {
|
|
|
12064
12199
|
const ready = !parsed.queued && parsed.failing.length === 0 && parsed.pending === 0 && parsed.reviewDecision === "APPROVED";
|
|
12065
12200
|
const view = ready && prInMergeQueue(exec, repoPath, url) === true ? { ...parsed, queued: true } : parsed;
|
|
12066
12201
|
let read = view.updatedAt && last?.updatedAt === view.updatedAt ? last : undefined;
|
|
12202
|
+
let findings = null;
|
|
12067
12203
|
if (!read) {
|
|
12068
12204
|
const f = prFeedback(exec, repoPath, url, view.author);
|
|
12069
|
-
read = f && {
|
|
12070
|
-
|
|
12071
|
-
|
|
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;
|
|
12072
12215
|
} catch {
|
|
12073
12216
|
return null;
|
|
12074
12217
|
}
|
|
@@ -12616,6 +12759,32 @@ function resumesOf(s) {
|
|
|
12616
12759
|
const r = s;
|
|
12617
12760
|
return r && typeof r.resumeParked === "function" ? s : null;
|
|
12618
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
|
+
}
|
|
12619
12788
|
var FAIL_LOG_MAX = 2000;
|
|
12620
12789
|
var UNREACHABLE3 = "SLEDS unreachable";
|
|
12621
12790
|
function keysIn(states, state) {
|
|
@@ -12861,6 +13030,10 @@ function nextPickup(r, o) {
|
|
|
12861
13030
|
continue;
|
|
12862
13031
|
if (isPrMirror(row.source))
|
|
12863
13032
|
continue;
|
|
13033
|
+
if (route.packable === false)
|
|
13034
|
+
continue;
|
|
13035
|
+
if (isObject8(route.elsewhere))
|
|
13036
|
+
continue;
|
|
12864
13037
|
const states = isObject8(route.states) ? route.states : {};
|
|
12865
13038
|
if (statesLanded(states) || keysIn(states, "running").length > 0)
|
|
12866
13039
|
continue;
|
|
@@ -12890,6 +13063,8 @@ function armedDrafts(r, engine) {
|
|
|
12890
13063
|
return false;
|
|
12891
13064
|
if (route.drafting === true || route.proposed === true || route.draftParked)
|
|
12892
13065
|
return false;
|
|
13066
|
+
if (isObject8(route.elsewhere))
|
|
13067
|
+
return false;
|
|
12893
13068
|
if (strings(route.needsRoute).length === 0 || isPrMirror(row.source))
|
|
12894
13069
|
return false;
|
|
12895
13070
|
const states = isObject8(route.states) ? route.states : {};
|
|
@@ -13094,7 +13269,8 @@ function sledsSource(client, o) {
|
|
|
13094
13269
|
...str6(hit.answer) ? { answer: str6(hit.answer) } : {},
|
|
13095
13270
|
...runToken ? { runToken } : {},
|
|
13096
13271
|
...group ? { group } : {},
|
|
13097
|
-
...run ? { run } : {}
|
|
13272
|
+
...run ? { run } : {},
|
|
13273
|
+
...str6(hit.ticket) ? { ticket: str6(hit.ticket) } : {}
|
|
13098
13274
|
};
|
|
13099
13275
|
return { ok: true, claim };
|
|
13100
13276
|
},
|
|
@@ -13117,7 +13293,7 @@ function sledsSource(client, o) {
|
|
|
13117
13293
|
return "running";
|
|
13118
13294
|
if (claim.kind === "draft") {
|
|
13119
13295
|
const filed = Array.isArray(route.nodes) && route.nodes.length > 0;
|
|
13120
|
-
if (filed || isObject8(route.proposed))
|
|
13296
|
+
if (filed || isObject8(route.proposed) || isObject8(route.elsewhere))
|
|
13121
13297
|
return "done";
|
|
13122
13298
|
if (isObject8(route.draftParked))
|
|
13123
13299
|
return "parked";
|
|
@@ -13317,6 +13493,23 @@ function sledsSource(client, o) {
|
|
|
13317
13493
|
return false;
|
|
13318
13494
|
}
|
|
13319
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
|
+
},
|
|
13320
13513
|
async answerDecision(a, engine, answer) {
|
|
13321
13514
|
const r = await client.postFull("/proxy/run", { runId: a.runId, engine, answer });
|
|
13322
13515
|
if (!r)
|
|
@@ -14284,8 +14477,8 @@ async function tickOnce(d) {
|
|
|
14284
14477
|
await report({ kind: "release", why: repoPath.error });
|
|
14285
14478
|
continue;
|
|
14286
14479
|
}
|
|
14287
|
-
const branch = d.branchOf ? d.branchOf(claim) : nodeBranch(claim.item, claim.node);
|
|
14288
|
-
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);
|
|
14289
14482
|
const found = d.prForBranch(d.exec, repoPath, branch);
|
|
14290
14483
|
if (isGhError(found))
|
|
14291
14484
|
d.log(`${at}: could not ask GitHub for ${branch}'s PR: ${found.error}`);
|
|
@@ -14295,6 +14488,12 @@ async function tickOnce(d) {
|
|
|
14295
14488
|
await report({ kind: existing.state === "MERGED" ? "merged" : "pr", ref: existing.url });
|
|
14296
14489
|
continue;
|
|
14297
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
|
+
}
|
|
14298
14497
|
let start;
|
|
14299
14498
|
let mcpConfig;
|
|
14300
14499
|
try {
|
|
@@ -14380,6 +14579,7 @@ async function tickOnce(d) {
|
|
|
14380
14579
|
...mcpConfig ? { mcpConfig, ...claim.runToken ? { onRunToken: true } : {} } : {},
|
|
14381
14580
|
...claim.group ? { group: claim.group } : {},
|
|
14382
14581
|
...packBandOf(claim.pack) ? { band: packBandOf(claim.pack) } : {},
|
|
14582
|
+
...claim.ticket ? { ticket: claim.ticket } : {},
|
|
14383
14583
|
branch,
|
|
14384
14584
|
repoPath,
|
|
14385
14585
|
handle,
|
|
@@ -14505,7 +14705,7 @@ async function settlePermission(d, runner, run, shown) {
|
|
|
14505
14705
|
if (!perms || !run.runId)
|
|
14506
14706
|
return false;
|
|
14507
14707
|
const at = ref(run);
|
|
14508
|
-
const markFile = d.permissionMark?.(sessionName(run.item, run.node));
|
|
14708
|
+
const markFile = d.permissionMark?.(sessionName(run.item, run.node, run.ticket));
|
|
14509
14709
|
const dialog = runner.readsSession ? permissionDialog(shown) : null;
|
|
14510
14710
|
const p = run.permission;
|
|
14511
14711
|
if (p) {
|
|
@@ -15354,6 +15554,25 @@ var safeWatch = (runner, h) => {
|
|
|
15354
15554
|
return h.id;
|
|
15355
15555
|
}
|
|
15356
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
|
+
}
|
|
15357
15576
|
async function startDraft(d, claim, why, state, started, finished) {
|
|
15358
15577
|
const at = ref(claim);
|
|
15359
15578
|
const report = async (outcome) => {
|
|
@@ -15370,6 +15589,12 @@ async function startDraft(d, claim, why, state, started, finished) {
|
|
|
15370
15589
|
await report({ kind: "park", question: repoPath.error });
|
|
15371
15590
|
return;
|
|
15372
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
|
+
}
|
|
15373
15598
|
const name = sessionName(claim.item, DRAFT_NODE);
|
|
15374
15599
|
const branch = nodeBranch(claim.item, DRAFT_NODE);
|
|
15375
15600
|
let handle;
|
|
@@ -15535,6 +15760,30 @@ function ciNudge(url, failing) {
|
|
|
15535
15760
|
var SELF_NUDGES = 2;
|
|
15536
15761
|
var FEEDBACK_REREAD_MS = 10 * 60000;
|
|
15537
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
|
+
}
|
|
15538
15787
|
function reviewNudge(url, count) {
|
|
15539
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.`;
|
|
15540
15789
|
}
|
|
@@ -15652,6 +15901,7 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
|
|
|
15652
15901
|
pr.selfNudges = selfNudges || undefined;
|
|
15653
15902
|
}
|
|
15654
15903
|
}
|
|
15904
|
+
await carryInstructions(d, run, runner, view, attention);
|
|
15655
15905
|
pr.failing = view.failing;
|
|
15656
15906
|
pr.feedback = Math.max(pr.feedback, view.feedback);
|
|
15657
15907
|
pr.authorFeedback = Math.max(pr.authorFeedback ?? 0, view.authorFeedback) || undefined;
|
|
@@ -16173,22 +16423,37 @@ async function answerBaseConflict(d, events, state, dec, attention, decided) {
|
|
|
16173
16423
|
return false;
|
|
16174
16424
|
}
|
|
16175
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
|
+
}
|
|
16176
16433
|
const runner = runnerFor(d, run.handle);
|
|
16177
|
-
if (
|
|
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) {
|
|
16440
|
+
const runner = runnerFor(d, run.handle);
|
|
16441
|
+
if (!runner || run.handle.cwd === run.repoPath)
|
|
16442
|
+
return false;
|
|
16443
|
+
if (runner.makesWorktree && !runner.reopen)
|
|
16178
16444
|
return false;
|
|
16179
|
-
const name = sessionName(run.item, `${run.node}-resolve`);
|
|
16180
16445
|
try {
|
|
16181
16446
|
runner.stop(run.handle);
|
|
16182
16447
|
} catch {}
|
|
16183
16448
|
try {
|
|
16184
16449
|
const launch = nodeLaunchFor(d, ref(run));
|
|
16185
|
-
|
|
16450
|
+
const o = {
|
|
16186
16451
|
name,
|
|
16187
16452
|
repoPath: run.repoPath,
|
|
16188
16453
|
worktree: run.handle.cwd,
|
|
16189
16454
|
branch: run.branch,
|
|
16190
|
-
base:
|
|
16191
|
-
promptFile: d.writePrompt(name,
|
|
16455
|
+
...base ? { base } : {},
|
|
16456
|
+
promptFile: d.writePrompt(name, prompt),
|
|
16192
16457
|
permissionMode: d.permissionMode,
|
|
16193
16458
|
...launchOptions(launch),
|
|
16194
16459
|
...run.mcpConfig ? { mcpConfig: run.mcpConfig, ...d.strictMcp ? { strictMcp: true } : {} } : {},
|
|
@@ -16196,16 +16461,75 @@ function startResolver(d, run, e, answer) {
|
|
|
16196
16461
|
...hooksFor(d, name),
|
|
16197
16462
|
...mainCheckoutFor(d, run.handle.cwd),
|
|
16198
16463
|
...run.group ? { group: run.group } : {}
|
|
16199
|
-
}
|
|
16464
|
+
};
|
|
16465
|
+
run.handle = runner.makesWorktree ? runner.reopen(run.handle, o) : runner.start(o);
|
|
16200
16466
|
} catch (err) {
|
|
16201
|
-
|
|
16202
|
-
return false;
|
|
16467
|
+
return { error: message(err) };
|
|
16203
16468
|
}
|
|
16204
|
-
if (run.pr)
|
|
16205
|
-
run.pr.resolving = e.baseSha;
|
|
16206
|
-
d.log(`${ref(run)}: a resolver session has the conflict brief — ${safeWatch(runner, run.handle)}`);
|
|
16207
16469
|
return true;
|
|
16208
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
|
+
}
|
|
16209
16533
|
async function startEventAnswerer(d, answers, state, dec, decided) {
|
|
16210
16534
|
const at = ref(dec);
|
|
16211
16535
|
const kind = dec.event.kind;
|
|
@@ -17355,7 +17679,7 @@ import {
|
|
|
17355
17679
|
} from "node:fs";
|
|
17356
17680
|
|
|
17357
17681
|
// ../../packages/shared/src/updates/releaseKey.ts
|
|
17358
|
-
var RELEASE_PUBLIC_KEY = "";
|
|
17682
|
+
var RELEASE_PUBLIC_KEY = "MCowBQYDK2VwAyEADIZiQghghQlN6WoLDF9xLmqMeFSQL4crcCBT4wINNCc=";
|
|
17359
17683
|
|
|
17360
17684
|
// ../../packages/shared/src/updates/verify.ts
|
|
17361
17685
|
import { createPrivateKey, createPublicKey as createPublicKey2, sign, verify } from "node:crypto";
|
|
@@ -17586,7 +17910,7 @@ async function fetchVersion(o) {
|
|
|
17586
17910
|
const platform = o.platform ?? process.platform;
|
|
17587
17911
|
const arch = o.arch ?? process.arch;
|
|
17588
17912
|
const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
|
|
17589
|
-
const running = o.running ?? "0.1.
|
|
17913
|
+
const running = o.running ?? "0.1.59";
|
|
17590
17914
|
if (!publicKey)
|
|
17591
17915
|
return { ok: false, why: "no release key yet" };
|
|
17592
17916
|
if (!VERSION_RE2.test(o.version))
|
|
@@ -18865,7 +19189,7 @@ function templateProblem(flag, template, jira = false) {
|
|
|
18865
19189
|
return `--${flag} has ${unknown.map((f) => `{${f}}`).join(", ")}, which the engine does not fill — it knows ${known.map((f) => `{${f}}`).join(" ")}`;
|
|
18866
19190
|
const perNode = jira ? ["node", "key", "ticket", "slug"] : ["node", "slug"];
|
|
18867
19191
|
if (flag === "branch" && !used.some((f) => perNode.includes(f)))
|
|
18868
|
-
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
|
|
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";
|
|
18869
19193
|
return null;
|
|
18870
19194
|
}
|
|
18871
19195
|
function defaultEngineName(host) {
|
|
@@ -19171,7 +19495,7 @@ function engineClient(o, org, breaker) {
|
|
|
19171
19495
|
...src,
|
|
19172
19496
|
...breaker ? { fetchFn: breaker.fetch } : {},
|
|
19173
19497
|
timeoutMs: ENGINE_TIMEOUT_MS,
|
|
19174
|
-
version: "0.1.
|
|
19498
|
+
version: "0.1.59"
|
|
19175
19499
|
});
|
|
19176
19500
|
const asked = org?.trim() || env.SLEDS_ORG?.trim();
|
|
19177
19501
|
if (asked && !selectOrg(readConfig(o.home), org, env).seat)
|
|
@@ -19348,6 +19672,7 @@ function engineDeps(o, flags, source, runner, stateName) {
|
|
|
19348
19672
|
defaultBranch,
|
|
19349
19673
|
runnerOf: (name) => name === runner.name ? runner : restoreRunner(name, exec),
|
|
19350
19674
|
prForBranch,
|
|
19675
|
+
otherWork,
|
|
19351
19676
|
prView,
|
|
19352
19677
|
mergeTree,
|
|
19353
19678
|
prFiles,
|
|
@@ -19462,7 +19787,7 @@ async function driveEngine(o, flags, deps, until = {}) {
|
|
|
19462
19787
|
runner: deps.runner.name,
|
|
19463
19788
|
atOnce: flags.atOnce,
|
|
19464
19789
|
interval: flags.interval,
|
|
19465
|
-
cliVersion: "0.1.
|
|
19790
|
+
cliVersion: "0.1.59"
|
|
19466
19791
|
};
|
|
19467
19792
|
const beat = (checkedAt) => {
|
|
19468
19793
|
const m = deps.machine;
|
|
@@ -20971,7 +21296,7 @@ function templateProblem2(flag, template, jira = false) {
|
|
|
20971
21296
|
return `--${flag} has ${unknown.map((f) => `{${f}}`).join(", ")}, which the engine does not fill — it knows ${known.map((f) => `{${f}}`).join(" ")}`;
|
|
20972
21297
|
const perNode = jira ? ["node", "key", "ticket", "slug"] : ["node", "slug"];
|
|
20973
21298
|
if (flag === "branch" && !used.some((f) => perNode.includes(f)))
|
|
20974
|
-
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
|
|
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";
|
|
20975
21300
|
return null;
|
|
20976
21301
|
}
|
|
20977
21302
|
function defaultEngineName2(host) {
|
|
@@ -21329,7 +21654,7 @@ function engineClient2(o, org, breaker) {
|
|
|
21329
21654
|
...src,
|
|
21330
21655
|
...breaker ? { fetchFn: breaker.fetch } : {},
|
|
21331
21656
|
timeoutMs: ENGINE_TIMEOUT_MS2,
|
|
21332
|
-
version: "0.1.
|
|
21657
|
+
version: "0.1.59"
|
|
21333
21658
|
});
|
|
21334
21659
|
const asked = org?.trim() || env.SLEDS_ORG?.trim();
|
|
21335
21660
|
if (asked && !selectOrg(readConfig(o.home), org, env).seat)
|
|
@@ -21528,6 +21853,7 @@ function engineDeps2(o, flags, source, runner, stateName) {
|
|
|
21528
21853
|
defaultBranch,
|
|
21529
21854
|
runnerOf: (name) => name === runner.name ? runner : restoreRunner(name, exec),
|
|
21530
21855
|
prForBranch,
|
|
21856
|
+
otherWork,
|
|
21531
21857
|
prView,
|
|
21532
21858
|
mergeTree,
|
|
21533
21859
|
prFiles,
|
|
@@ -21655,7 +21981,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
|
|
|
21655
21981
|
runner: deps.runner.name,
|
|
21656
21982
|
atOnce: flags.atOnce,
|
|
21657
21983
|
interval: flags.interval,
|
|
21658
|
-
cliVersion: "0.1.
|
|
21984
|
+
cliVersion: "0.1.59"
|
|
21659
21985
|
};
|
|
21660
21986
|
const beat = (checkedAt) => {
|
|
21661
21987
|
const m = deps.machine;
|
|
@@ -23758,7 +24084,7 @@ function contextFor(deps) {
|
|
|
23758
24084
|
...src,
|
|
23759
24085
|
fetchFn: deps.fetchFn,
|
|
23760
24086
|
timeoutMs: deps.timeoutMs,
|
|
23761
|
-
version: "0.1.
|
|
24087
|
+
version: "0.1.59"
|
|
23762
24088
|
}), deps.home, now) : null;
|
|
23763
24089
|
return {
|
|
23764
24090
|
channel: "code",
|
|
@@ -23872,6 +24198,8 @@ When the person asks what is on their sled, what they hold, or what to pick up n
|
|
|
23872
24198
|
|
|
23873
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\`).
|
|
23874
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
|
+
|
|
23875
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.
|
|
23876
24204
|
|
|
23877
24205
|
## Finish — leave what you learned
|
|
@@ -25553,12 +25881,13 @@ init_src();
|
|
|
25553
25881
|
import { readFileSync as readFileSync29 } from "node:fs";
|
|
25554
25882
|
import { hostname as hostname12 } from "node:os";
|
|
25555
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>]";
|
|
25556
|
-
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]";
|
|
25557
25885
|
var ANSWER_USAGE = "usage: sleds route answer <key>/<node> <answer…> | sleds route answer <key>/<node> --stdin [--json]";
|
|
25558
25886
|
var PROPOSE_USAGE = "usage: sleds route propose <key> (--file <nodes.json> | --stdin) [--name <engine>] [--json]";
|
|
25559
25887
|
var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--json]";
|
|
25560
25888
|
var REDRAFT_USAGE = "usage: sleds route redraft <key> [--json]";
|
|
25561
25889
|
var SINGLE_USAGE = "usage: sleds route single <key> [--json]";
|
|
25890
|
+
var ELSEWHERE_USAGE = "usage: sleds route elsewhere <key> [--note <text>] [--json]";
|
|
25562
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]";
|
|
25563
25892
|
var ROUTE_VERDICTS = ["accept", "keep", "redraft"];
|
|
25564
25893
|
function nodesFromJson(text) {
|
|
@@ -25611,6 +25940,8 @@ function parseVerbArgs(argv, allowed) {
|
|
|
25611
25940
|
out.file = value;
|
|
25612
25941
|
if (flag === "name")
|
|
25613
25942
|
out.name = value;
|
|
25943
|
+
if (flag === "note")
|
|
25944
|
+
out.note = value;
|
|
25614
25945
|
i++;
|
|
25615
25946
|
} else if (a.startsWith("-"))
|
|
25616
25947
|
return { error: `unknown flag ${a}` };
|
|
@@ -25843,10 +26174,13 @@ async function redraftCmd(o, argv) {
|
|
|
25843
26174
|
async function singleCmd(o, argv) {
|
|
25844
26175
|
return oneVerb(o, argv, SINGLE_USAGE, { single: true }, "nothing changed", (key) => singleLine(key));
|
|
25845
26176
|
}
|
|
25846
|
-
async function
|
|
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 = []) {
|
|
25847
26181
|
const log = o.log ?? console.log;
|
|
25848
26182
|
const error = o.error ?? console.error;
|
|
25849
|
-
const a = parseVerbArgs(argv, ["json"]);
|
|
26183
|
+
const a = parseVerbArgs(argv, ["json", ...flags]);
|
|
25850
26184
|
if ("error" in a) {
|
|
25851
26185
|
error(a.error);
|
|
25852
26186
|
error(usage);
|
|
@@ -25857,7 +26191,10 @@ async function oneVerb(o, argv, usage, body, unreachable, line) {
|
|
|
25857
26191
|
error(client.error);
|
|
25858
26192
|
return 1;
|
|
25859
26193
|
}
|
|
25860
|
-
const r = await client.postFull("/proxy/route", {
|
|
26194
|
+
const r = await client.postFull("/proxy/route", {
|
|
26195
|
+
project: a.key,
|
|
26196
|
+
...typeof body === "function" ? body(a) : body
|
|
26197
|
+
});
|
|
25861
26198
|
if (!r) {
|
|
25862
26199
|
error(`${a.key}: SLEDS unreachable — ${unreachable}`);
|
|
25863
26200
|
return 1;
|
|
@@ -25871,6 +26208,9 @@ function redraftLine(key) {
|
|
|
25871
26208
|
function singleLine(key) {
|
|
25872
26209
|
return `${key} runs as one node — it is not planned again`;
|
|
25873
26210
|
}
|
|
26211
|
+
function elsewhereLine(key) {
|
|
26212
|
+
return `${key} marked built elsewhere — no engine runs it`;
|
|
26213
|
+
}
|
|
25874
26214
|
function answeredLine(key, route, held) {
|
|
25875
26215
|
const nodes = Array.isArray(held) && held.length ? ` (${held.join(", ")})` : "";
|
|
25876
26216
|
if (route === "accept")
|
|
@@ -26138,6 +26478,8 @@ var routeCmd = async (o) => {
|
|
|
26138
26478
|
return redraftCmd(o, o.argv.slice(1));
|
|
26139
26479
|
if (o.argv[0] === "single")
|
|
26140
26480
|
return singleCmd(o, o.argv.slice(1));
|
|
26481
|
+
if (o.argv[0] === "elsewhere")
|
|
26482
|
+
return elsewhereCmd(o, o.argv.slice(1));
|
|
26141
26483
|
if (o.argv[0] === "decision")
|
|
26142
26484
|
return decisionCmd(o, o.argv.slice(1));
|
|
26143
26485
|
if (o.argv[0] === "decisions")
|
|
@@ -27876,7 +28218,7 @@ var realPidAlive4 = (pid) => {
|
|
|
27876
28218
|
};
|
|
27877
28219
|
function machineClient(home, env) {
|
|
27878
28220
|
const src = meterSource(home, undefined, { env });
|
|
27879
|
-
return src ? fetchClient({ ...src, version: "0.1.
|
|
28221
|
+
return src ? fetchClient({ ...src, version: "0.1.59" }) : null;
|
|
27880
28222
|
}
|
|
27881
28223
|
async function noteAnswer2(home, a, now) {
|
|
27882
28224
|
try {
|
|
@@ -28526,7 +28868,10 @@ commands:
|
|
|
28526
28868
|
approve <key> a person's yes to a planned route: files it and, under
|
|
28527
28869
|
Pickup: Auto, arms it; --file <nodes.json> approves an
|
|
28528
28870
|
edited set instead (--json)
|
|
28529
|
-
|
|
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
|
|
28530
28875
|
--at-once <n> sessions at once (default 1; one per seat \u2014 tick seats
|
|
28531
28876
|
when you press Run to run more than one)
|
|
28532
28877
|
--runner tmux|headless|command|orca where each node runs (default tmux, any
|
|
@@ -28736,7 +29081,7 @@ var main = async (argv) => {
|
|
|
28736
29081
|
now: Date.now(),
|
|
28737
29082
|
stderr: (line) => console.error(line),
|
|
28738
29083
|
isTTY: !!process.stderr.isTTY,
|
|
28739
|
-
version: "0.1.
|
|
29084
|
+
version: "0.1.59"
|
|
28740
29085
|
});
|
|
28741
29086
|
switch (cmd) {
|
|
28742
29087
|
case "update":
|
|
@@ -28969,7 +29314,7 @@ ${renderHelpText(argv[1])}`);
|
|
|
28969
29314
|
case "version":
|
|
28970
29315
|
case "--version":
|
|
28971
29316
|
case "-v":
|
|
28972
|
-
console.log("sleds 0.1.
|
|
29317
|
+
console.log("sleds 0.1.59");
|
|
28973
29318
|
return 0;
|
|
28974
29319
|
default:
|
|
28975
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.
|
|
3
|
+
"version": "0.1.59",
|
|
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",
|