@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.
- package/README.md +1 -1
- package/dist/sleds.mjs +444 -67
- 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
|
|
7063
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 && {
|
|
12038
|
-
|
|
12039
|
-
|
|
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
|
|
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" &&
|
|
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 ||
|
|
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
|
-
|
|
16450
|
+
const o = {
|
|
16154
16451
|
name,
|
|
16155
16452
|
repoPath: run.repoPath,
|
|
16156
16453
|
worktree: run.handle.cwd,
|
|
16157
16454
|
branch: run.branch,
|
|
16158
|
-
base:
|
|
16159
|
-
promptFile: d.writePrompt(name,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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", {
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|