@sleds/cli 0.1.32 → 0.1.33
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/dist/sleds.mjs +378 -100
- package/package.json +1 -1
package/dist/sleds.mjs
CHANGED
|
@@ -5274,11 +5274,26 @@ var init_statusMap = __esm(() => {
|
|
|
5274
5274
|
status: false,
|
|
5275
5275
|
assignee: false,
|
|
5276
5276
|
children: false,
|
|
5277
|
-
comments: false
|
|
5277
|
+
comments: false,
|
|
5278
|
+
fields: false
|
|
5278
5279
|
};
|
|
5279
5280
|
WAY_SWITCHES = {
|
|
5280
|
-
jira: {
|
|
5281
|
-
|
|
5281
|
+
jira: {
|
|
5282
|
+
create: true,
|
|
5283
|
+
status: true,
|
|
5284
|
+
assignee: true,
|
|
5285
|
+
children: true,
|
|
5286
|
+
comments: true,
|
|
5287
|
+
fields: true
|
|
5288
|
+
},
|
|
5289
|
+
moving: {
|
|
5290
|
+
create: false,
|
|
5291
|
+
status: true,
|
|
5292
|
+
assignee: true,
|
|
5293
|
+
children: true,
|
|
5294
|
+
comments: true,
|
|
5295
|
+
fields: true
|
|
5296
|
+
},
|
|
5282
5297
|
readonly: { ...DEFAULT_WRITES }
|
|
5283
5298
|
};
|
|
5284
5299
|
});
|
|
@@ -6075,7 +6090,8 @@ function nodePrompt(o) {
|
|
|
6075
6090
|
`- Work on branch \`${o.branch}\` from the latest \`origin/${base}\` (create it if this checkout is not on it). One branch, one PR.`,
|
|
6076
6091
|
`- Open the PR against \`${base}\` with \`gh pr create\`${o.prTitle ? ` and title it exactly \`${o.prTitle}\` (the repo's CI checks it)` : ""}. Its body carries \`SLEDS-Node: ${claim.node}\` on the line under \`SLEDS-Source:\` when the pack names a source.`,
|
|
6077
6092
|
"- Run the repo's own gate before you push. Never merge: a person (or the musher, when the attachment says so) does.",
|
|
6078
|
-
"- Stay inside this node's scope. Work another node needs goes in the PR description, not in this branch."
|
|
6093
|
+
"- Stay inside this node's scope. Work another node needs goes in the PR description, not in this branch.",
|
|
6094
|
+
`- End every comment and reply you post on the PR with the line \`${ENGINE_MARK}\`, so the engine knows it is yours and not a reviewer's.`
|
|
6079
6095
|
];
|
|
6080
6096
|
if (o.sledsTools && claim.runId)
|
|
6081
6097
|
lines.push(`- If only a person can decide something, first search the record (the pack, the decision log in it, the specs and docs it names); if it is still open, call the SLEDS \`${toolName("run", o.engineMcp)}\` tool with \`{runId: "${claim.runId}", park: {question, options?, irreversible?}}\` — say in the question what you already read — and wait; do not guess. An answerer takes it first, then a person. Mark \`irreversible: true\` when acting on either answer could not be undone by reverting the PR (data deleted or migrated in place, a deploy, anything that leaves the org, spend, access, the brief). The engine leaves this session open for the answer.`, `- Before you finish, \`${toolName("land", o.engineMcp)}\` with \`{project: "${claim.item}", node: "${claim.node}", state: "pr", ref: <the PR url>, notes}\` — notes are what the next node should know${deviations}.`);
|
|
@@ -6332,9 +6348,15 @@ function eventPrompt(o) {
|
|
|
6332
6348
|
return conflictPrompt({ ...o, event });
|
|
6333
6349
|
return ciTwicePrompt({ ...o, event });
|
|
6334
6350
|
}
|
|
6335
|
-
var DEFAULT_SEAT = "\x00default", MAX_SEAT_AT_ONCE = 16, DRAFT_NODE = "draft", BRANCH_FIELDS, PR_TITLE_FIELDS, ENGINE_MCP_SERVER = "sleds", RUN_TOKEN_ENV = "SLEDS_RUN_TOKEN", NO_RUN_TOKEN = "no run token from SLEDS: this session acts as the seat", toolName = (name, engineMcp) => engineMcp ? `mcp__${ENGINE_MCP_SERVER}__${name}` : name, APPROACH_SHAPE, AUTONOMY_SAYS, IRREVERSIBLE = "deleting or migrating data in place; deploying outside the playbook's window; anything that leaves the org (a message to a customer, a publish, a release, an email); spend past the band or the allowance; force-pushing or deleting a branch someone else holds; granting or removing access; changing the brief. When in doubt, it is irreversible.", oneLine = (s, max) => s.replace(/\s+/g, " ").trim().slice(0, max), VERDICT_SHAPE, codes = (xs) => xs.map((x) => `\`${x}\``).join(", "), RESOLVE_MAX_FILES = 8;
|
|
6351
|
+
var DEFAULT_SEAT = "\x00default", MAX_SEAT_AT_ONCE = 16, HOLD_DEFAULTS, DRAFT_NODE = "draft", BRANCH_FIELDS, PR_TITLE_FIELDS, ENGINE_MCP_SERVER = "sleds", RUN_TOKEN_ENV = "SLEDS_RUN_TOKEN", NO_RUN_TOKEN = "no run token from SLEDS: this session acts as the seat", toolName = (name, engineMcp) => engineMcp ? `mcp__${ENGINE_MCP_SERVER}__${name}` : name, ENGINE_MARK = "<!-- sleds:engine -->", APPROACH_SHAPE, AUTONOMY_SAYS, IRREVERSIBLE = "deleting or migrating data in place; deploying outside the playbook's window; anything that leaves the org (a message to a customer, a publish, a release, an email); spend past the band or the allowance; force-pushing or deleting a branch someone else holds; granting or removing access; changing the brief. When in doubt, it is irreversible.", oneLine = (s, max) => s.replace(/\s+/g, " ").trim().slice(0, max), VERDICT_SHAPE, codes = (xs) => xs.map((x) => `\`${x}\``).join(", "), RESOLVE_MAX_FILES = 8;
|
|
6336
6352
|
var init_engine = __esm(() => {
|
|
6337
6353
|
init_route();
|
|
6354
|
+
HOLD_DEFAULTS = {
|
|
6355
|
+
holdCpu: 85,
|
|
6356
|
+
holdLoad: 1.5,
|
|
6357
|
+
holdPressure: "warn",
|
|
6358
|
+
holdUsage: 80
|
|
6359
|
+
};
|
|
6338
6360
|
BRANCH_FIELDS = ["key", "item", "node", "slug", "ticket"];
|
|
6339
6361
|
PR_TITLE_FIELDS = ["key", "item", "node", "title", "ticket"];
|
|
6340
6362
|
APPROACH_SHAPE = [
|
|
@@ -7369,9 +7391,12 @@ function parseRun(v) {
|
|
|
7369
7391
|
url: v.pr.url,
|
|
7370
7392
|
failing: Array.isArray(v.pr.failing) ? v.pr.failing.filter((x) => typeof x === "string") : [],
|
|
7371
7393
|
feedback: typeof v.pr.feedback === "number" ? v.pr.feedback : 0,
|
|
7394
|
+
...typeof v.pr.authorFeedback === "number" ? { authorFeedback: v.pr.authorFeedback } : {},
|
|
7395
|
+
...typeof v.pr.selfNudges === "number" ? { selfNudges: v.pr.selfNudges } : {},
|
|
7372
7396
|
...v.pr.clear === true ? { clear: true } : {},
|
|
7373
7397
|
...typeof v.pr.ci === "string" ? { ci: v.pr.ci } : {},
|
|
7374
7398
|
...typeof v.pr.review === "string" ? { review: v.pr.review } : {},
|
|
7399
|
+
...typeof v.pr.posted === "string" ? { posted: v.pr.posted } : {},
|
|
7375
7400
|
...Array.isArray(v.pr.failedHeads) ? { failedHeads: v.pr.failedHeads.filter((x) => typeof x === "string") } : {},
|
|
7376
7401
|
...typeof v.pr.conflict === "string" ? { conflict: v.pr.conflict } : {},
|
|
7377
7402
|
...typeof v.pr.resolving === "string" ? { resolving: v.pr.resolving } : {},
|
|
@@ -7387,10 +7412,12 @@ function parseState(text) {
|
|
|
7387
7412
|
return { runs: [] };
|
|
7388
7413
|
const decisions = Array.isArray(raw.decisions) ? raw.decisions.map(parseDecision).filter((x) => x !== null) : [];
|
|
7389
7414
|
const merged = Array.isArray(raw.merged) ? raw.merged.map(parseMerged).filter((x) => x !== null) : [];
|
|
7415
|
+
const slowStarts = isObject8(raw.slowStarts) ? Object.fromEntries(Object.entries(raw.slowStarts).filter((e) => typeof e[1] === "number" && e[1] > 0)) : {};
|
|
7390
7416
|
return {
|
|
7391
7417
|
runs: raw.runs.map(parseRun).filter((r) => r !== null),
|
|
7392
7418
|
...decisions.length ? { decisions } : {},
|
|
7393
|
-
...merged.length ? { merged } : {}
|
|
7419
|
+
...merged.length ? { merged } : {},
|
|
7420
|
+
...Object.keys(slowStarts).length ? { slowStarts } : {}
|
|
7394
7421
|
};
|
|
7395
7422
|
} catch {
|
|
7396
7423
|
return { runs: [] };
|
|
@@ -9258,6 +9285,8 @@ var defaultExec = (cmd, args, o) => {
|
|
|
9258
9285
|
}
|
|
9259
9286
|
return { status: r.status ?? 1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
9260
9287
|
};
|
|
9288
|
+
var isTimeout = (msg) => /\btime[ds]?[\s_-]?out\b|ETIMEDOUT/i.test(msg);
|
|
9289
|
+
var UNWATCHED = "the agent may be running in it unwatched";
|
|
9261
9290
|
var shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
9262
9291
|
var agentShellCommand = (o) => {
|
|
9263
9292
|
const prompt = `"$(cat ${shq(o.promptFile)})"`;
|
|
@@ -9350,6 +9379,18 @@ var cmuxIdForRef = (stdout, ref, name) => {
|
|
|
9350
9379
|
}
|
|
9351
9380
|
return null;
|
|
9352
9381
|
};
|
|
9382
|
+
var cmuxIdForName = (stdout, name) => {
|
|
9383
|
+
let found = null;
|
|
9384
|
+
for (const words of namedLines(stdout, name)) {
|
|
9385
|
+
const id = words[1]?.match(UUID)?.[0];
|
|
9386
|
+
if (!id || found)
|
|
9387
|
+
return null;
|
|
9388
|
+
found = id.toLowerCase();
|
|
9389
|
+
}
|
|
9390
|
+
return found;
|
|
9391
|
+
};
|
|
9392
|
+
var namedLines = (stdout, name) => stdout.split(`
|
|
9393
|
+
`).map((line) => line.trim().replace(/^\*\s*/, "").split(/\s+/)).filter((words) => words.slice(2).includes(name));
|
|
9353
9394
|
var cmuxWorkspaceListed = (status, stdout, id) => status !== 0 || stdout.toLowerCase().includes(id.toLowerCase());
|
|
9354
9395
|
var cmuxGroupName = (g) => g.title.trim() ? `${g.key} · ${g.title.trim()}` : g.key;
|
|
9355
9396
|
var str7 = (v) => typeof v === "string" && v ? v : undefined;
|
|
@@ -9410,6 +9451,28 @@ var cmuxProbeReason = (r) => {
|
|
|
9410
9451
|
const why = line.replace(/^(cmux:\s*)?(error:\s*)?/i, "").trim();
|
|
9411
9452
|
return `cmux: ${why || `exit ${r.status}`}`;
|
|
9412
9453
|
};
|
|
9454
|
+
var cmuxAuthRefused = (r) => r.status !== 0 && /\b(auth_required|unauthorized|authentication required|invalid password)\b/i.test(`${r.stderr}
|
|
9455
|
+
${r.stdout}`);
|
|
9456
|
+
var CMUX_SETTLE_MS = 300;
|
|
9457
|
+
var CMUX_ENTER_RETRIES = 2;
|
|
9458
|
+
var PROMPT_LINE = /^[\s│|]*[>❯›]\s?(.*)$/;
|
|
9459
|
+
var PASTED = /\[Pasted text\b/i;
|
|
9460
|
+
var cmuxInputPending = (screen, text) => {
|
|
9461
|
+
const squash = (s) => s.replace(/\s+/g, " ").trim();
|
|
9462
|
+
const head = squash(text.split(`
|
|
9463
|
+
`).find((l) => l.trim()) ?? "").slice(0, 24);
|
|
9464
|
+
let input = null;
|
|
9465
|
+
for (const line of screen.split(`
|
|
9466
|
+
`)) {
|
|
9467
|
+
const m = line.match(PROMPT_LINE);
|
|
9468
|
+
if (m)
|
|
9469
|
+
input = squash((m[1] ?? "").replace(/│\s*$/, ""));
|
|
9470
|
+
}
|
|
9471
|
+
if (!input || !head)
|
|
9472
|
+
return false;
|
|
9473
|
+
return PASTED.test(input) || input.startsWith(head) || input.length >= 8 && head.startsWith(input);
|
|
9474
|
+
};
|
|
9475
|
+
var sleepSync = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
9413
9476
|
var CMUX_LANE = {
|
|
9414
9477
|
working: "working",
|
|
9415
9478
|
parked: "needs-attention",
|
|
@@ -9419,18 +9482,26 @@ var CMUX_LANE = {
|
|
|
9419
9482
|
var launcherPath = (promptFile) => logPathFor(promptFile, "cmux.sh");
|
|
9420
9483
|
var exitPath = (promptFile) => logPathFor(promptFile, "cmux.exit");
|
|
9421
9484
|
function cmuxRunner(exec, deps = {}) {
|
|
9485
|
+
const sleep = deps.sleep ?? sleepSync;
|
|
9422
9486
|
const writeFile = deps.writeFile ?? ((p, t) => writeFileSync10(p, t, { mode: 384 }));
|
|
9423
9487
|
const exists = deps.exists ?? existsSync11;
|
|
9424
9488
|
const remove = deps.remove ?? ((p) => rmSync2(p, { force: true }));
|
|
9425
9489
|
const bin = resolveCmuxBin(exists);
|
|
9426
9490
|
const password = deps.password ?? (() => readCmuxPassword(exec));
|
|
9427
|
-
let
|
|
9491
|
+
let pw;
|
|
9492
|
+
const run = (args) => pw ? exec(bin, args, { env: { CMUX_SOCKET_PASSWORD: pw } }) : exec(bin, args);
|
|
9428
9493
|
const cmux = (args) => {
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9494
|
+
const fresh = pw === undefined;
|
|
9495
|
+
if (fresh)
|
|
9496
|
+
pw = password();
|
|
9497
|
+
const r = run(args);
|
|
9498
|
+
if (fresh || !cmuxAuthRefused(r))
|
|
9499
|
+
return r;
|
|
9500
|
+
const now = password();
|
|
9501
|
+
if (now === pw)
|
|
9502
|
+
return r;
|
|
9503
|
+
pw = now;
|
|
9504
|
+
return pw ? run(args) : r;
|
|
9434
9505
|
};
|
|
9435
9506
|
const failed = (r) => r.stderr.trim() || r.stdout.trim() || `exit ${r.status}`;
|
|
9436
9507
|
return {
|
|
@@ -9467,19 +9538,27 @@ function cmuxRunner(exec, deps = {}) {
|
|
|
9467
9538
|
joined = undefined;
|
|
9468
9539
|
r = cmux(args);
|
|
9469
9540
|
}
|
|
9470
|
-
if (r.status !== 0)
|
|
9471
|
-
|
|
9541
|
+
if (r.status !== 0) {
|
|
9542
|
+
const why = failed(r);
|
|
9543
|
+
const l = cmux(["--id-format", "both", "list-workspaces"]);
|
|
9544
|
+
const stray = l.status === 0 ? cmuxIdForName(l.stdout, o.name) : null;
|
|
9545
|
+
const left = l.status !== 0 || (stray ? cmux(["close-workspace", "--workspace", stray]).status !== 0 : namedLines(l.stdout, o.name).length > 0);
|
|
9546
|
+
throw new Error(`cmux new-workspace ${o.name} failed: ${why}${left ? `; ${UNWATCHED}` : ""}`);
|
|
9547
|
+
}
|
|
9472
9548
|
let id = parseCmuxWorkspace(r.stdout);
|
|
9473
9549
|
const ref = id ? null : parseCmuxRef(r.stdout);
|
|
9550
|
+
let closed = false;
|
|
9474
9551
|
if (ref) {
|
|
9475
9552
|
const l = cmux(["--id-format", "both", "list-workspaces"]);
|
|
9476
|
-
if (l.status === 0)
|
|
9553
|
+
if (l.status === 0) {
|
|
9477
9554
|
id = cmuxIdForRef(l.stdout, ref, o.name);
|
|
9478
|
-
|
|
9479
|
-
|
|
9555
|
+
const stray = id ? null : cmuxIdForName(l.stdout, o.name);
|
|
9556
|
+
if (stray)
|
|
9557
|
+
closed = cmux(["close-workspace", "--workspace", stray]).status === 0;
|
|
9558
|
+
}
|
|
9480
9559
|
}
|
|
9481
9560
|
if (!id) {
|
|
9482
|
-
throw new Error(ref ? `cmux new-workspace ${o.name} gave ${ref} but no workspace id for it; that workspace was closed` : `cmux new-workspace ${o.name} returned no workspace (${r.stdout.trim() || "nothing"});
|
|
9561
|
+
throw new Error(ref ? `cmux new-workspace ${o.name} gave ${ref} but no workspace id for it; ${closed ? "that workspace was closed" : UNWATCHED}` : `cmux new-workspace ${o.name} returned no workspace (${r.stdout.trim() || "nothing"}); ${UNWATCHED}`);
|
|
9483
9562
|
}
|
|
9484
9563
|
if (group && listed && !joined) {
|
|
9485
9564
|
const c = cmux(["workspace-group", "create", "--name", group, "--from", id, "--json"]);
|
|
@@ -9505,9 +9584,26 @@ function cmuxRunner(exec, deps = {}) {
|
|
|
9505
9584
|
const r = cmux(["send", "--workspace", h.id, "--", text]);
|
|
9506
9585
|
if (r.status !== 0)
|
|
9507
9586
|
throw new Error(`cmux send ${h.id} failed: ${failed(r)}`);
|
|
9508
|
-
const
|
|
9509
|
-
|
|
9510
|
-
|
|
9587
|
+
const enter = () => {
|
|
9588
|
+
const k = cmux(["send-key", "--workspace", h.id, "enter"]);
|
|
9589
|
+
if (k.status !== 0)
|
|
9590
|
+
throw new Error(`cmux send-key ${h.id} failed: ${failed(k)}`);
|
|
9591
|
+
};
|
|
9592
|
+
sleep(CMUX_SETTLE_MS);
|
|
9593
|
+
enter();
|
|
9594
|
+
const pending = () => {
|
|
9595
|
+
sleep(CMUX_SETTLE_MS);
|
|
9596
|
+
const s = cmux(["read-screen", "--workspace", h.id, "--lines", "20"]);
|
|
9597
|
+
return s.status === 0 && cmuxInputPending(s.stdout, text);
|
|
9598
|
+
};
|
|
9599
|
+
let again = 0;
|
|
9600
|
+
while (again < CMUX_ENTER_RETRIES && pending()) {
|
|
9601
|
+
enter();
|
|
9602
|
+
again++;
|
|
9603
|
+
}
|
|
9604
|
+
if (again === 0)
|
|
9605
|
+
return;
|
|
9606
|
+
return pending() ? `the Enter was swallowed; pressed it ${again} more times and the text is still in the input` : `the Enter was swallowed; pressed it again${again > 1 ? ` (${again}×)` : ""}`;
|
|
9511
9607
|
},
|
|
9512
9608
|
stop(h) {
|
|
9513
9609
|
cmux(["close-workspace", "--workspace", h.id]);
|
|
@@ -9788,6 +9884,7 @@ function noteEvent(line) {
|
|
|
9788
9884
|
}
|
|
9789
9885
|
|
|
9790
9886
|
// src/engine/gh.ts
|
|
9887
|
+
init_src();
|
|
9791
9888
|
var isGhError = (r) => r !== null && ("error" in r);
|
|
9792
9889
|
function prForBranch(exec, repoPath, branch) {
|
|
9793
9890
|
try {
|
|
@@ -9884,6 +9981,7 @@ function parsePrView(stdout) {
|
|
|
9884
9981
|
pending,
|
|
9885
9982
|
reviewDecision: typeof o.reviewDecision === "string" ? o.reviewDecision : "",
|
|
9886
9983
|
feedback: 0,
|
|
9984
|
+
authorFeedback: 0,
|
|
9887
9985
|
...typeof o.headRefOid === "string" && o.headRefOid ? { head: o.headRefOid } : {},
|
|
9888
9986
|
...typeof mergeCommit === "string" && mergeCommit ? { mergeCommit } : {}
|
|
9889
9987
|
};
|
|
@@ -9892,22 +9990,39 @@ function prRef(url) {
|
|
|
9892
9990
|
const m = /github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/.exec(url);
|
|
9893
9991
|
return m?.[1] && m[2] ? { repo: m[1], number: Number(m[2]) } : null;
|
|
9894
9992
|
}
|
|
9993
|
+
function countFeedback(stdout, author) {
|
|
9994
|
+
const n = { all: 0, author: 0 };
|
|
9995
|
+
for (const line of stdout.split(`
|
|
9996
|
+
`)) {
|
|
9997
|
+
const [login = "", marked, hasBody] = line.trim().split("\t");
|
|
9998
|
+
if (!login || marked === "true")
|
|
9999
|
+
continue;
|
|
10000
|
+
if (login === author && hasBody !== "true")
|
|
10001
|
+
continue;
|
|
10002
|
+
n.all++;
|
|
10003
|
+
if (login === author)
|
|
10004
|
+
n.author++;
|
|
10005
|
+
}
|
|
10006
|
+
return n;
|
|
10007
|
+
}
|
|
10008
|
+
var FEEDBACK_JQ = `.[] | select(.user.type != "Bot") | [.user.login, ((.body // "") | contains("${ENGINE_MARK}") | tostring), ((.body // "") | length > 0 | tostring)] | @tsv`;
|
|
9895
10009
|
function prFeedback(exec, repoPath, url, author) {
|
|
9896
10010
|
const ref = prRef(url);
|
|
9897
10011
|
if (!ref)
|
|
9898
10012
|
return null;
|
|
9899
|
-
|
|
10013
|
+
const total = { all: 0, author: 0 };
|
|
9900
10014
|
for (const path of [
|
|
9901
10015
|
`repos/${ref.repo}/issues/${ref.number}/comments`,
|
|
9902
10016
|
`repos/${ref.repo}/pulls/${ref.number}/reviews`,
|
|
9903
10017
|
`repos/${ref.repo}/pulls/${ref.number}/comments`
|
|
9904
10018
|
]) {
|
|
9905
10019
|
try {
|
|
9906
|
-
const r = exec("gh", ["api", path, "--paginate", "--jq",
|
|
10020
|
+
const r = exec("gh", ["api", path, "--paginate", "--jq", FEEDBACK_JQ], { cwd: repoPath });
|
|
9907
10021
|
if (r.status !== 0)
|
|
9908
10022
|
return null;
|
|
9909
|
-
|
|
9910
|
-
|
|
10023
|
+
const n = countFeedback(r.stdout, author);
|
|
10024
|
+
total.all += n.all;
|
|
10025
|
+
total.author += n.author;
|
|
9911
10026
|
} catch {
|
|
9912
10027
|
return null;
|
|
9913
10028
|
}
|
|
@@ -9927,7 +10042,7 @@ function prView(exec, repoPath, url) {
|
|
|
9927
10042
|
if (!view || view.state !== "OPEN")
|
|
9928
10043
|
return view;
|
|
9929
10044
|
const feedback = prFeedback(exec, repoPath, url, view.author);
|
|
9930
|
-
return feedback === null ? view : { ...view, feedback };
|
|
10045
|
+
return feedback === null ? view : { ...view, feedback: feedback.all, authorFeedback: feedback.author };
|
|
9931
10046
|
} catch {
|
|
9932
10047
|
return null;
|
|
9933
10048
|
}
|
|
@@ -10400,6 +10515,17 @@ class SeatTokenError extends Error {
|
|
|
10400
10515
|
this.name = "SeatTokenError";
|
|
10401
10516
|
}
|
|
10402
10517
|
}
|
|
10518
|
+
|
|
10519
|
+
class ReportRefusedError extends Error {
|
|
10520
|
+
status;
|
|
10521
|
+
final;
|
|
10522
|
+
constructor(status, message, final = false) {
|
|
10523
|
+
super(message);
|
|
10524
|
+
this.status = status;
|
|
10525
|
+
this.final = final;
|
|
10526
|
+
this.name = "ReportRefusedError";
|
|
10527
|
+
}
|
|
10528
|
+
}
|
|
10403
10529
|
async function readOrSay(client, path, what) {
|
|
10404
10530
|
if (!client.getFull) {
|
|
10405
10531
|
const r = await client.get(path);
|
|
@@ -10475,7 +10601,11 @@ function sledItems(r, armedOnly, engine) {
|
|
|
10475
10601
|
}
|
|
10476
10602
|
return out;
|
|
10477
10603
|
}
|
|
10478
|
-
var
|
|
10604
|
+
var HOLD_DEFAULTS2 = {
|
|
10605
|
+
holdCpu: HOLD_DEFAULTS.holdCpu,
|
|
10606
|
+
holdLoad: HOLD_DEFAULTS.holdLoad,
|
|
10607
|
+
holdPressure: HOLD_DEFAULTS.holdPressure
|
|
10608
|
+
};
|
|
10479
10609
|
var PROFILE_TEXT = [
|
|
10480
10610
|
"runner",
|
|
10481
10611
|
"launcher",
|
|
@@ -10498,7 +10628,7 @@ function profileOf(r) {
|
|
|
10498
10628
|
const p = r.profile;
|
|
10499
10629
|
const out = {
|
|
10500
10630
|
pickup: p.pickup === "auto" ? "auto" : "approve",
|
|
10501
|
-
...
|
|
10631
|
+
...HOLD_DEFAULTS2,
|
|
10502
10632
|
holdOff: p.holdOff === true
|
|
10503
10633
|
};
|
|
10504
10634
|
for (const k of PROFILE_TEXT) {
|
|
@@ -10795,8 +10925,12 @@ function sledsSource(client, o) {
|
|
|
10795
10925
|
throw new Error(`${UNREACHABLE2} — ${claim.item}/${claim.node} ${outcome.kind} not recorded`);
|
|
10796
10926
|
if (r.status === 409 && path === "/proxy/run")
|
|
10797
10927
|
return;
|
|
10798
|
-
if (r.status
|
|
10799
|
-
|
|
10928
|
+
if (r.status < 300)
|
|
10929
|
+
return;
|
|
10930
|
+
if (r.status === 401 || r.status === 403 || r.status === 404 && !r.json)
|
|
10931
|
+
throw new SeatTokenError(r.status);
|
|
10932
|
+
const final = r.json !== null && (r.status === 404 || r.status === 400 && r.json.badState === true);
|
|
10933
|
+
throw new ReportRefusedError(r.status, `${claim.item}/${claim.node} ${outcome.kind}: ${serverMessage(r)}`, final);
|
|
10800
10934
|
},
|
|
10801
10935
|
async openDecision(item, node) {
|
|
10802
10936
|
const route = await routeOf(item);
|
|
@@ -10879,6 +11013,14 @@ function sledsSource(client, o) {
|
|
|
10879
11013
|
] : [];
|
|
10880
11014
|
});
|
|
10881
11015
|
},
|
|
11016
|
+
async prRead(item, node, read) {
|
|
11017
|
+
try {
|
|
11018
|
+
const r = await client.postFull("/proxy/prread", { project: item, node, ...read });
|
|
11019
|
+
return !!r && r.status >= 200 && r.status < 300;
|
|
11020
|
+
} catch {
|
|
11021
|
+
return false;
|
|
11022
|
+
}
|
|
11023
|
+
},
|
|
10882
11024
|
async answerDecision(a, engine, answer) {
|
|
10883
11025
|
const r = await client.postFull("/proxy/run", { runId: a.runId, engine, answer });
|
|
10884
11026
|
if (!r)
|
|
@@ -10997,6 +11139,13 @@ function removeBranch(exec, repoPath, branch, head) {
|
|
|
10997
11139
|
}
|
|
10998
11140
|
|
|
10999
11141
|
// src/engine/loop.ts
|
|
11142
|
+
function profileOnce(read) {
|
|
11143
|
+
let p;
|
|
11144
|
+
return () => {
|
|
11145
|
+
p ??= read ? read().catch(() => null) : Promise.resolve(null);
|
|
11146
|
+
return p;
|
|
11147
|
+
};
|
|
11148
|
+
}
|
|
11000
11149
|
function ciLine(v) {
|
|
11001
11150
|
if (v.failing.length)
|
|
11002
11151
|
return `failing: ${v.failing.join(", ")}`;
|
|
@@ -11090,6 +11239,7 @@ async function tick(deps) {
|
|
|
11090
11239
|
} finally {
|
|
11091
11240
|
if (d !== deps) {
|
|
11092
11241
|
deps.runnerHold = d.runnerHold;
|
|
11242
|
+
deps.startBackoff = d.startBackoff;
|
|
11093
11243
|
deps.atOnce = d.atOnce;
|
|
11094
11244
|
}
|
|
11095
11245
|
}
|
|
@@ -11115,6 +11265,7 @@ async function tick(deps) {
|
|
|
11115
11265
|
return report;
|
|
11116
11266
|
}
|
|
11117
11267
|
async function tickOnce(d) {
|
|
11268
|
+
d.tickProfile = profileOnce(d.readProfile);
|
|
11118
11269
|
const state = d.readState();
|
|
11119
11270
|
const seat = seatNow(d);
|
|
11120
11271
|
if (seat.changed) {
|
|
@@ -11130,6 +11281,8 @@ async function tickOnce(d) {
|
|
|
11130
11281
|
const decided = [];
|
|
11131
11282
|
const save = () => d.writeState(state);
|
|
11132
11283
|
probeRunner(d);
|
|
11284
|
+
if (d.startBackoff && --d.startBackoff.checks <= 0)
|
|
11285
|
+
d.startBackoff = undefined;
|
|
11133
11286
|
const keep = [];
|
|
11134
11287
|
for (const run of state.runs) {
|
|
11135
11288
|
const claim = claimOf(run);
|
|
@@ -11284,7 +11437,7 @@ async function tickOnce(d) {
|
|
|
11284
11437
|
const answers = answersOf(d.source);
|
|
11285
11438
|
if (answers) {
|
|
11286
11439
|
for (const run of state.runs)
|
|
11287
|
-
if (run.parked && !run.parked.answerer && !d
|
|
11440
|
+
if (run.parked && !run.parked.answerer && !startsHeld(d)) {
|
|
11288
11441
|
await startAnswerer(d, answers, run);
|
|
11289
11442
|
save();
|
|
11290
11443
|
}
|
|
@@ -11301,7 +11454,7 @@ async function tickOnce(d) {
|
|
|
11301
11454
|
const items = await d.source.read();
|
|
11302
11455
|
const takeNone = seat.gone;
|
|
11303
11456
|
for (const it of items)
|
|
11304
|
-
if (it.draft && !takeNone && !d
|
|
11457
|
+
if (it.draft && !takeNone && !startsHeld(d))
|
|
11305
11458
|
await startDraft(d, it.draft.claim, it.draft.why, state, started, finished);
|
|
11306
11459
|
const live = new Map(items.map((i) => [i.item, i]));
|
|
11307
11460
|
const unheld = state.runs.filter((r) => r.parked && (live.get(r.item)?.landed ?? true));
|
|
@@ -11319,9 +11472,16 @@ async function tickOnce(d) {
|
|
|
11319
11472
|
state.decisions = (state.decisions ?? []).filter((x) => alive(x) || x.answerer);
|
|
11320
11473
|
save();
|
|
11321
11474
|
}
|
|
11475
|
+
const counted = Object.keys(state.slowStarts ?? {});
|
|
11476
|
+
const gone = counted.filter((k) => !alive({ item: k.slice(0, k.lastIndexOf("/")) }));
|
|
11477
|
+
if (gone.length) {
|
|
11478
|
+
for (const k of gone)
|
|
11479
|
+
forgetSlowStarts(state, k);
|
|
11480
|
+
save();
|
|
11481
|
+
}
|
|
11322
11482
|
await rereadAtOnce(d);
|
|
11323
11483
|
const cap = await machineCap(d);
|
|
11324
|
-
const atOnce = d
|
|
11484
|
+
const atOnce = startsHeld(d) ? 0 : cap;
|
|
11325
11485
|
const takes = planTakes({
|
|
11326
11486
|
items,
|
|
11327
11487
|
tracked: [
|
|
@@ -11349,7 +11509,7 @@ async function tickOnce(d) {
|
|
|
11349
11509
|
return p;
|
|
11350
11510
|
};
|
|
11351
11511
|
for (const t of takeNone ? [] : takes) {
|
|
11352
|
-
if (d
|
|
11512
|
+
if (startsHeld(d))
|
|
11353
11513
|
break;
|
|
11354
11514
|
const at = ref(t);
|
|
11355
11515
|
let claim;
|
|
@@ -11455,10 +11615,26 @@ async function tickOnce(d) {
|
|
|
11455
11615
|
await report({ kind: "release", why: `runner unavailable: ${down}` });
|
|
11456
11616
|
continue;
|
|
11457
11617
|
}
|
|
11618
|
+
const slow = slowStart(d, message(e));
|
|
11619
|
+
const inRow = (state.slowStarts?.[at] ?? 0) + 1;
|
|
11620
|
+
if (slow && inRow <= SLOW_START_RELEASES) {
|
|
11621
|
+
d.log(`${at}: released — ${slow}: ${message(e)}; backing off one check`);
|
|
11622
|
+
d.startBackoff = { why: slow, checks: 2 };
|
|
11623
|
+
state.slowStarts = { ...state.slowStarts, [at]: inRow };
|
|
11624
|
+
save();
|
|
11625
|
+
await report({ kind: "release", why: slow });
|
|
11626
|
+
continue;
|
|
11627
|
+
}
|
|
11628
|
+
forgetSlowStarts(state, at);
|
|
11629
|
+
save();
|
|
11630
|
+
if (slow)
|
|
11631
|
+
d.log(`${at}: ${slow} ${inRow} times in a row — failed, not released again`);
|
|
11458
11632
|
d.log(`${at}: could not start: ${message(e)}`);
|
|
11459
11633
|
await report({ kind: "fail", log: `The engine could not start the session: ${message(e)}` });
|
|
11460
11634
|
continue;
|
|
11461
11635
|
}
|
|
11636
|
+
if (forgetSlowStarts(state, at))
|
|
11637
|
+
save();
|
|
11462
11638
|
const watch = safeWatch(d.runner, handle);
|
|
11463
11639
|
const run = {
|
|
11464
11640
|
item: claim.item,
|
|
@@ -11625,12 +11801,34 @@ function probeRunner(d) {
|
|
|
11625
11801
|
}
|
|
11626
11802
|
return why;
|
|
11627
11803
|
}
|
|
11804
|
+
function profileOf2(d) {
|
|
11805
|
+
return d.tickProfile ?? profileOnce(d.readProfile);
|
|
11806
|
+
}
|
|
11807
|
+
function forgetSlowStarts(state, at) {
|
|
11808
|
+
if (!state.slowStarts || !(at in state.slowStarts))
|
|
11809
|
+
return false;
|
|
11810
|
+
const { [at]: _, ...rest } = state.slowStarts;
|
|
11811
|
+
state.slowStarts = Object.keys(rest).length ? rest : undefined;
|
|
11812
|
+
return true;
|
|
11813
|
+
}
|
|
11814
|
+
var startsHeld = (d) => Boolean(d.runnerHold || d.startBackoff);
|
|
11815
|
+
var SLOW_START_RELEASES = 3;
|
|
11816
|
+
function slowStart(d, msg) {
|
|
11817
|
+
if (msg.includes(UNWATCHED))
|
|
11818
|
+
return null;
|
|
11819
|
+
if (isTimeout(msg))
|
|
11820
|
+
return "the runner start timed out";
|
|
11821
|
+
const hold = d.machine?.hold;
|
|
11822
|
+
if (hold)
|
|
11823
|
+
return `the runner start failed with the machine busy (${hold.signals.join(", ")})`;
|
|
11824
|
+
return null;
|
|
11825
|
+
}
|
|
11628
11826
|
async function rereadAtOnce(d) {
|
|
11629
11827
|
if (!d.rereadAtOnce)
|
|
11630
11828
|
return;
|
|
11631
11829
|
let next = null;
|
|
11632
11830
|
try {
|
|
11633
|
-
next = await d.rereadAtOnce();
|
|
11831
|
+
next = await d.rereadAtOnce(profileOf2(d));
|
|
11634
11832
|
} catch {}
|
|
11635
11833
|
if (next === null || next === d.atOnce)
|
|
11636
11834
|
return;
|
|
@@ -11642,7 +11840,7 @@ async function rereadMachine(w, d) {
|
|
|
11642
11840
|
return;
|
|
11643
11841
|
let next = null;
|
|
11644
11842
|
try {
|
|
11645
|
-
next = await w.reread();
|
|
11843
|
+
next = await w.reread(profileOf2(d));
|
|
11646
11844
|
} catch {}
|
|
11647
11845
|
if (!next)
|
|
11648
11846
|
return;
|
|
@@ -11700,7 +11898,9 @@ async function resume(d, held, claim, state, started) {
|
|
|
11700
11898
|
d.log(`${at}: answered, but the parked session is gone — starting a new one`);
|
|
11701
11899
|
return false;
|
|
11702
11900
|
}
|
|
11703
|
-
runner.send(held.handle, claim.answer);
|
|
11901
|
+
const note = runner.send(held.handle, claim.answer);
|
|
11902
|
+
if (note)
|
|
11903
|
+
d.log(`${at}: answered — ${note}`);
|
|
11704
11904
|
} catch (e) {
|
|
11705
11905
|
d.log(`${at}: answered, but the session could not be told (${message(e)}) — starting a new one`);
|
|
11706
11906
|
stopOpen(d, held, runner);
|
|
@@ -11817,7 +12017,7 @@ async function roleLaunchFor(d, at, role) {
|
|
|
11817
12017
|
};
|
|
11818
12018
|
if (d.rereadRoles) {
|
|
11819
12019
|
try {
|
|
11820
|
-
const now = await d.rereadRoles();
|
|
12020
|
+
const now = await d.rereadRoles(profileOf2(d));
|
|
11821
12021
|
if (now !== null)
|
|
11822
12022
|
roles = now;
|
|
11823
12023
|
} catch {}
|
|
@@ -12175,8 +12375,9 @@ function endDraft(d, run, runner) {
|
|
|
12175
12375
|
function ciNudge(url, failing) {
|
|
12176
12376
|
return `CI failed on your PR (${failing.join(", ")}): ${url}. Read the failing logs (gh pr checks ${url}), fix the cause, run the repo's gate, and push to the same branch.`;
|
|
12177
12377
|
}
|
|
12378
|
+
var SELF_NUDGES = 2;
|
|
12178
12379
|
function reviewNudge(url, count) {
|
|
12179
|
-
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.`;
|
|
12380
|
+
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.`;
|
|
12180
12381
|
}
|
|
12181
12382
|
async function follow(d, state, run, claim, runner, finished, attention, changes, decided) {
|
|
12182
12383
|
const pr = run.pr;
|
|
@@ -12213,8 +12414,8 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
|
|
|
12213
12414
|
try {
|
|
12214
12415
|
alive = runner.status(run.handle) === "running";
|
|
12215
12416
|
if (alive) {
|
|
12216
|
-
runner.send(run.handle, text);
|
|
12217
|
-
d.log(`${ref(run)}: ${what} — told the session`);
|
|
12417
|
+
const note = runner.send(run.handle, text);
|
|
12418
|
+
d.log(`${ref(run)}: ${what} — told the session${note ? ` (${note})` : ""}`);
|
|
12218
12419
|
return;
|
|
12219
12420
|
}
|
|
12220
12421
|
} catch {}
|
|
@@ -12231,16 +12432,38 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
|
|
|
12231
12432
|
pr.resolving = undefined;
|
|
12232
12433
|
pr.conflicted = undefined;
|
|
12233
12434
|
}
|
|
12234
|
-
if (view.feedback > pr.feedback)
|
|
12235
|
-
|
|
12435
|
+
if (view.feedback > pr.feedback) {
|
|
12436
|
+
const what = `${view.feedback - pr.feedback} new review comment(s)`;
|
|
12437
|
+
const authorBefore = pr.authorFeedback ?? 0;
|
|
12438
|
+
const othersGrew = view.feedback - view.authorFeedback > pr.feedback - authorBefore;
|
|
12439
|
+
const selfNudges = othersGrew ? 0 : (pr.selfNudges ?? 0) + 1;
|
|
12440
|
+
if (selfNudges > SELF_NUDGES) {
|
|
12441
|
+
d.log(`${ref(run)}: ${what}, only the PR author's own after ${SELF_NUDGES} nudges in a row — needs a person (the session may be answering itself): ${pr.url}`);
|
|
12442
|
+
attention.push({
|
|
12443
|
+
...at,
|
|
12444
|
+
what: `${what} from the PR's author only, after ${SELF_NUDGES} nudges: is the session answering itself?`
|
|
12445
|
+
});
|
|
12446
|
+
} else {
|
|
12447
|
+
tell(reviewNudge(pr.url, view.feedback), what);
|
|
12448
|
+
pr.selfNudges = selfNudges || undefined;
|
|
12449
|
+
}
|
|
12450
|
+
}
|
|
12236
12451
|
pr.failing = view.failing;
|
|
12237
12452
|
pr.feedback = Math.max(pr.feedback, view.feedback);
|
|
12453
|
+
pr.authorFeedback = Math.max(pr.authorFeedback ?? 0, view.authorFeedback) || undefined;
|
|
12238
12454
|
const clear = view.failing.length === 0 && view.pending === 0 && view.reviewDecision === "APPROVED";
|
|
12239
12455
|
if (clear && !pr.clear) {
|
|
12240
12456
|
d.log(`${ref(run)}: ${pr.url} is green and approved — ready for your merge call`);
|
|
12241
12457
|
attention.push({ ...at, what: "green and approved: ready for a merge" });
|
|
12242
12458
|
}
|
|
12243
12459
|
pr.clear = clear;
|
|
12460
|
+
const prRead = d.source.prRead;
|
|
12461
|
+
const posted = `${ci}|${review}|${clear}`;
|
|
12462
|
+
if (prRead && posted !== pr.posted) {
|
|
12463
|
+
const read = { ci, ...review ? { review } : {}, clear };
|
|
12464
|
+
if (await prRead.call(d.source, run.item, run.node, read))
|
|
12465
|
+
pr.posted = posted;
|
|
12466
|
+
}
|
|
12244
12467
|
return true;
|
|
12245
12468
|
}
|
|
12246
12469
|
function rippleRunnerWorktree(d, run, runner, head) {
|
|
@@ -12270,15 +12493,21 @@ async function ripple(d, state, run, claim, runner, url, finished, decided, head
|
|
|
12270
12493
|
...(state.merged ?? []).filter((m) => !(m.item === run.item && m.node === run.node)),
|
|
12271
12494
|
{ item: run.item, node: run.node, url, repoPath: run.repoPath }
|
|
12272
12495
|
];
|
|
12496
|
+
let landed = true;
|
|
12273
12497
|
try {
|
|
12274
12498
|
await d.source.report(claim, outcome, d.engine);
|
|
12275
12499
|
} catch (e) {
|
|
12276
|
-
|
|
12277
|
-
|
|
12500
|
+
if (!(e instanceof ReportRefusedError && e.final)) {
|
|
12501
|
+
d.log(`${ref(run)}: merged (${url}), but the report failed — retrying next tick: ${message(e)}`);
|
|
12502
|
+
return false;
|
|
12503
|
+
}
|
|
12504
|
+
d.log(`${ref(run)}: merged (${url}), but SLEDS refused the merged report (HTTP ${e.status}) — not retrying: ${message(e)}`);
|
|
12505
|
+
landed = false;
|
|
12278
12506
|
}
|
|
12279
12507
|
finished.push({ item: run.item, node: run.node, outcome });
|
|
12280
12508
|
setLane(d, runner, run.handle, "done");
|
|
12281
|
-
|
|
12509
|
+
if (landed)
|
|
12510
|
+
await openLanded(d, state, run, url, mergeCommit, decided);
|
|
12282
12511
|
try {
|
|
12283
12512
|
if (runner.status(run.handle) === "running")
|
|
12284
12513
|
runner.stop(run.handle);
|
|
@@ -12333,7 +12562,7 @@ function eventAnswerNudge(e, answer) {
|
|
|
12333
12562
|
|
|
12334
12563
|
${answer}
|
|
12335
12564
|
|
|
12336
|
-
On your own branch, by a merge commit — never a rebase that needs a force-push — run the repo's gate, push, and comment on the PR how each file was resolved.`;
|
|
12565
|
+
On your own branch, by a merge commit — never a rebase that needs a force-push — run the repo's gate, push, and comment on the PR how each file was resolved, ending the comment with the line ${ENGINE_MARK}.`;
|
|
12337
12566
|
if (e.kind === "conflict")
|
|
12338
12567
|
return `The decider on your PR's conflict with ${e.sibling} (${e.pr}):
|
|
12339
12568
|
|
|
@@ -12563,9 +12792,13 @@ function deliver(d, state, dec, answer, attention, decided) {
|
|
|
12563
12792
|
const url = dec.event.pr;
|
|
12564
12793
|
if (kind === "conflict")
|
|
12565
12794
|
conflictStep(run, "decidedAt", d.now());
|
|
12795
|
+
let status;
|
|
12566
12796
|
try {
|
|
12567
|
-
|
|
12568
|
-
|
|
12797
|
+
status = run && runner ? runner.status(run.handle) : undefined;
|
|
12798
|
+
if (run && runner && status === "running") {
|
|
12799
|
+
const note = runner.send(run.handle, eventAnswerNudge(dec.event, answer));
|
|
12800
|
+
if (note)
|
|
12801
|
+
d.log(`${ref(run)}: ${KIND_SAYS[kind]} answered — ${note}`);
|
|
12569
12802
|
if (run.pr && baseConflict(dec.event))
|
|
12570
12803
|
run.pr.resolving = dec.event.baseSha;
|
|
12571
12804
|
if (kind === "conflict")
|
|
@@ -12581,7 +12814,8 @@ function deliver(d, state, dec, answer, attention, decided) {
|
|
|
12581
12814
|
}
|
|
12582
12815
|
} catch {}
|
|
12583
12816
|
const event = dec.event;
|
|
12584
|
-
|
|
12817
|
+
const gone = status !== undefined && status !== "running";
|
|
12818
|
+
if (run && gone && baseConflict(event) && startResolver(d, run, event, answer)) {
|
|
12585
12819
|
conflictStep(run, "fixingAt", d.now());
|
|
12586
12820
|
decided.push({
|
|
12587
12821
|
item: dec.item,
|
|
@@ -12670,6 +12904,9 @@ function startResolver(d, run, e, answer) {
|
|
|
12670
12904
|
if (!runner || runner.makesWorktree || run.handle.cwd === run.repoPath)
|
|
12671
12905
|
return false;
|
|
12672
12906
|
const name = sessionName(run.item, `${run.node}-resolve`);
|
|
12907
|
+
try {
|
|
12908
|
+
runner.stop(run.handle);
|
|
12909
|
+
} catch {}
|
|
12673
12910
|
try {
|
|
12674
12911
|
const launch = nodeLaunchFor(d, ref(run));
|
|
12675
12912
|
run.handle = runner.start({
|
|
@@ -12680,7 +12917,9 @@ function startResolver(d, run, e, answer) {
|
|
|
12680
12917
|
base: e.base,
|
|
12681
12918
|
promptFile: d.writePrompt(name, eventAnswerNudge(e, answer)),
|
|
12682
12919
|
permissionMode: d.permissionMode,
|
|
12683
|
-
...launchOptions(launch)
|
|
12920
|
+
...launchOptions(launch),
|
|
12921
|
+
...run.mcpConfig ? { mcpConfig: run.mcpConfig, ...d.strictMcp ? { strictMcp: true } : {} } : {},
|
|
12922
|
+
...run.group ? { group: run.group } : {}
|
|
12684
12923
|
});
|
|
12685
12924
|
} catch (err) {
|
|
12686
12925
|
d.log(`${ref(run)}: could not start a resolver session: ${message(err)}`);
|
|
@@ -12834,7 +13073,7 @@ async function settleEvent(d, events, state, dec, attention, decided) {
|
|
|
12834
13073
|
if (!dec.answerer && baseConflict(dec.event) && events.answerDecision)
|
|
12835
13074
|
return answerBaseConflict(d, events, state, dec, attention, decided);
|
|
12836
13075
|
if (!dec.answerer)
|
|
12837
|
-
return d
|
|
13076
|
+
return startsHeld(d) ? true : startEventAnswerer(d, events, state, dec, decided);
|
|
12838
13077
|
const a = dec.answerer;
|
|
12839
13078
|
const runner = runnerFor(d, a.handle);
|
|
12840
13079
|
if (!runner) {
|
|
@@ -13542,17 +13781,17 @@ function flagsFromProfile(flags, given, profile, home) {
|
|
|
13542
13781
|
}
|
|
13543
13782
|
if (profile.holdCpu !== undefined && !has("hold-cpu")) {
|
|
13544
13783
|
out.holdCpu = profile.holdCpu;
|
|
13545
|
-
if (profile.holdCpu !==
|
|
13784
|
+
if (profile.holdCpu !== HOLD_DEFAULTS2.holdCpu)
|
|
13546
13785
|
used.push(`hold at cpu ${profile.holdCpu}%`);
|
|
13547
13786
|
}
|
|
13548
13787
|
if (profile.holdLoad !== undefined && !has("hold-load")) {
|
|
13549
13788
|
out.holdLoad = profile.holdLoad;
|
|
13550
|
-
if (profile.holdLoad !==
|
|
13789
|
+
if (profile.holdLoad !== HOLD_DEFAULTS2.holdLoad)
|
|
13551
13790
|
used.push(`hold at load ${profile.holdLoad}× cores`);
|
|
13552
13791
|
}
|
|
13553
13792
|
if (profile.holdPressure && !has("hold-pressure")) {
|
|
13554
13793
|
out.holdPressure = profile.holdPressure;
|
|
13555
|
-
if (profile.holdPressure !==
|
|
13794
|
+
if (profile.holdPressure !== HOLD_DEFAULTS2.holdPressure)
|
|
13556
13795
|
used.push(`hold at memory pressure ${profile.holdPressure}`);
|
|
13557
13796
|
}
|
|
13558
13797
|
if (profile.holdOff !== undefined && !has("hold")) {
|
|
@@ -13565,10 +13804,10 @@ function flagsFromProfile(flags, given, profile, home) {
|
|
|
13565
13804
|
function machineSettings(flags) {
|
|
13566
13805
|
return {
|
|
13567
13806
|
thresholds: {
|
|
13568
|
-
cpuPct: flags.holdCpu ??
|
|
13569
|
-
loadPerCore: flags.holdLoad ??
|
|
13807
|
+
cpuPct: flags.holdCpu ?? HOLD_DEFAULTS2.holdCpu,
|
|
13808
|
+
loadPerCore: flags.holdLoad ?? HOLD_DEFAULTS2.holdLoad
|
|
13570
13809
|
},
|
|
13571
|
-
pressure: flags.holdPressure ??
|
|
13810
|
+
pressure: flags.holdPressure ?? HOLD_DEFAULTS2.holdPressure,
|
|
13572
13811
|
off: flags.holdOff === true
|
|
13573
13812
|
};
|
|
13574
13813
|
}
|
|
@@ -15184,17 +15423,17 @@ function flagsFromProfile2(flags, given, profile, home) {
|
|
|
15184
15423
|
}
|
|
15185
15424
|
if (profile.holdCpu !== undefined && !has("hold-cpu")) {
|
|
15186
15425
|
out.holdCpu = profile.holdCpu;
|
|
15187
|
-
if (profile.holdCpu !==
|
|
15426
|
+
if (profile.holdCpu !== HOLD_DEFAULTS2.holdCpu)
|
|
15188
15427
|
used.push(`hold at cpu ${profile.holdCpu}%`);
|
|
15189
15428
|
}
|
|
15190
15429
|
if (profile.holdLoad !== undefined && !has("hold-load")) {
|
|
15191
15430
|
out.holdLoad = profile.holdLoad;
|
|
15192
|
-
if (profile.holdLoad !==
|
|
15431
|
+
if (profile.holdLoad !== HOLD_DEFAULTS2.holdLoad)
|
|
15193
15432
|
used.push(`hold at load ${profile.holdLoad}× cores`);
|
|
15194
15433
|
}
|
|
15195
15434
|
if (profile.holdPressure && !has("hold-pressure")) {
|
|
15196
15435
|
out.holdPressure = profile.holdPressure;
|
|
15197
|
-
if (profile.holdPressure !==
|
|
15436
|
+
if (profile.holdPressure !== HOLD_DEFAULTS2.holdPressure)
|
|
15198
15437
|
used.push(`hold at memory pressure ${profile.holdPressure}`);
|
|
15199
15438
|
}
|
|
15200
15439
|
if (profile.holdOff !== undefined && !has("hold")) {
|
|
@@ -15207,15 +15446,15 @@ function flagsFromProfile2(flags, given, profile, home) {
|
|
|
15207
15446
|
function machineSettings2(flags) {
|
|
15208
15447
|
return {
|
|
15209
15448
|
thresholds: {
|
|
15210
|
-
cpuPct: flags.holdCpu ??
|
|
15211
|
-
loadPerCore: flags.holdLoad ??
|
|
15449
|
+
cpuPct: flags.holdCpu ?? HOLD_DEFAULTS2.holdCpu,
|
|
15450
|
+
loadPerCore: flags.holdLoad ?? HOLD_DEFAULTS2.holdLoad
|
|
15212
15451
|
},
|
|
15213
|
-
pressure: flags.holdPressure ??
|
|
15452
|
+
pressure: flags.holdPressure ?? HOLD_DEFAULTS2.holdPressure,
|
|
15214
15453
|
off: flags.holdOff === true
|
|
15215
15454
|
};
|
|
15216
15455
|
}
|
|
15217
|
-
async function rereadMachine2(
|
|
15218
|
-
const profile = await
|
|
15456
|
+
async function rereadMachine2(read, flags, given) {
|
|
15457
|
+
const profile = await read();
|
|
15219
15458
|
return profile ? machineSettings2(flagsFromProfile2(flags, given, profile).flags) : null;
|
|
15220
15459
|
}
|
|
15221
15460
|
var EFFORT_SAYS2 = `${EFFORTS.slice(0, -1).join(", ")} or ${EFFORTS.at(-1)}`;
|
|
@@ -15230,8 +15469,8 @@ var ROLE_KEYS = [
|
|
|
15230
15469
|
["drafter-model", "drafterModel"],
|
|
15231
15470
|
["drafter-effort", "drafterEffort"]
|
|
15232
15471
|
];
|
|
15233
|
-
async function rereadRoles(
|
|
15234
|
-
const profile = ROLE_KEYS.every(([flag]) => given.includes(flag)) ? undefined : await
|
|
15472
|
+
async function rereadRoles(read, flags, given) {
|
|
15473
|
+
const profile = ROLE_KEYS.every(([flag]) => given.includes(flag)) ? undefined : await read();
|
|
15235
15474
|
if (profile === null)
|
|
15236
15475
|
return null;
|
|
15237
15476
|
const out = {};
|
|
@@ -15243,10 +15482,10 @@ async function rereadRoles(client, flags, given) {
|
|
|
15243
15482
|
}
|
|
15244
15483
|
return out;
|
|
15245
15484
|
}
|
|
15246
|
-
async function rereadAtOnce2(
|
|
15485
|
+
async function rereadAtOnce2(read, flags, given) {
|
|
15247
15486
|
if (given.includes("at-once"))
|
|
15248
15487
|
return flags.atOnce;
|
|
15249
|
-
const profile = await
|
|
15488
|
+
const profile = await read();
|
|
15250
15489
|
return profile ? profile.atOnce ?? 1 : null;
|
|
15251
15490
|
}
|
|
15252
15491
|
var PROFILE_RETRY_MS2 = [2000, 5000];
|
|
@@ -15729,16 +15968,18 @@ var engineCmd = async (o) => {
|
|
|
15729
15968
|
});
|
|
15730
15969
|
}
|
|
15731
15970
|
const deps = engineDeps2(o, flags, source, runner, `engine:${flags.name}`);
|
|
15732
|
-
if (client
|
|
15971
|
+
if (client) {
|
|
15733
15972
|
const c = client;
|
|
15973
|
+
deps.readProfile = () => readProfile(c);
|
|
15974
|
+
}
|
|
15975
|
+
if (client && deps.machine) {
|
|
15734
15976
|
const started = flags;
|
|
15735
|
-
deps.machine.reread = () => rereadMachine2(
|
|
15977
|
+
deps.machine.reread = (read) => rereadMachine2(read, started, parsed.given);
|
|
15736
15978
|
}
|
|
15737
15979
|
if (client) {
|
|
15738
|
-
const c = client;
|
|
15739
15980
|
const started = flags;
|
|
15740
|
-
deps.rereadRoles = () => rereadRoles(
|
|
15741
|
-
deps.rereadAtOnce = () => rereadAtOnce2(
|
|
15981
|
+
deps.rereadRoles = (read) => rereadRoles(read, started, parsed.given);
|
|
15982
|
+
deps.rereadAtOnce = (read) => rereadAtOnce2(read, started, parsed.given);
|
|
15742
15983
|
}
|
|
15743
15984
|
log(engineHeader2(terminalOf2(o).c, deps.engine, (source.name === "jira" ? `Jira epic ${flags.jira}` : pickup === "auto" ? "your sled, Pickup: Auto (it arms the next ready item)" : "the routes armed on your sled") + (flags.org ? ` · org ${flags.org}` : "") + (flags.seat ? ` · as ${flags.seat}` : ""), runner.name, flags));
|
|
15744
15985
|
if (flags.strictMcp && flags.agent)
|
|
@@ -16878,8 +17119,8 @@ function withEstimated(rows, a) {
|
|
|
16878
17119
|
function withActual(rows, a) {
|
|
16879
17120
|
if (a.state === "started")
|
|
16880
17121
|
return rows;
|
|
16881
|
-
const key = sourceKey(a.source);
|
|
16882
|
-
return rows.filter((r) => r.key !== key);
|
|
17122
|
+
const key = a.source ? sourceKey(a.source) : a.key;
|
|
17123
|
+
return key ? rows.filter((r) => r.key !== key) : rows;
|
|
16883
17124
|
}
|
|
16884
17125
|
function owedBySession(rows, sessionId) {
|
|
16885
17126
|
return rows.filter((r) => r.sessionId === sessionId && r.askedAt === undefined);
|
|
@@ -16934,11 +17175,11 @@ async function usageTool2(deps) {
|
|
|
16934
17175
|
}
|
|
16935
17176
|
var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16936
17177
|
function trackingClient(client, home, now) {
|
|
16937
|
-
const after = (path, body, status) => {
|
|
16938
|
-
if (status < 200 || status >= 300
|
|
17178
|
+
const after = (path, body, status, json) => {
|
|
17179
|
+
if (status < 200 || status >= 300)
|
|
16939
17180
|
return;
|
|
16940
17181
|
try {
|
|
16941
|
-
if (path === "/proxy/estimate") {
|
|
17182
|
+
if (path === "/proxy/estimate" && isObj2(body.source)) {
|
|
16942
17183
|
const spec = typeof body.spec === "string" ? body.spec : "";
|
|
16943
17184
|
writeOwed(home, withEstimated(readOwed(home, now()), {
|
|
16944
17185
|
source: body.source,
|
|
@@ -16947,9 +17188,12 @@ function trackingClient(client, home, now) {
|
|
|
16947
17188
|
now: now(),
|
|
16948
17189
|
sessionId: readSession(home)?.sessionId
|
|
16949
17190
|
}));
|
|
16950
|
-
} else if (path === "/proxy/estimate/actual") {
|
|
17191
|
+
} else if (path === "/proxy/estimate/actual" || path === "/proxy/land") {
|
|
17192
|
+
const key = typeof json?.key === "string" ? json.key : undefined;
|
|
17193
|
+
if (!isObj2(body.source) && !key)
|
|
17194
|
+
return;
|
|
16951
17195
|
writeOwed(home, withActual(readOwed(home, now()), {
|
|
16952
|
-
source: body.source,
|
|
17196
|
+
...isObj2(body.source) ? { source: body.source } : { key },
|
|
16953
17197
|
state: typeof body.state === "string" ? body.state : undefined
|
|
16954
17198
|
}));
|
|
16955
17199
|
}
|
|
@@ -16959,13 +17203,13 @@ function trackingClient(client, home, now) {
|
|
|
16959
17203
|
async post(path, body) {
|
|
16960
17204
|
const r = await client.post(path, body);
|
|
16961
17205
|
if (r)
|
|
16962
|
-
after(path, body, 200);
|
|
17206
|
+
after(path, body, 200, r);
|
|
16963
17207
|
return r;
|
|
16964
17208
|
},
|
|
16965
17209
|
async postFull(path, body) {
|
|
16966
17210
|
const r = await client.postFull(path, body);
|
|
16967
17211
|
if (r)
|
|
16968
|
-
after(path, body, r.status);
|
|
17212
|
+
after(path, body, r.status, r.json);
|
|
16969
17213
|
return r;
|
|
16970
17214
|
},
|
|
16971
17215
|
get: (path) => client.get(path)
|
|
@@ -17402,23 +17646,53 @@ var hooksOrg2 = (home, env = process.env) => {
|
|
|
17402
17646
|
|
|
17403
17647
|
// src/commands/init.ts
|
|
17404
17648
|
var DEFAULT_PROXY_URL = "https://proxy.sleds.ai";
|
|
17405
|
-
var
|
|
17406
|
-
|
|
17649
|
+
var lineReaders = new WeakMap;
|
|
17650
|
+
var lineReader = (input, output) => {
|
|
17651
|
+
const cached = lineReaders.get(input);
|
|
17652
|
+
if (cached)
|
|
17653
|
+
return cached;
|
|
17654
|
+
const rl = createInterface({ input, output, terminal: false });
|
|
17407
17655
|
const events = rl;
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17656
|
+
const queued = [];
|
|
17657
|
+
const waiting = [];
|
|
17658
|
+
let closed = false;
|
|
17659
|
+
events.on("line", (line) => {
|
|
17660
|
+
const take = waiting.shift();
|
|
17661
|
+
if (take)
|
|
17662
|
+
take(line);
|
|
17663
|
+
else
|
|
17664
|
+
queued.push(line);
|
|
17665
|
+
if (waiting.length === 0 && !closed)
|
|
17666
|
+
rl.pause();
|
|
17412
17667
|
});
|
|
17413
17668
|
events.once("close", () => {
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
(
|
|
17417
|
-
`);
|
|
17418
|
-
resolve((answer ?? "").trim());
|
|
17669
|
+
closed = true;
|
|
17670
|
+
for (const take of waiting.splice(0))
|
|
17671
|
+
take(undefined);
|
|
17419
17672
|
});
|
|
17673
|
+
const reader = {
|
|
17674
|
+
next: () => new Promise((resolve) => {
|
|
17675
|
+
if (queued.length > 0)
|
|
17676
|
+
return resolve(queued.shift());
|
|
17677
|
+
if (closed)
|
|
17678
|
+
return resolve(undefined);
|
|
17679
|
+
waiting.push(resolve);
|
|
17680
|
+
rl.resume();
|
|
17681
|
+
})
|
|
17682
|
+
};
|
|
17683
|
+
lineReaders.set(input, reader);
|
|
17684
|
+
return reader;
|
|
17685
|
+
};
|
|
17686
|
+
var promptStdin = async (question, input = process.stdin, output = process.stderr) => {
|
|
17687
|
+
const reader = lineReader(input, output);
|
|
17420
17688
|
output.write(question);
|
|
17421
|
-
|
|
17689
|
+
const answer = await reader.next();
|
|
17690
|
+
if (answer === undefined)
|
|
17691
|
+
output.write(`
|
|
17692
|
+
(stdin closed before an answer; taking it as no answer)
|
|
17693
|
+
`);
|
|
17694
|
+
return (answer ?? "").trim();
|
|
17695
|
+
};
|
|
17422
17696
|
async function signupIndividual(proxyUrl, email, credential, fetchFn = fetch, code) {
|
|
17423
17697
|
return signupRequest(proxyUrl, { email, apiKey: credential, ...code ? { code } : {} }, fetchFn);
|
|
17424
17698
|
}
|
|
@@ -18320,6 +18594,7 @@ init_config();
|
|
|
18320
18594
|
init_state();
|
|
18321
18595
|
init_settings();
|
|
18322
18596
|
var DEFAULT_PROXY_URL2 = "https://proxy.sleds.ai";
|
|
18597
|
+
var lineReaders2 = new WeakMap;
|
|
18323
18598
|
|
|
18324
18599
|
// src/commands/on.ts
|
|
18325
18600
|
var on = (o) => {
|
|
@@ -19012,7 +19287,8 @@ async function decisionCmd(o, argv) {
|
|
|
19012
19287
|
return said(o, key, json, r, () => log(seen ? `${key}: marked seen` : overrule ? `${key}: overruled — your answer stands in the log` : answeredLine(key, r.json?.route ?? route, r.json?.held)));
|
|
19013
19288
|
}
|
|
19014
19289
|
var DECISIONS_USAGE = "usage: sleds route decisions [<key>] [--flagged] [--json]";
|
|
19015
|
-
function decisionsLines(
|
|
19290
|
+
function decisionsLines(raw) {
|
|
19291
|
+
const j = clean(raw);
|
|
19016
19292
|
const rows = Array.isArray(j.decisions) ? j.decisions : [];
|
|
19017
19293
|
if (!rows.length)
|
|
19018
19294
|
return ["no decisions"];
|
|
@@ -20686,7 +20962,8 @@ function ago2(at, now) {
|
|
|
20686
20962
|
const h = Math.floor(m / 60);
|
|
20687
20963
|
return h < 24 ? `${h}h ago` : `${Math.floor(h / 24)}d ago`;
|
|
20688
20964
|
}
|
|
20689
|
-
function webhookLines(
|
|
20965
|
+
function webhookLines(raw, now) {
|
|
20966
|
+
const j = clean(raw);
|
|
20690
20967
|
const rows = Array.isArray(j.webhooks) ? j.webhooks : [];
|
|
20691
20968
|
if (!rows.length)
|
|
20692
20969
|
return ["no webhooks — add one in the app: Settings › Integrations › Webhooks"];
|
|
@@ -20703,7 +20980,8 @@ function webhookLines(j, now) {
|
|
|
20703
20980
|
return `${off} ${str10(h.name)} ${host} ${events.includes("*") ? "all events" : events.join(",")} ${lastText}${why} [${str10(h.id)}]`;
|
|
20704
20981
|
});
|
|
20705
20982
|
}
|
|
20706
|
-
function deliveryLines(
|
|
20983
|
+
function deliveryLines(raw, now) {
|
|
20984
|
+
const j = clean(raw);
|
|
20707
20985
|
const rows = Array.isArray(j.deliveries) ? j.deliveries : [];
|
|
20708
20986
|
const name = str10(j.webhook?.name);
|
|
20709
20987
|
if (!rows.length)
|
|
@@ -21422,7 +21700,7 @@ ${renderHelpText(argv[1])}`);
|
|
|
21422
21700
|
case "version":
|
|
21423
21701
|
case "--version":
|
|
21424
21702
|
case "-v":
|
|
21425
|
-
console.log("sleds 0.1.
|
|
21703
|
+
console.log("sleds 0.1.33");
|
|
21426
21704
|
return 0;
|
|
21427
21705
|
default:
|
|
21428
21706
|
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.33",
|
|
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",
|