@coreplane/switchboard 1.260.2 → 1.260.3
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/assets/deploy/cloudflare-memory/backgroundTasks.ts +27 -3
- package/dist/assets/deploy/cloudflare-memory/testDiagnostics.ts +227 -0
- package/dist/assets/deploy/cloudflare-memory/testDiagnosticsProtocol.ts +22 -0
- package/dist/assets/deploy/cloudflare-memory/testDiagnosticsReporter.ts +88 -0
- package/dist/assets/deploy/cloudflare-memory/tsconfig.json +14 -2
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +6 -5
- package/dist/assets/src/core/coordinator/contract.ts +48 -2
- package/dist/assets/src/core/coordinator/driver.ts +36 -1
- package/dist/assets/src/core/reviewVerdict.ts +15 -5
- package/dist/assets/src/core/ship/contract.ts +1 -1
- package/dist/assets/src/core/ship/coordinator.ts +206 -78
- package/dist/cli.js +372 -108
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2094,7 +2094,7 @@ function renderFirstInstruction(rebase) {
|
|
|
2094
2094
|
const branch2 = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
|
|
2095
2095
|
const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
|
|
2096
2096
|
const gates = `${FAST_GATES_POINTER} ${GATE_RECEIPTS}`;
|
|
2097
|
-
return `Rebase ${branch2} onto ${onto} before any other work \u2014 the parent unit has merged and the base has moved; the only writes are your own on that branch.
|
|
2097
|
+
return `Rebase ${branch2} onto ${onto} before any other work \u2014 the parent unit has merged and the base has moved; the only writes are your own on that branch. Push the branch as soon as the change exists and the fast gates pass \u2014 the project's full verification is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not survive the run's end. ${gates} ${REBASE_POINTER} At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`;
|
|
2098
2098
|
}
|
|
2099
2099
|
function renderSpecRow(row2, dropText) {
|
|
2100
2100
|
const head = `#### ${row2.spec} item ${row2.item}`;
|
|
@@ -2313,7 +2313,7 @@ EVERY PR includes one that already exists when you push \u2014 opened by a perso
|
|
|
2313
2313
|
- **For agents** (\`agentNotes\`, optional, \u22642000, collapsed): what a reviewing agent needs that a person does not \u2014 the rebase you did, generated files to skip, the command that reproduces the bug.`;
|
|
2314
2314
|
NEVER_MERGE = `NEVER merge a pull request and NEVER approve one \u2014 no merge or approve command, no merge/approve API call, no pushing to the default branch. Your deliverable is the pushed branch plus the submitted description; Switchboard's own GitHub writes are the PR open/edit, never an approval or a merge \u2014 a human decides what merges.`;
|
|
2315
2315
|
CONTRACT_HEADINGS_LIST = Object.values(CONTRACT_SECTION_HEADINGS).map((h) => `\`${h}\``).join(", ");
|
|
2316
|
-
UNIT_CONTRACT = `UNIT CONTRACT: when your first user turn carries a \`${CONTRACT_HEADING}\` block \u2014 its sub-headings, in this order: ${CONTRACT_HEADINGS_LIST} \u2014 it is the contract for one plan unit, rendered by Switchboard from the plan itself, and it outranks any free-text task beside it. Do its first instruction first: the rebase of the unit's branch onto the merged parent
|
|
2316
|
+
UNIT_CONTRACT = `UNIT CONTRACT: when your first user turn carries a \`${CONTRACT_HEADING}\` block \u2014 its sub-headings, in this order: ${CONTRACT_HEADINGS_LIST} \u2014 it is the contract for one plan unit, rendered by Switchboard from the plan itself, and it outranks any free-text task beside it. Do its first instruction first: the rebase of the unit's branch onto the merged parent. Then implement the unit's section as written: every test scenario it lists is added as a test, every spec row it names is updated so its proof binding resolves, the agent rules are followed, and no guard it names is weakened. The review is handed the same block and checks the diff against it: a test scenario the unit listed and the diff did not add is a finding at minor severity \u2014 the same severity as a spec contradiction. Never edit the plan record itself; where the unit is wrong or a criterion could not be proven, say so in the handoff and in your final message.`;
|
|
2317
2317
|
UNIT_HANDOFF = `UNIT HANDOFF: when your first user turn carries a \`${CONTRACT_HEADING}\` block, call the submit_handoff tool once, after submit_pr_description and before your final message, with the typed handoff \u2014 deviations: where you departed from the unit as written (from, to, why); followUps: what you found and did not do, and where it belongs (what, where); unproven: which of the unit's test scenarios or criteria you could not prove, and why (criterion, why); landed (optional): what of the unit was already on the base when you began, and the pull request or commit that carries it (what, where) \u2014 when the whole unit is already there, push nothing of your own and open no pull request; the handoff's landed rows end the unit done. Switchboard records it on the run and posts it to the unit's board issue, where a person decides each row's disposition; you never edit the plan's ledger yourself. An empty handoff is submitted as three empty lists, never skipped \u2014 a missing handoff reads as an unfinished run, not as nothing to say. Without a \`${CONTRACT_HEADING}\` block, do not call it.`;
|
|
2318
2318
|
IMAGE_TOOLCHAIN = `Node 24 with npm and pnpm; python3, make and g++ (native modules build); ffmpeg (frames out of a video \u2014 \`ffmpeg -i in.mp4 -vf fps=1 f_%03d.png\` \u2014 and video out of frames or a recording); and a headless Chromium through Playwright \u2014 \`playwright screenshot <url> out.png\`, \`playwright pdf <url> out.pdf\`, or \`require('playwright')\` for a scripted page and \`recordVideo\``;
|
|
2319
2319
|
SANDBOX_TOOLCHAIN = `The sandbox image carries ${IMAGE_TOOLCHAIN}; and Docker (the engine starts on the first \`docker\` call).`;
|
|
@@ -2330,7 +2330,7 @@ Keep notes with the \`notes\` tool: one short document, replaced whole each time
|
|
|
2330
2330
|
CHECKS_BY_COST = `CHECKS BY COST \u2014 push before the expensive ones. Every check you might run has a cost class: seconds (a formatter or a linter on the files you touched, one test file, a docs, link or spec check, the typecheck of one package) or minutes (the whole test suite, a build, a dependency install, an end-to-end or full verification). Know a command's class before you run it \u2014 from the project's own scripts and CI configuration, from how long it took last time, or by the class above when you have nothing better. Prove each change with the cheapest check that can prove it, matched to the change's scope and scoped to the changed set \u2014 the tests nearest your change, the touched project's typecheck, the changed files' formatting, never the whole tree: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. Every CI pipeline runs the tests, the types, the formatting and the full verification on your push, so you never run them again: you validate and fix your own change before pushing, at the changed-set scope. Passing the full test suite and the full typecheck is NOT part of your criteria: CI is that gate and the only place they run \u2014 on a shared machine they cost minutes that every other run pays for. As soon as the change exists and those checks pass, commit and push \u2014 the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Beyond the changed set, use judgement about what this change needs rather than a checklist, fixing forward with further commits and pushes. Never start an operation whose expected duration does not fit the time you have left minus what a commit, a push and the description need: push what there is and say plainly what is unverified instead. At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS} The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
|
|
2331
2331
|
TOUCHED_TESTS_COMMAND = "`npx vitest run` on the test files you touched, by name,";
|
|
2332
2332
|
FAST_GATES_BEFORE_PUSH = `THE FAST GATES, before every push \u2014 each scoped to the changed set, never the whole project: ${TOUCHED_TESTS_COMMAND} once (never \`--changed\`, never a directory: on a moving base that is most of the suite), \`tsc --noEmit -p\` the touched tsconfig under \`NODE_OPTIONS=--max-old-space-size=6144\`, \`npx prettier --check\` on the changed files, \`npm run hygiene:check\` and \`npm run specs:check\` \u2014 then your judgement on what else this change needs, not a longer checklist. The full verification is CI's gate \u2014 \`npm run verify\` runs there on your push, never here: push a head early and let CI judge it, fixing forward with further commits and pushes.`;
|
|
2333
|
-
REBASE_BEFORE_PUSH = "REBASE BEFORE EVERY PUSH \u2014 always, not configurable. Immediately before each push: fetch your base branch, rebase your branch onto it, resolve any conflict with the context you already have
|
|
2333
|
+
REBASE_BEFORE_PUSH = "REBASE BEFORE EVERY PUSH \u2014 always, not configurable. Immediately before each push: fetch your base branch, rebase your branch onto it, and resolve any conflict with the context you already have in one bounded model round \u2014 the thread's context and the repository's AGENTS.md (which says how a generated file is regenerated: regenerate it, never hand-merge it). If you cannot resolve it inside that bound, end the round; otherwise re-run THE FAST GATES on the rebased tree, and only then push. Every head that reaches review is then current with its base when it lands, and no unit ends merge-ready behind a sibling that merged first.";
|
|
2334
2334
|
CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
2335
2335
|
|
|
2336
2336
|
You work inside a dedicated workspace directory with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
|
|
@@ -3162,6 +3162,9 @@ var init_redact = __esm({
|
|
|
3162
3162
|
function severityAtOrAbove(severity, level) {
|
|
3163
3163
|
return isAddressSeverity(severity) && severityRank(severity) <= severityRank(level);
|
|
3164
3164
|
}
|
|
3165
|
+
function findingsAtOrAbove(findings, level) {
|
|
3166
|
+
return findings.filter((f) => severityAtOrAbove(f.severity, level));
|
|
3167
|
+
}
|
|
3165
3168
|
function resolveAddressSeverity(layers) {
|
|
3166
3169
|
if (layers.run !== void 0) return { level: layers.run, source: "run" };
|
|
3167
3170
|
if (layers.user !== void 0) return { level: layers.user, source: "user" };
|
|
@@ -3274,7 +3277,7 @@ function findingCounts(findings) {
|
|
|
3274
3277
|
}
|
|
3275
3278
|
function whereCell(f, target) {
|
|
3276
3279
|
const location = f.line !== void 0 ? `${f.file}:${f.line}` : f.file;
|
|
3277
|
-
const label = `\`${
|
|
3280
|
+
const label = `\`${escapeMarkdownTableCell(location)}\``;
|
|
3278
3281
|
if (!target || !isRepoPath(f.file)) return label;
|
|
3279
3282
|
const path = encodeURI(f.file).replace(/#/g, "%23").replace(/\?/g, "%3F");
|
|
3280
3283
|
const url = `https://github.com/${target.repo}/blob/${target.head}/${path}${f.line !== void 0 ? `#L${f.line}` : ""}`;
|
|
@@ -3290,6 +3293,7 @@ function verdictMarker(verdict, target) {
|
|
|
3290
3293
|
id: f.id,
|
|
3291
3294
|
severity: f.severity,
|
|
3292
3295
|
file: f.file,
|
|
3296
|
+
title: f.title,
|
|
3293
3297
|
...f.line !== void 0 ? { line: f.line } : {},
|
|
3294
3298
|
...f.humanGated ? { humanGated: true } : {}
|
|
3295
3299
|
}))
|
|
@@ -3311,7 +3315,9 @@ function buildReviewPostBody(answer, verdict, target) {
|
|
|
3311
3315
|
[
|
|
3312
3316
|
"| Severity | Finding | Where |",
|
|
3313
3317
|
"| --- | --- | --- |",
|
|
3314
|
-
...findings.map(
|
|
3318
|
+
...findings.map(
|
|
3319
|
+
(f) => `| ${f.severity} | **${escapeMarkdownTableCell(f.id)}** ${escapeMarkdownTableCell(f.title)} | ${whereCell(f, target)} |`
|
|
3320
|
+
)
|
|
3315
3321
|
].join("\n")
|
|
3316
3322
|
);
|
|
3317
3323
|
}
|
|
@@ -3412,7 +3418,7 @@ function redactVerdict(v, redact = redactSecrets) {
|
|
|
3412
3418
|
function redactDispositions(dispositions, redact = redactSecrets) {
|
|
3413
3419
|
return dispositions.map((d) => ({ findingId: d.findingId, disposition: d.disposition, note: redact(d.note) }));
|
|
3414
3420
|
}
|
|
3415
|
-
var FINDING_SEVERITIES, ADDRESS_SEVERITIES, DEFAULT_ADDRESS_SEVERITY, isAddressSeverity, severityRank, LGTM_TOKEN, CHANGES_TOKEN, NO_VERDICT_LINE,
|
|
3421
|
+
var FINDING_SEVERITIES, ADDRESS_SEVERITIES, DEFAULT_ADDRESS_SEVERITY, isAddressSeverity, severityRank, LGTM_TOKEN, CHANGES_TOKEN, NO_VERDICT_LINE, escapeMarkdownTableCell, unescapeMarkdownTableCell, isRepoPath, VERDICT_KINDS, DISPOSITION_KINDS, isRecordLike, REVIEW_POST_HEAD;
|
|
3416
3422
|
var init_reviewVerdict = __esm({
|
|
3417
3423
|
"../../src/core/reviewVerdict.ts"() {
|
|
3418
3424
|
"use strict";
|
|
@@ -3427,7 +3433,8 @@ var init_reviewVerdict = __esm({
|
|
|
3427
3433
|
LGTM_TOKEN = "LGTM:";
|
|
3428
3434
|
CHANGES_TOKEN = "Changes requested:";
|
|
3429
3435
|
NO_VERDICT_LINE = "No verdict submitted \u2014 not approving.";
|
|
3430
|
-
|
|
3436
|
+
escapeMarkdownTableCell = (text) => oneLine2(text).replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
3437
|
+
unescapeMarkdownTableCell = (text) => text.replace(/\\([\\|])/g, "$1");
|
|
3431
3438
|
isRepoPath = (file) => /^[^\s`]+$/.test(file) && !/^[a-z][a-z0-9+.-]*:\/\//i.test(file);
|
|
3432
3439
|
VERDICT_KINDS = ["approve", "request_changes"];
|
|
3433
3440
|
DISPOSITION_KINDS = ["fixed", "declined"];
|
|
@@ -5299,7 +5306,7 @@ function redactHandoff(h, redact = redactSecrets) {
|
|
|
5299
5306
|
function line(s) {
|
|
5300
5307
|
return s.replace(/\s*\n\s*/g, " ");
|
|
5301
5308
|
}
|
|
5302
|
-
function
|
|
5309
|
+
function cell(s) {
|
|
5303
5310
|
return line(s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|"));
|
|
5304
5311
|
}
|
|
5305
5312
|
function entryLines(h) {
|
|
@@ -5331,7 +5338,7 @@ function prLink(pr) {
|
|
|
5331
5338
|
function renderHandoffLedgerRows(h, ctx) {
|
|
5332
5339
|
const link = prLink(ctx.pr);
|
|
5333
5340
|
const source2 = `${ctx.unitId} handoff${link ? ` (${link})` : ""}`;
|
|
5334
|
-
return entryLines(h).map((e) => `| ${
|
|
5341
|
+
return entryLines(h).map((e) => `| ${cell(e.row)} | ${cell(source2)} | open |`).join("\n");
|
|
5335
5342
|
}
|
|
5336
5343
|
function renderHandoffComment(h, ctx) {
|
|
5337
5344
|
if (isEmptyHandoff(h)) return void 0;
|
|
@@ -5514,6 +5521,18 @@ function isCoordinatorUnit(v) {
|
|
|
5514
5521
|
if (r.startedAt !== void 0 && !isFinite(r.startedAt)) return false;
|
|
5515
5522
|
return true;
|
|
5516
5523
|
}
|
|
5524
|
+
async function sendUnitNudge(workflow, key) {
|
|
5525
|
+
const instance = key.instanceId;
|
|
5526
|
+
if (!workflow) return { kind: "no-binding", instance };
|
|
5527
|
+
const type = unitNudgeEventType(key);
|
|
5528
|
+
try {
|
|
5529
|
+
const handle2 = await workflow.get(instance);
|
|
5530
|
+
await handle2.sendEvent({ type, payload: {} });
|
|
5531
|
+
return { kind: "sent", instance, type };
|
|
5532
|
+
} catch (err2) {
|
|
5533
|
+
return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5517
5536
|
async function sendChecksSettled(workflow, instance, headSha, settledAt) {
|
|
5518
5537
|
if (!workflow) return { kind: "no-binding", instance };
|
|
5519
5538
|
const type = checksSettledEventType(headSha);
|
|
@@ -5550,12 +5569,13 @@ async function sendChildSignal(workflow, signal2) {
|
|
|
5550
5569
|
return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
|
|
5551
5570
|
}
|
|
5552
5571
|
}
|
|
5553
|
-
var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, UNIT_PATTERN, UNIT_KEY_PATTERN, CHILD_INTERRUPTED_EVENT_PREFIX, CHILD_RESUMED_EVENT_PREFIX, THREAD_EVENT_MAX_BYTES, serializedBytes, isThreadEventStagedFile, isThreadEventAttachment, isThreadEventMode, UNIT_NUDGE_EVENT_PREFIX, CHECKS_SETTLED_EVENT_PREFIX, PULL_MERGED_EVENT_PREFIX, IDLE_WHY_MAX, REPO_SLUG, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isCount, isObject, isPr, isResume, isThread, isRoundGate, isUnitIdle, isSegment, isUnitWakeAnswer;
|
|
5572
|
+
var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, UNIT_PATTERN, UNIT_KEY_PATTERN, CHILD_INTERRUPTED_EVENT_PREFIX, CHILD_RESUMED_EVENT_PREFIX, THREAD_EVENT_MAX_BYTES, serializedBytes, isThreadEventStagedFile, isThreadEventAttachment, isThreadEventMode, UNIT_NUDGE_EVENT_PREFIX, CHECKS_SETTLED_EVENT_PREFIX, PULL_MERGED_EVENT_PREFIX, IDLE_WHY_MAX, REPO_SLUG, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isCount, isObject, isPr, isResume, isThread, isRoundGate, isHumanGatePending, isUnitIdle, isSegment, isUnitWakeAnswer;
|
|
5554
5573
|
var init_contract2 = __esm({
|
|
5555
5574
|
"../../src/core/coordinator/contract.ts"() {
|
|
5556
5575
|
"use strict";
|
|
5557
5576
|
init_budgets();
|
|
5558
5577
|
init_verbosity();
|
|
5578
|
+
init_reviewVerdict();
|
|
5559
5579
|
init_coordinator();
|
|
5560
5580
|
init_handoff();
|
|
5561
5581
|
COORDINATOR_IDENTITY = "coordinator";
|
|
@@ -5591,13 +5611,14 @@ var init_contract2 = __esm({
|
|
|
5591
5611
|
isResume = (v) => isObject(v) && isFinite(v.pr) && (v.headSha === void 0 || isText(v.headSha)) && (v.url === void 0 || isText(v.url, 2048));
|
|
5592
5612
|
isThread = (v) => isObject(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
|
|
5593
5613
|
isRoundGate = (v) => isObject(v) && isAddressSeverity(v.level) && Array.isArray(v.findings) && v.findings.every((f) => typeof f === "string");
|
|
5594
|
-
|
|
5614
|
+
isHumanGatePending = (v) => isObject(v) && isObject(v.pr) && typeof v.pr.number === "number" && Number.isInteger(v.pr.number) && v.pr.number > 0 && isText(v.pr.url) && typeof v.round === "number" && Number.isInteger(v.round) && v.round >= 1 && Array.isArray(v.findings) && v.findings.every((finding) => isFindingShape(finding)) && (v.verdict === "approve" || v.verdict === "request_changes") && (v.reviewRunId === void 0 || isText(v.reviewRunId)) && (v.headSha === void 0 || isText(v.headSha)) && (v.askedAt === void 0 || isFinite(v.askedAt));
|
|
5615
|
+
isUnitIdle = (v) => isObject(v) && isText(v.why, IDLE_WHY_MAX) && isFinite(v.at) && isCount(v.renewalsLeft) && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && (v.humanGate === void 0 || isHumanGatePending(v.humanGate)) && isCount(v.wakes);
|
|
5595
5616
|
isSegment = (v) => isObject(v) && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 2 && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && typeof v.at === "number";
|
|
5596
5617
|
isUnitWakeAnswer = (v) => {
|
|
5597
5618
|
if (!isObject(v)) return false;
|
|
5598
5619
|
if (v.kind === "answered") return typeof v.reply === "string";
|
|
5599
5620
|
if (v.kind === "stopped" || v.kind === "expired") return true;
|
|
5600
|
-
return v.kind === "segment" && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 1 && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && Array.isArray(v.texts) && v.texts.every((text) => typeof text === "string") && Array.isArray(v.senders) && v.senders.every((sender) => isText(sender)) && (v.leaseMs === void 0 || isFinite(v.leaseMs) && v.leaseMs > 0);
|
|
5621
|
+
return v.kind === "segment" && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 1 && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && Array.isArray(v.texts) && v.texts.every((text) => typeof text === "string") && Array.isArray(v.senders) && v.senders.every((sender) => isText(sender)) && (v.leaseMs === void 0 || isFinite(v.leaseMs) && v.leaseMs > 0) && (v.humanGate === void 0 || isHumanGatePending(v.humanGate));
|
|
5601
5622
|
};
|
|
5602
5623
|
}
|
|
5603
5624
|
});
|
|
@@ -17465,20 +17486,83 @@ async function fetchPullRequestTitleBody(pr) {
|
|
|
17465
17486
|
if (!data || typeof data.title !== "string") return void 0;
|
|
17466
17487
|
return { title: data.title, body: typeof data.body === "string" ? data.body : "" };
|
|
17467
17488
|
}
|
|
17468
|
-
|
|
17469
|
-
const
|
|
17470
|
-
|
|
17489
|
+
function nextGithubPage(link) {
|
|
17490
|
+
for (const entry of link?.split(",") ?? []) {
|
|
17491
|
+
const target = /<([^>]+)>/.exec(entry)?.[1];
|
|
17492
|
+
const relations = /(?:^|;)\s*rel="([^"]+)"/.exec(entry)?.[1]?.split(/\s+/) ?? [];
|
|
17493
|
+
if (target !== void 0 && relations.includes("next")) return target;
|
|
17494
|
+
}
|
|
17495
|
+
return void 0;
|
|
17496
|
+
}
|
|
17497
|
+
function isGithubListPage(url, base) {
|
|
17498
|
+
let candidate;
|
|
17499
|
+
let requested;
|
|
17471
17500
|
try {
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
17475
|
-
});
|
|
17501
|
+
candidate = new URL(url);
|
|
17502
|
+
requested = new URL(base);
|
|
17476
17503
|
} catch {
|
|
17477
|
-
return
|
|
17504
|
+
return false;
|
|
17478
17505
|
}
|
|
17479
|
-
if (
|
|
17480
|
-
|
|
17481
|
-
if (
|
|
17506
|
+
if (candidate.origin !== requested.origin || candidate.username !== "" || candidate.password !== "" || candidate.hash !== "")
|
|
17507
|
+
return false;
|
|
17508
|
+
if (candidate.pathname === requested.pathname) return true;
|
|
17509
|
+
const requestedParts = requested.pathname.split("/").filter(Boolean);
|
|
17510
|
+
const candidateParts = candidate.pathname.split("/").filter(Boolean);
|
|
17511
|
+
const resourcePath = requestedParts.slice(3);
|
|
17512
|
+
return requestedParts.length >= 4 && requestedParts[0] === "repos" && candidateParts.length === resourcePath.length + 2 && candidateParts[0] === "repositories" && /^\d+$/.test(candidateParts[1] ?? "") && candidateParts.slice(2).every((part, index) => part === resourcePath[index]);
|
|
17513
|
+
}
|
|
17514
|
+
async function fetchGithubListPages(base, token2) {
|
|
17515
|
+
const rows = [];
|
|
17516
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17517
|
+
let url = `${base}?per_page=100`;
|
|
17518
|
+
while (url !== void 0) {
|
|
17519
|
+
if (!isGithubListPage(url, base) || seen.has(url)) return void 0;
|
|
17520
|
+
seen.add(url);
|
|
17521
|
+
let res;
|
|
17522
|
+
try {
|
|
17523
|
+
res = await fetch(url, {
|
|
17524
|
+
headers: apiHeaders(token2),
|
|
17525
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
17526
|
+
});
|
|
17527
|
+
} catch {
|
|
17528
|
+
return void 0;
|
|
17529
|
+
}
|
|
17530
|
+
if (!res.ok) return void 0;
|
|
17531
|
+
const page2 = await res.json().catch(() => null);
|
|
17532
|
+
if (!Array.isArray(page2)) return void 0;
|
|
17533
|
+
rows.push(...page2);
|
|
17534
|
+
url = nextGithubPage(res.headers.get("link"));
|
|
17535
|
+
}
|
|
17536
|
+
return rows;
|
|
17537
|
+
}
|
|
17538
|
+
async function fetchPullRequestComments(pr) {
|
|
17539
|
+
const token2 = await resolveGithubToken().catch(() => null);
|
|
17540
|
+
const base = `https://api.github.com/repos/${pr.repo}/issues/${pr.number}/comments`;
|
|
17541
|
+
const rows = await fetchGithubListPages(base, token2);
|
|
17542
|
+
if (rows === void 0) return void 0;
|
|
17543
|
+
return rows.flatMap((raw) => {
|
|
17544
|
+
const row2 = raw;
|
|
17545
|
+
if (typeof row2.id !== "number" || typeof row2.user?.login !== "string" || typeof row2.user.type !== "string" || typeof row2.created_at !== "string" || typeof row2.body !== "string")
|
|
17546
|
+
return [];
|
|
17547
|
+
return [
|
|
17548
|
+
{
|
|
17549
|
+
id: row2.id,
|
|
17550
|
+
author: {
|
|
17551
|
+
login: row2.user.login,
|
|
17552
|
+
...typeof row2.user.id === "number" ? { id: row2.user.id } : {},
|
|
17553
|
+
type: row2.user.type
|
|
17554
|
+
},
|
|
17555
|
+
createdAt: row2.created_at,
|
|
17556
|
+
body: row2.body
|
|
17557
|
+
}
|
|
17558
|
+
];
|
|
17559
|
+
});
|
|
17560
|
+
}
|
|
17561
|
+
async function fetchPullRequestReviews(pr) {
|
|
17562
|
+
const token2 = await resolveGithubToken().catch(() => null);
|
|
17563
|
+
const base = `https://api.github.com/repos/${pr.repo}/pulls/${pr.number}/reviews`;
|
|
17564
|
+
const rows = await fetchGithubListPages(base, token2);
|
|
17565
|
+
if (rows === void 0) return void 0;
|
|
17482
17566
|
return rows.flatMap((r) => {
|
|
17483
17567
|
const row2 = r;
|
|
17484
17568
|
if (typeof row2.state !== "string") return [];
|
|
@@ -17492,6 +17576,7 @@ async function fetchPullRequestReviews(pr) {
|
|
|
17492
17576
|
} : {},
|
|
17493
17577
|
state: row2.state,
|
|
17494
17578
|
...typeof row2.commit_id === "string" ? { commitId: row2.commit_id } : {},
|
|
17579
|
+
...typeof row2.submitted_at === "string" ? { submittedAt: row2.submitted_at } : {},
|
|
17495
17580
|
body: typeof row2.body === "string" ? row2.body : ""
|
|
17496
17581
|
}
|
|
17497
17582
|
];
|
|
@@ -19957,11 +20042,11 @@ function resolveRange(daysParam, now = new Date(systemClock())) {
|
|
|
19957
20042
|
function buildBillerTieOuts(billers, invoices, cells, range) {
|
|
19958
20043
|
return billers.map(({ name, invoice }) => {
|
|
19959
20044
|
const attributed2 = /* @__PURE__ */ new Map();
|
|
19960
|
-
for (const
|
|
19961
|
-
if (
|
|
19962
|
-
for (const [ref, m] of Object.entries(
|
|
20045
|
+
for (const cell3 of cells) {
|
|
20046
|
+
if (cell3.day < range.from || cell3.day > range.to) continue;
|
|
20047
|
+
for (const [ref, m] of Object.entries(cell3.usage.byModel)) {
|
|
19963
20048
|
if (billerOfRef(ref) !== name) continue;
|
|
19964
|
-
const day2 = attributed2.get(
|
|
20049
|
+
const day2 = attributed2.get(cell3.day) ?? { usd: 0, fee: 0, hasFee: false, unpricedTokens: 0 };
|
|
19965
20050
|
if (typeof m.usd === "number") {
|
|
19966
20051
|
day2.usd += m.usd;
|
|
19967
20052
|
if (m.feeUsd !== void 0) {
|
|
@@ -19971,7 +20056,7 @@ function buildBillerTieOuts(billers, invoices, cells, range) {
|
|
|
19971
20056
|
} else {
|
|
19972
20057
|
day2.unpricedTokens += m.inputTokens + m.outputTokens + m.cacheReadTokens + m.cacheWriteTokens;
|
|
19973
20058
|
}
|
|
19974
|
-
attributed2.set(
|
|
20059
|
+
attributed2.set(cell3.day, day2);
|
|
19975
20060
|
}
|
|
19976
20061
|
}
|
|
19977
20062
|
const invoiced = /* @__PURE__ */ new Map();
|
|
@@ -35281,7 +35366,7 @@ var init_reply = __esm({
|
|
|
35281
35366
|
repo_unverified: (p) => p.via === "github" ? `\u26A0\uFE0F I couldn't verify \`${p.slug}\` against GitHub \u2014 it didn't answer \u2014 so I did not start ${aRun(p.agent)} rather than guess which repository you meant. Try again in a minute.` : `\u26A0\uFE0F I couldn't verify that \`${p.slug}\` is an onboarded repo \u2014 the resident registry didn't answer \u2014 so I did not start a *${p.agent}* run rather than guess which repo you meant. Try again in a minute, or name the repository by URL (https://github.com/${p.slug}) to run in a cold per-thread sandbox.`,
|
|
35282
35367
|
repo_not_onboarded: (p) => `\u{1F4E6} \`${p.slug}\` is not onboarded as a resident, so I did not start a *${p.agent}* run for it. ${p.onboardHint} for a warm, deps-ready environment, or name the repository by URL (https://github.com/${p.slug}) to run in a cold per-thread sandbox.`,
|
|
35283
35368
|
repo_access: (p) => `\u{1F6AB} You're not on the allowlist for the \`${p.repo}\` repo environment. Ask ${p.adminsHint} for access.`,
|
|
35284
|
-
which_branch: (p) => `\u{1F33F} Which branch of \`${p.repo}\` should this thread work on? No branch is bound yet
|
|
35369
|
+
which_branch: (p) => `\u{1F33F} Which branch of \`${p.repo}\` should this thread work on? No branch is bound yet; a branch token such as \`branch:main\` will bind it.`,
|
|
35285
35370
|
ship_thread_live: () => "\u{1F6AB} A pipeline is already running in this thread \u2014 one pipeline per thread. Follow the one in flight here, or start this one in a thread of its own.",
|
|
35286
35371
|
pipeline_thread_owned: (p) => `\u{1F6A6} This thread belongs to the live *${p.agent}* pipeline runner \u2014 nothing runs beside it here. ` + (p.units.length > 0 ? `Reply in the unit's own thread instead: ${p.units.map((u) => `${u.unit}${u.threadKey !== void 0 ? ` (\`${u.threadKey}\`)` : ""}`).join(", ")}.` : "Reply in the unit's own thread instead."),
|
|
35287
35372
|
ship_budget: (p) => `\u{1F6AB} Ship cannot start under a ${p.maxMinutes}-minute budget: the loop it allows (${p.maxRounds} review rounds) needs ${p.need} minutes \u2014 ${p.provision} to provision, the coding child's ${p.coding}, and the reserve for the rounds after it at their floors. Widen the budget or the boundary that clipped it, or run \`agent:coding\` for a single pass without the review loop.`
|
|
@@ -35307,6 +35392,10 @@ function slugOf(token2) {
|
|
|
35307
35392
|
if (!OWNER_RE.test(owner) || !NAME_RE.test(name) || /^\.+$/.test(name)) return void 0;
|
|
35308
35393
|
return `${owner}/${name}`.toLowerCase();
|
|
35309
35394
|
}
|
|
35395
|
+
function headBranchRef(text) {
|
|
35396
|
+
const match = /^\s*(?:(?:<@[^>\s]+>|[a-z]+:\S+)\s+)*(?:in\s+\S+\s+)?on\s+(?:the\s+)?branch\s+(\S+?)\s*:/i.exec(text);
|
|
35397
|
+
return match ? validRef(stripPunct(match[1])) : void 0;
|
|
35398
|
+
}
|
|
35310
35399
|
function extractSignals(rawText) {
|
|
35311
35400
|
const text = unwrapSlack(rawText);
|
|
35312
35401
|
const out = {};
|
|
@@ -35315,8 +35404,9 @@ function extractSignals(rawText) {
|
|
|
35315
35404
|
const slug = slugOf(`${stripPunct(prUrl3[1])}/${stripPunct(prUrl3[2])}`);
|
|
35316
35405
|
if (slug) out.pr = { repo: slug, number: Number(prUrl3[3]) };
|
|
35317
35406
|
}
|
|
35318
|
-
|
|
35319
|
-
|
|
35407
|
+
out.ref = headBranchRef(text);
|
|
35408
|
+
const branchToken = /(?:^|\s)branch:(\S+)/i.exec(text);
|
|
35409
|
+
if (!out.ref && branchToken) out.ref = validRef(stripPunct(branchToken[1]));
|
|
35320
35410
|
const treeUrl = /https?:\/\/(?:www\.)?github\.com\/([^/\s]+)\/([^/\s#?]+)\/tree\/([^\s?#]+)/i.exec(text);
|
|
35321
35411
|
if (treeUrl) {
|
|
35322
35412
|
const slug = slugOf(`${stripPunct(treeUrl[1])}/${stripPunct(treeUrl[2])}`);
|
|
@@ -35360,9 +35450,7 @@ function extractSignals(rawText) {
|
|
|
35360
35450
|
out.addressed = { name: t.toLowerCase() };
|
|
35361
35451
|
}
|
|
35362
35452
|
if (prev === "on") {
|
|
35363
|
-
if (!out.
|
|
35364
|
-
else if (!out.onSlug && slugOf(t) && validRef(t)) out.onSlug = t;
|
|
35365
|
-
else if (!out.ref && t.includes("/") && !slugOf(t)) out.ref = validRef(t);
|
|
35453
|
+
if (!out.onSlug && slugOf(t) && validRef(t)) out.onSlug = t;
|
|
35366
35454
|
continue;
|
|
35367
35455
|
}
|
|
35368
35456
|
const prShort = /^([^/#\s]+)\/([^/#\s]+)#(\d+)$/.exec(t);
|
|
@@ -35465,13 +35553,9 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
|
|
|
35465
35553
|
if (!repo && s.repo && await vet(s.repo)) repo = s.repo;
|
|
35466
35554
|
let ref = s.ref;
|
|
35467
35555
|
let refFromPr = false;
|
|
35468
|
-
if (s.onSlug) {
|
|
35469
|
-
|
|
35470
|
-
|
|
35471
|
-
} else if (!repo) {
|
|
35472
|
-
const cand = slugOf(s.onSlug);
|
|
35473
|
-
if (cand && await vet(cand)) repo = cand;
|
|
35474
|
-
}
|
|
35556
|
+
if (s.onSlug && !repo) {
|
|
35557
|
+
const cand = slugOf(s.onSlug);
|
|
35558
|
+
if (cand && await vet(cand)) repo = cand;
|
|
35475
35559
|
}
|
|
35476
35560
|
if (repo) {
|
|
35477
35561
|
rejected = void 0;
|
|
@@ -35688,7 +35772,7 @@ function prFacts(title, body) {
|
|
|
35688
35772
|
if (last >= 55296 && last <= 56319) end--;
|
|
35689
35773
|
return { title, body: body.slice(0, end), truncated: true };
|
|
35690
35774
|
}
|
|
35691
|
-
var PR_BODY_CAP, OWNER_RE, NAME_RE,
|
|
35775
|
+
var PR_BODY_CAP, OWNER_RE, NAME_RE, DETERMINERS, CODE_SPAN, threadSignalsCache, COMPARE_FILES_CAP;
|
|
35692
35776
|
var init_repoContext = __esm({
|
|
35693
35777
|
"../../src/core/repoContext.ts"() {
|
|
35694
35778
|
"use strict";
|
|
@@ -35699,7 +35783,6 @@ var init_repoContext = __esm({
|
|
|
35699
35783
|
PR_BODY_CAP = 65536;
|
|
35700
35784
|
OWNER_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
|
|
35701
35785
|
NAME_RE = /^[A-Za-z0-9._-]{1,100}$/;
|
|
35702
|
-
WELL_KNOWN_REFS = /* @__PURE__ */ new Set(["main", "master", "develop", "trunk"]);
|
|
35703
35786
|
DETERMINERS = /* @__PURE__ */ new Set(["the", "a", "an", "our", "my", "this"]);
|
|
35704
35787
|
CODE_SPAN = /```[\s\S]*?```|`[^`\n]*`/g;
|
|
35705
35788
|
threadSignalsCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -40661,8 +40744,8 @@ async function executeOperatorDecision(deps, ctx) {
|
|
|
40661
40744
|
await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
|
|
40662
40745
|
return answered;
|
|
40663
40746
|
}
|
|
40664
|
-
const
|
|
40665
|
-
const text =
|
|
40747
|
+
const cell3 = decideExecution({ kind: "run_command", confirm: "at_or_above", mintable: false }, surface);
|
|
40748
|
+
const text = cell3.cell === "refuse" && cell3.names === "typed_form" ? renderHandBackLine(bind.line) : mint !== void 0 && mint.kind === "unshowable" ? UNSHOWABLE_LINE : STORE_UNREACHABLE_LINE;
|
|
40666
40749
|
await io.reply(`${verbose ? `${receipt}
|
|
40667
40750
|
` : ""}${text}`);
|
|
40668
40751
|
await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
|
|
@@ -44108,7 +44191,7 @@ function encodeGithubPathSegments(path) {
|
|
|
44108
44191
|
function anchorUrl(ctx, a) {
|
|
44109
44192
|
return `https://github.com/${ctx.repo}/blob/${ctx.headSha}/${encodeGithubPathSegments(a.path)}#L${a.from}-L${a.to}`;
|
|
44110
44193
|
}
|
|
44111
|
-
function
|
|
44194
|
+
function cell2(s) {
|
|
44112
44195
|
return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ");
|
|
44113
44196
|
}
|
|
44114
44197
|
function linkLabel(s) {
|
|
@@ -44146,7 +44229,7 @@ function renderPrDescriptionMarkdown(desc, ctx) {
|
|
|
44146
44229
|
...details(FOLD_SUMMARIES.validation(desc.validation.criteria.length), [
|
|
44147
44230
|
"| Criterion | Proof |",
|
|
44148
44231
|
"|---|---|",
|
|
44149
|
-
...desc.validation.criteria.map((c) => `| ${
|
|
44232
|
+
...desc.validation.criteria.map((c) => `| ${cell2(c.criterion)} | ${cell2(c.proof)} |`)
|
|
44150
44233
|
])
|
|
44151
44234
|
);
|
|
44152
44235
|
const agentLines = [
|
|
@@ -50645,6 +50728,11 @@ async function salvageBudgetPush(executor, opts, span) {
|
|
|
50645
50728
|
nothing: `the coding child ended with nothing to preserve: the tree is clean and \`${opts.branch}\` holds no unpushed commits`,
|
|
50646
50729
|
pushedLead: "the coding child ended with interrupted work",
|
|
50647
50730
|
failedLead: `the interrupted-work push to \`${opts.branch}\` failed`
|
|
50731
|
+
} : opts.cue === "completion" ? {
|
|
50732
|
+
commit: "wip: preserve unfinished coding work \u2014 work in progress, not reviewed",
|
|
50733
|
+
nothing: `the coding child ended with nothing to preserve: the tree is clean and \`${opts.branch}\` holds no unpushed commits`,
|
|
50734
|
+
pushedLead: "the coding child ended with unfinished work",
|
|
50735
|
+
failedLead: `the unfinished-work push to \`${opts.branch}\` failed`
|
|
50648
50736
|
} : {
|
|
50649
50737
|
commit: "wip: committed at the budget wind-down \u2014 work in progress, not reviewed",
|
|
50650
50738
|
nothing: nothingToSalvageNote(opts.branch),
|
|
@@ -50653,7 +50741,7 @@ async function salvageBudgetPush(executor, opts, span) {
|
|
|
50653
50741
|
};
|
|
50654
50742
|
try {
|
|
50655
50743
|
const dirty = (await run2("git status --porcelain")).trim() !== "";
|
|
50656
|
-
const endingCheckpoint = opts.cue !== "compaction";
|
|
50744
|
+
const endingCheckpoint = opts.cue !== "compaction" && opts.cue !== "completion";
|
|
50657
50745
|
if (dirty) {
|
|
50658
50746
|
await run2("git add -A");
|
|
50659
50747
|
await run2(`git commit -m ${shellQuote(words.commit)}`);
|
|
@@ -51618,8 +51706,6 @@ async function runLoop(deps, ctx) {
|
|
|
51618
51706
|
const preserveCodingChildWork = async (cue) => {
|
|
51619
51707
|
if (workSalvageAttempted || !isCodingPrRun || coordinator === void 0) return;
|
|
51620
51708
|
if (!workspaceObserved) await observeWorkspaceNow();
|
|
51621
|
-
workSalvageAttempted = true;
|
|
51622
|
-
if (cue === "ending") endingSalvageAttempted = true;
|
|
51623
51709
|
const target = salvageTargetOf({
|
|
51624
51710
|
pushedBranch: pushes.branch(),
|
|
51625
51711
|
checkedOut: observedCheckedOut,
|
|
@@ -51629,6 +51715,10 @@ async function runLoop(deps, ctx) {
|
|
|
51629
51715
|
cue === "budget" ? "run.budget_salvage" : "run.work_salvage",
|
|
51630
51716
|
(span) => salvageBudgetPush(executor, { branch: target.branch, cue }, span)
|
|
51631
51717
|
);
|
|
51718
|
+
const cleanCompletion = cue === "completion" && !salvaged.pushed && (/ended with nothing to preserve/i.test(salvaged.summary) || "skipped" in target && (observedUncommitted ?? 0) === 0 && (observedUnpushed ?? 0) === 0);
|
|
51719
|
+
if (cleanCompletion) return;
|
|
51720
|
+
workSalvageAttempted = true;
|
|
51721
|
+
if (cue === "ending" || cue === "completion") endingSalvageAttempted = true;
|
|
51632
51722
|
onEvent({
|
|
51633
51723
|
type: "run_note",
|
|
51634
51724
|
kind: cue === "budget" ? "budget_salvage" : "work_salvage",
|
|
@@ -52068,6 +52158,7 @@ async function runLoop(deps, ctx) {
|
|
|
52068
52158
|
if (endingNeedsPreservation && run2.control.requested === "hard") await endHarness();
|
|
52069
52159
|
if (isCodingPrRun && !tailSkipped()) await observeWorkspaceNow();
|
|
52070
52160
|
if (endingNeedsPreservation) await preserveCodingChildWork(budgetEnded ? "budget" : "ending");
|
|
52161
|
+
else if (isCodingPrRun && coordinator !== void 0 && !tailSkipped()) await preserveCodingChildWork("completion");
|
|
52071
52162
|
let descriptionTurnRan = false;
|
|
52072
52163
|
if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted && prDescription === void 0) {
|
|
52073
52164
|
const turnTarget = await descriptionTurnTarget({
|
|
@@ -52103,6 +52194,7 @@ async function runLoop(deps, ctx) {
|
|
|
52103
52194
|
if (!run2.control.hardSignal.aborted) await observeWorkspaceNow();
|
|
52104
52195
|
}
|
|
52105
52196
|
}
|
|
52197
|
+
if (descriptionTurnRan) await preserveCodingChildWork("completion");
|
|
52106
52198
|
await endHarness();
|
|
52107
52199
|
const leftBehind = isCodingPrRun && !tailSkipped() && !workSalvageAttempted ? workLeftBehindOf({ uncommittedChanges: observedUncommitted, unpushedCommits: observedUnpushed }) : void 0;
|
|
52108
52200
|
const endingFacts = () => {
|
|
@@ -52448,14 +52540,6 @@ function shipUnitText(requestText, repo) {
|
|
|
52448
52540
|
t = t.replace(new RegExp(`^\\s*in\\s+${escapeRegExp3(repo)}\\s*:?`, "i"), " ");
|
|
52449
52541
|
return t.replace(/\s+/g, " ").trim();
|
|
52450
52542
|
}
|
|
52451
|
-
function refNamedExplicitly(requestText, ref) {
|
|
52452
|
-
const text = requestText.replace(SLACK_LINK, " $1 ");
|
|
52453
|
-
const strip = (s) => s.replace(/^[("'`<[{*]+/, "").replace(/[)"'`>\]}.,;:!?*]+$/, "");
|
|
52454
|
-
const kw = /(?:^|\s)on\s+(?:the\s+)?branch\s+(\S+)/i.exec(text) ?? /(?:^|\s)branch[:=](\S+)/i.exec(text);
|
|
52455
|
-
if (kw && strip(kw[1]) === ref) return true;
|
|
52456
|
-
const treeUrl = /https?:\/\/(?:www\.)?github\.com\/[^/\s]+\/[^/\s#?]+\/tree\/([^\s?#]+)/i.exec(text);
|
|
52457
|
-
return treeUrl !== void 0 && treeUrl !== null && strip(treeUrl[1]) === ref;
|
|
52458
|
-
}
|
|
52459
52543
|
function noRepoGuess(input) {
|
|
52460
52544
|
const candidates = input.repoCandidates ?? [];
|
|
52461
52545
|
if (candidates.length === 0) return void 0;
|
|
@@ -52549,18 +52633,9 @@ async function shipPreflight(input) {
|
|
|
52549
52633
|
);
|
|
52550
52634
|
}
|
|
52551
52635
|
const ownBase = facts2.baseRef ?? repoCtx.baseRef;
|
|
52552
|
-
|
|
52553
|
-
|
|
52554
|
-
|
|
52555
|
-
return refuse2(
|
|
52556
|
-
"ship_preflight_base_missing",
|
|
52557
|
-
"base ref missing",
|
|
52558
|
-
"not started (base ref missing)",
|
|
52559
|
-
`\u{1F6AB} ${where}'s base branch \`${ownBase}\` does not exist on \`${repo}\` \u2014 refusing fail-closed. Retarget the pull request at an existing branch first.`
|
|
52560
|
-
);
|
|
52561
|
-
}
|
|
52562
|
-
}
|
|
52563
|
-
const base = resolveBaseRef([ownBase], info?.defaultBranch);
|
|
52636
|
+
const ownBaseExists = ownBase !== void 0 && input.refExists ? await input.refExists(repo, ownBase).catch(() => void 0) : void 0;
|
|
52637
|
+
const baseFallback = ownBase !== void 0 && ownBaseExists === false ? { requested: ownBase } : void 0;
|
|
52638
|
+
const base = resolveBaseRef([ownBaseExists === false ? void 0 : ownBase], info?.defaultBranch);
|
|
52564
52639
|
const autoMerge = facts2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: facts2.autoMergeEnabled } : {};
|
|
52565
52640
|
if (task) {
|
|
52566
52641
|
return {
|
|
@@ -52570,6 +52645,7 @@ async function shipPreflight(input) {
|
|
|
52570
52645
|
branch: branch2,
|
|
52571
52646
|
base,
|
|
52572
52647
|
adopt: { pr: repoCtx.pr, ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {} },
|
|
52648
|
+
...baseFallback !== void 0 ? { baseFallback } : {},
|
|
52573
52649
|
...autoMerge
|
|
52574
52650
|
}
|
|
52575
52651
|
};
|
|
@@ -52585,6 +52661,7 @@ async function shipPreflight(input) {
|
|
|
52585
52661
|
headSha: facts2.headSha ?? repoCtx.headSha,
|
|
52586
52662
|
...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {}
|
|
52587
52663
|
},
|
|
52664
|
+
...baseFallback !== void 0 ? { baseFallback } : {},
|
|
52588
52665
|
...autoMerge
|
|
52589
52666
|
}
|
|
52590
52667
|
};
|
|
@@ -52610,20 +52687,11 @@ async function shipPreflight(input) {
|
|
|
52610
52687
|
const ref = repoCtx.ref && !repoCtx.refFromPr && !isUnitBranch(repoCtx.ref) && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
|
|
52611
52688
|
if (ref !== void 0 && input.refExists) {
|
|
52612
52689
|
const exists = await input.refExists(repo, ref).catch(() => void 0);
|
|
52613
|
-
if (exists === false)
|
|
52614
|
-
if (refNamedExplicitly(input.requestText, ref)) {
|
|
52615
|
-
return refuse2(
|
|
52616
|
-
"ship_preflight_base_missing",
|
|
52617
|
-
"base ref missing",
|
|
52618
|
-
"not started (base ref missing)",
|
|
52619
|
-
`\u{1F6AB} The branch \`${ref}\` does not exist on \`${repo}\` \u2014 nothing was started. Name an existing branch, or drop it to run on the default branch.`
|
|
52620
|
-
);
|
|
52621
|
-
}
|
|
52690
|
+
if (exists === false)
|
|
52622
52691
|
return {
|
|
52623
52692
|
ok: true,
|
|
52624
52693
|
entry: { repo, base: info?.defaultBranch, baseFallback: { requested: ref } }
|
|
52625
52694
|
};
|
|
52626
|
-
}
|
|
52627
52695
|
}
|
|
52628
52696
|
return {
|
|
52629
52697
|
ok: true,
|
|
@@ -54063,7 +54131,7 @@ async function answerUnitOwnedThread(deps, ctx) {
|
|
|
54063
54131
|
...msg.userName !== void 0 ? { senderName: msg.userName } : {},
|
|
54064
54132
|
text: ctx.text,
|
|
54065
54133
|
...attachments.length > 0 ? { attachments } : {},
|
|
54066
|
-
mode: "steer",
|
|
54134
|
+
mode: owner.unit.idle !== void 0 ? "wake" : "steer",
|
|
54067
54135
|
at
|
|
54068
54136
|
}).catch((err2) => {
|
|
54069
54137
|
console.warn(
|
|
@@ -54361,7 +54429,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
54361
54429
|
({ sticky, resolved, agentSource } = settled);
|
|
54362
54430
|
agentSource = "sticky";
|
|
54363
54431
|
}
|
|
54364
|
-
if (owner.kind === "unit" && directives.agent === void 0) {
|
|
54432
|
+
if (owner.kind === "unit" && (directives.agent === void 0 || owner.unit.idle?.humanGate !== void 0)) {
|
|
54365
54433
|
if ((await authorizeAgent(deps, { msg, io, refuse: refuse3, agentName: "coding" })).kind === "refused") {
|
|
54366
54434
|
await recordPendingOperator();
|
|
54367
54435
|
return ended;
|
|
@@ -68876,14 +68944,14 @@ var init_resumeLaunch = __esm({
|
|
|
68876
68944
|
});
|
|
68877
68945
|
|
|
68878
68946
|
// ../../src/core/runnerOwnership.ts
|
|
68879
|
-
async function
|
|
68880
|
-
const owned = /* @__PURE__ */ new
|
|
68947
|
+
async function recoverRunnerOwnedPullOwners(instanceIds, instances) {
|
|
68948
|
+
const owned = /* @__PURE__ */ new Map();
|
|
68881
68949
|
for (const instanceId of new Set(instanceIds)) {
|
|
68882
68950
|
const instance = await instances.get(instanceId);
|
|
68883
68951
|
if (instance === null) continue;
|
|
68884
68952
|
for (const unit of await instances.listUnits(instanceId)) {
|
|
68885
68953
|
if (unit.pr !== void 0 && unit.ending === void 0)
|
|
68886
|
-
owned.
|
|
68954
|
+
owned.set(runnerOwnedPullKey(instance.repo, unit.pr.number), { instanceId, unit: unit.unit });
|
|
68887
68955
|
}
|
|
68888
68956
|
}
|
|
68889
68957
|
return owned;
|
|
@@ -68896,24 +68964,35 @@ var init_runnerOwnership = __esm({
|
|
|
68896
68964
|
RunnerOwnershipFence = class {
|
|
68897
68965
|
claimed = /* @__PURE__ */ new Set();
|
|
68898
68966
|
recovered = /* @__PURE__ */ new Set();
|
|
68967
|
+
claimedOwners = /* @__PURE__ */ new Map();
|
|
68968
|
+
recoveredOwners = /* @__PURE__ */ new Map();
|
|
68899
68969
|
localRecoveredInstances = /* @__PURE__ */ new Set();
|
|
68900
68970
|
recoveryComplete;
|
|
68901
68971
|
constructor(requiresRecovery) {
|
|
68902
68972
|
this.recoveryComplete = !requiresRecovery;
|
|
68903
68973
|
}
|
|
68904
|
-
claim(repo, prNumber) {
|
|
68905
|
-
|
|
68974
|
+
claim(repo, prNumber, owner) {
|
|
68975
|
+
const key = runnerOwnedPullKey(repo, prNumber);
|
|
68976
|
+
this.claimed.add(key);
|
|
68977
|
+
if (owner !== void 0) this.claimedOwners.set(key, owner);
|
|
68906
68978
|
}
|
|
68907
68979
|
release(repo, prNumber) {
|
|
68908
68980
|
const key = runnerOwnedPullKey(repo, prNumber);
|
|
68909
68981
|
this.claimed.delete(key);
|
|
68910
68982
|
this.recovered.delete(key);
|
|
68983
|
+
this.claimedOwners.delete(key);
|
|
68984
|
+
this.recoveredOwners.delete(key);
|
|
68911
68985
|
}
|
|
68912
68986
|
owns(repo, prNumber) {
|
|
68913
68987
|
if (!this.recoveryComplete) throw new Error("runner ownership recovery is still in progress");
|
|
68914
68988
|
const key = runnerOwnedPullKey(repo, prNumber);
|
|
68915
68989
|
return this.claimed.has(key) || this.recovered.has(key);
|
|
68916
68990
|
}
|
|
68991
|
+
owner(repo, prNumber) {
|
|
68992
|
+
if (!this.recoveryComplete) throw new Error("runner ownership recovery is still in progress");
|
|
68993
|
+
const key = runnerOwnedPullKey(repo, prNumber);
|
|
68994
|
+
return this.claimedOwners.get(key) ?? this.recoveredOwners.get(key);
|
|
68995
|
+
}
|
|
68917
68996
|
async recover(outcome, instances) {
|
|
68918
68997
|
for (const run2 of outcome.resumable) {
|
|
68919
68998
|
if (run2.kind === "rehost" && run2.hosting !== void 0) this.localRecoveredInstances.add(run2.hosting.instanceId);
|
|
@@ -68925,9 +69004,13 @@ var init_runnerOwnership = __esm({
|
|
|
68925
69004
|
for (const run2 of outcome.liveElsewhere) {
|
|
68926
69005
|
if (run2.hosting !== void 0) activeRunnerInstances.add(run2.hosting.instanceId);
|
|
68927
69006
|
}
|
|
68928
|
-
const recovered = await
|
|
69007
|
+
const recovered = await recoverRunnerOwnedPullOwners(activeRunnerInstances, instances);
|
|
68929
69008
|
this.recovered.clear();
|
|
68930
|
-
|
|
69009
|
+
this.recoveredOwners.clear();
|
|
69010
|
+
for (const [key, owner] of recovered) {
|
|
69011
|
+
this.recovered.add(key);
|
|
69012
|
+
this.recoveredOwners.set(key, owner);
|
|
69013
|
+
}
|
|
68931
69014
|
this.recoveryComplete = true;
|
|
68932
69015
|
}
|
|
68933
69016
|
};
|
|
@@ -69400,10 +69483,14 @@ ${cont.texts.join("\n\n")}`);
|
|
|
69400
69483
|
}
|
|
69401
69484
|
function findingsRequest(input) {
|
|
69402
69485
|
const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
|
|
69486
|
+
const answers = input.answers !== void 0 && input.answers.length > 0 ? `
|
|
69487
|
+
|
|
69488
|
+
A person answered the human-gated finding. Treat the finding and this answer together as the fix brief:
|
|
69489
|
+
${input.answers.join("\n\n")}` : "";
|
|
69403
69490
|
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note), squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
|
|
69404
69491
|
|
|
69405
69492
|
Findings:
|
|
69406
|
-
${findings}
|
|
69493
|
+
${findings}${answers}
|
|
69407
69494
|
|
|
69408
69495
|
Review:
|
|
69409
69496
|
${input.review}`;
|
|
@@ -69422,7 +69509,8 @@ ${task}` : task;
|
|
|
69422
69509
|
case "review": {
|
|
69423
69510
|
let prior;
|
|
69424
69511
|
if (brief.prior !== void 0) {
|
|
69425
|
-
const
|
|
69512
|
+
const reviewFindings = brief.prior.findings ?? (brief.prior.reviewRunId !== void 0 ? (await facts(readers, brief.prior.reviewRunId)).findings ?? [] : []);
|
|
69513
|
+
const findings = [...reviewFindings, ...brief.checks ?? []];
|
|
69426
69514
|
const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
|
|
69427
69515
|
const { matched, dropped } = matchDispositions(findings, recorded);
|
|
69428
69516
|
prior = { findings, dispositions: matched, dropped };
|
|
@@ -69440,13 +69528,14 @@ ${task}` : task;
|
|
|
69440
69528
|
${turn}`, contract };
|
|
69441
69529
|
}
|
|
69442
69530
|
case "findings": {
|
|
69443
|
-
const review = await facts(readers, brief.reviewRunId);
|
|
69531
|
+
const review = brief.reviewRunId !== void 0 ? brief.findings !== void 0 ? await readers.readRunFacts(brief.reviewRunId) ?? {} : await facts(readers, brief.reviewRunId) : {};
|
|
69444
69532
|
return {
|
|
69445
69533
|
preset: "coding",
|
|
69446
69534
|
prompt: findingsRequest({
|
|
69447
69535
|
where,
|
|
69448
|
-
findings: [...review.findings ?? [], ...brief.checks ?? []],
|
|
69449
|
-
review: review.finalReply ?? ""
|
|
69536
|
+
findings: [...brief.findings ?? review.findings ?? [], ...brief.checks ?? []],
|
|
69537
|
+
review: review.finalReply ?? "",
|
|
69538
|
+
...brief.answers !== void 0 ? { answers: brief.answers } : {}
|
|
69450
69539
|
}),
|
|
69451
69540
|
ref: unit.branch
|
|
69452
69541
|
};
|
|
@@ -69920,6 +70009,10 @@ ${foldThreadEvents(folded)}` };
|
|
|
69920
70009
|
at
|
|
69921
70010
|
});
|
|
69922
70011
|
}
|
|
70012
|
+
function reviewAskedAtOf(events) {
|
|
70013
|
+
const posted = [...events ?? []].reverse().find((event) => event.type === "review_posted");
|
|
70014
|
+
return posted?.type === "review_posted" && typeof posted.at === "number" ? posted.at : void 0;
|
|
70015
|
+
}
|
|
69923
70016
|
function finalReplyOf2(events) {
|
|
69924
70017
|
const last = [...events ?? []].reverse().find((e) => e.type === "answer");
|
|
69925
70018
|
return last && last.type === "answer" ? last.text : void 0;
|
|
@@ -69954,6 +70047,74 @@ async function reviewPostedAt(deps, pr, verdict, head) {
|
|
|
69954
70047
|
(r) => r.author?.login === self.login && (r.author.id === void 0 || r.author.id === self.id) && r.commitId !== void 0 && sameCommit(r.commitId.toLowerCase(), head) && r.body.startsWith(token2)
|
|
69955
70048
|
);
|
|
69956
70049
|
}
|
|
70050
|
+
function legacyPostedFinding(value, body) {
|
|
70051
|
+
if (isFindingShape(value)) return value;
|
|
70052
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
70053
|
+
const row2 = value;
|
|
70054
|
+
if (row2.title !== void 0 || typeof row2.id !== "string" || typeof row2.severity !== "string") return void 0;
|
|
70055
|
+
const prefix = `| ${row2.severity} | **${escapeMarkdownTableCell(row2.id)}** `;
|
|
70056
|
+
const header4 = "| Severity | Finding | Where |\n| --- | --- | --- |\n";
|
|
70057
|
+
const tableStart = body.indexOf(header4);
|
|
70058
|
+
if (tableStart < 0) return void 0;
|
|
70059
|
+
const table = body.slice(tableStart + header4.length).split("\n\n", 1)[0];
|
|
70060
|
+
const titles = table.split("\n").filter((line4) => line4.startsWith(prefix)).flatMap((line4) => {
|
|
70061
|
+
const tail = line4.slice(prefix.length);
|
|
70062
|
+
const boundary = tail.lastIndexOf(" | ");
|
|
70063
|
+
return boundary > 0 ? [unescapeMarkdownTableCell(tail.slice(0, boundary)).trim()] : [];
|
|
70064
|
+
}).filter(Boolean);
|
|
70065
|
+
if (titles.length !== 1) return void 0;
|
|
70066
|
+
const recovered = { ...row2, title: titles[0] };
|
|
70067
|
+
return isFindingShape(recovered) ? recovered : void 0;
|
|
70068
|
+
}
|
|
70069
|
+
async function postedHumanGateAnswer(deps, pr, requester, addressSeverity) {
|
|
70070
|
+
if (deps.fetchPrComments === void 0) return void 0;
|
|
70071
|
+
const [reviews, comments, self] = await Promise.all([
|
|
70072
|
+
deps.fetchPrReviews(pr).catch(() => void 0),
|
|
70073
|
+
deps.fetchPrComments(pr).catch(() => void 0),
|
|
70074
|
+
deps.selfIdentity().catch(() => void 0)
|
|
70075
|
+
]);
|
|
70076
|
+
if (reviews === void 0 || comments === void 0 || self === void 0) return void 0;
|
|
70077
|
+
const own = reviews.filter(
|
|
70078
|
+
(review) => review.author?.login === self.login && (review.author.id === void 0 || review.author.id === self.id) && review.submittedAt !== void 0 && Number.isFinite(Date.parse(review.submittedAt))
|
|
70079
|
+
).sort((a, b) => Date.parse(a.submittedAt) - Date.parse(b.submittedAt)).at(-1);
|
|
70080
|
+
if (own === void 0) return void 0;
|
|
70081
|
+
const marker = /<!-- switchboard:verdict (\{[^\n]*\}) -->/.exec(own.body);
|
|
70082
|
+
if (marker === null) return void 0;
|
|
70083
|
+
let payload;
|
|
70084
|
+
try {
|
|
70085
|
+
payload = JSON.parse(marker[1]);
|
|
70086
|
+
} catch {
|
|
70087
|
+
return void 0;
|
|
70088
|
+
}
|
|
70089
|
+
if (typeof payload !== "object" || payload === null) return void 0;
|
|
70090
|
+
const row2 = payload;
|
|
70091
|
+
if (row2.verdict !== "approve" && row2.verdict !== "request_changes") return void 0;
|
|
70092
|
+
if (!Array.isArray(row2.findings)) return void 0;
|
|
70093
|
+
const postedFindings = row2.findings.map((finding) => legacyPostedFinding(finding, own.body));
|
|
70094
|
+
if (!postedFindings.every((finding) => finding !== void 0)) return void 0;
|
|
70095
|
+
const findings = row2.verdict === "approve" ? findingsAtOrAbove(postedFindings, addressSeverity) : postedFindings;
|
|
70096
|
+
if (findings.length === 0 || !findings.every((finding) => finding.humanGated === true)) return void 0;
|
|
70097
|
+
const candidates = comments.filter(
|
|
70098
|
+
(comment) => comment.author.type === "User" && comment.body.trim() !== "" && Number.isFinite(Date.parse(comment.createdAt)) && Date.parse(comment.createdAt) >= githubSecondStart(Date.parse(own.submittedAt))
|
|
70099
|
+
).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
|
|
70100
|
+
let answer;
|
|
70101
|
+
for (const candidate of candidates) {
|
|
70102
|
+
const authorized = await deps.commenterAuthorized(requester, candidate.author).catch(() => false);
|
|
70103
|
+
if (authorized) {
|
|
70104
|
+
answer = candidate;
|
|
70105
|
+
break;
|
|
70106
|
+
}
|
|
70107
|
+
}
|
|
70108
|
+
if (answer === void 0) return void 0;
|
|
70109
|
+
return {
|
|
70110
|
+
round: 1,
|
|
70111
|
+
findings,
|
|
70112
|
+
verdict: row2.verdict,
|
|
70113
|
+
answer: answer.body.trim(),
|
|
70114
|
+
author: answer.author.login,
|
|
70115
|
+
commentId: String(answer.id)
|
|
70116
|
+
};
|
|
70117
|
+
}
|
|
69957
70118
|
async function reviewPostedAtPatiently(deps, pr, verdict, head) {
|
|
69958
70119
|
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve13) => setTimeout(resolve13, ms)));
|
|
69959
70120
|
let answer;
|
|
@@ -69986,6 +70147,7 @@ async function readRecord(body, deps) {
|
|
|
69986
70147
|
const full = await deps.runs.getRun(body.runId, { include: "messages" });
|
|
69987
70148
|
const record2 = full.ok ? full.value : view;
|
|
69988
70149
|
const finalReply = finalReplyOf2(record2.events);
|
|
70150
|
+
const reviewAskedAt = reviewAskedAtOf(record2.events);
|
|
69989
70151
|
const pr = prOpenedOf(record2.events);
|
|
69990
70152
|
const instanceRow = await deps.instances.get(id.value);
|
|
69991
70153
|
if (view.status === "interrupted") {
|
|
@@ -70027,6 +70189,7 @@ async function readRecord(body, deps) {
|
|
|
70027
70189
|
...pr !== void 0 ? { pr } : {},
|
|
70028
70190
|
...record2.verdict !== void 0 ? { verdict: record2.verdict } : {},
|
|
70029
70191
|
...record2.reviewHead !== void 0 ? { reviewHead: record2.reviewHead } : {},
|
|
70192
|
+
...reviewAskedAt !== void 0 ? { reviewAskedAt } : {},
|
|
70030
70193
|
...posted !== void 0 ? { reviewPosted: posted.reviewPosted } : {},
|
|
70031
70194
|
...posted?.reviewPostReason !== void 0 ? { reviewPostReason: posted.reviewPostReason } : {},
|
|
70032
70195
|
...record2.dispositions !== void 0 ? { dispositions: record2.dispositions } : {},
|
|
@@ -70172,11 +70335,20 @@ async function prCheck(body, deps) {
|
|
|
70172
70335
|
const open2 = await deps.findOpenPrByHead(instance.repo, branch2);
|
|
70173
70336
|
if (open2) {
|
|
70174
70337
|
await remember({ number: open2.number, url: open2.htmlUrl });
|
|
70175
|
-
deps.runnerOwnership?.claim(instance.repo, open2.number
|
|
70338
|
+
deps.runnerOwnership?.claim(instance.repo, open2.number, {
|
|
70339
|
+
instanceId: instance.id,
|
|
70340
|
+
unit: unit.row?.unit ?? body.unit
|
|
70341
|
+
});
|
|
70176
70342
|
const entryFacts = entry ? await deps.fetchPrFacts({ repo: instance.repo, number: open2.number }).catch(() => void 0) : void 0;
|
|
70177
70343
|
const branchHead = entryFacts?.headSha;
|
|
70178
70344
|
const entryHead = branchHead ?? open2.headSha;
|
|
70179
70345
|
const approved = entry && entryHead !== void 0 ? await reviewPostedAt(deps, { repo: instance.repo, number: open2.number }, "approve", entryHead) : void 0;
|
|
70346
|
+
const humanGate = entry ? await postedHumanGateAnswer(
|
|
70347
|
+
deps,
|
|
70348
|
+
{ repo: instance.repo, number: open2.number },
|
|
70349
|
+
instance.userId,
|
|
70350
|
+
instance.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY
|
|
70351
|
+
) : void 0;
|
|
70180
70352
|
const entryChecks = entry && entryHead !== void 0 ? await deps.fetchCommitChecks(instance.repo, entryHead).catch(() => void 0) : void 0;
|
|
70181
70353
|
const checks = body.checks === true && open2.headSha !== void 0 ? await deps.fetchCommitChecks(instance.repo, open2.headSha).catch(() => void 0) : entryChecks;
|
|
70182
70354
|
const prRef = { repo: instance.repo, number: open2.number };
|
|
@@ -70192,6 +70364,7 @@ async function prCheck(body, deps) {
|
|
|
70192
70364
|
...open2.headSha !== void 0 ? { headSha: open2.headSha } : {},
|
|
70193
70365
|
...branchHead !== void 0 ? { branchHead } : {},
|
|
70194
70366
|
...approved !== void 0 ? { approved } : {},
|
|
70367
|
+
...humanGate !== void 0 ? { humanGate } : {},
|
|
70195
70368
|
// The pull request's own auto-merge fact (agent-ship item 9), so a
|
|
70196
70369
|
// merge_ready ending can name it at the approved head.
|
|
70197
70370
|
...open2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: open2.autoMergeEnabled } : {},
|
|
@@ -70551,6 +70724,7 @@ function parseIdle(ending, runId3, at) {
|
|
|
70551
70724
|
...typeof runId3 === "string" && RUN_ID_PATTERN.test(runId3) ? { runId: runId3 } : {},
|
|
70552
70725
|
spendUsd: typeof ending.spendUsd === "number" && Number.isFinite(ending.spendUsd) ? ending.spendUsd : null,
|
|
70553
70726
|
...isHandoffShape(ending.handoff) ? { handoff: ending.handoff } : {},
|
|
70727
|
+
...isHumanGatePending(ending.humanGate) ? { humanGate: ending.humanGate } : {},
|
|
70554
70728
|
wakes: 0
|
|
70555
70729
|
};
|
|
70556
70730
|
}
|
|
@@ -70590,20 +70764,37 @@ async function unitWake(body, deps) {
|
|
|
70590
70764
|
}
|
|
70591
70765
|
const events = await deps.instances.listEvents({ instanceId: instance.id, unit: row2.unit }, true);
|
|
70592
70766
|
const idle = row2.idle;
|
|
70767
|
+
const askedAt = idle.humanGate?.askedAt;
|
|
70768
|
+
const wakeEvents = askedAt !== void 0 ? events.filter(
|
|
70769
|
+
(event) => event.id?.startsWith("github:issue-comment:") ? event.at >= githubSecondStart(askedAt) : event.at > askedAt
|
|
70770
|
+
) : events;
|
|
70593
70771
|
const senderOf = (e) => e.senderName ?? e.sender;
|
|
70594
|
-
const senders = [...new Set(
|
|
70595
|
-
const requesterWoke =
|
|
70596
|
-
const stopperWoke =
|
|
70772
|
+
const senders = [...new Set(wakeEvents.map(senderOf))];
|
|
70773
|
+
const requesterWoke = wakeEvents.some((event) => event.sender === instance.userId);
|
|
70774
|
+
const stopperWoke = wakeEvents.some((event) => event.mode === "interrupt");
|
|
70597
70775
|
const grantFact = deps.shipGrantFor?.(instance) ?? {
|
|
70598
70776
|
grant: instance.grant ?? DEFAULT_GRANT,
|
|
70599
70777
|
source: instance.grantSource ?? "org"
|
|
70600
70778
|
};
|
|
70601
70779
|
const renewalsSpent = row2.segments?.length ?? 0;
|
|
70602
70780
|
let answer;
|
|
70603
|
-
if (
|
|
70781
|
+
if (wakeEvents.length === 0) {
|
|
70604
70782
|
answer = { kind: "answered", reply: "Nothing new was waiting for this unit." };
|
|
70605
70783
|
} else if (idle.wakes + 1 >= IDLE_WAKES_MAX) {
|
|
70606
70784
|
answer = { kind: "expired" };
|
|
70785
|
+
} else if (idle.humanGate !== void 0) {
|
|
70786
|
+
answer = {
|
|
70787
|
+
kind: "segment",
|
|
70788
|
+
index: row2.segments?.at(-1)?.index ?? 1,
|
|
70789
|
+
...idle.from !== void 0 ? { from: idle.from } : {},
|
|
70790
|
+
...idle.runId !== void 0 ? { runId: idle.runId } : {},
|
|
70791
|
+
spendUsd: idle.spendUsd,
|
|
70792
|
+
...idle.handoff !== void 0 ? { handoff: idle.handoff } : {},
|
|
70793
|
+
texts: wakeEvents.map(attributedText),
|
|
70794
|
+
senders,
|
|
70795
|
+
leaseMs: minutesToMs(instance.caps?.maxMinutes ?? resolveShipCaps(void 0).maxMinutes),
|
|
70796
|
+
humanGate: idle.humanGate
|
|
70797
|
+
};
|
|
70607
70798
|
} else {
|
|
70608
70799
|
const segmentStart = row2.segments?.at(-1)?.at ?? row2.startedAt ?? idle.at;
|
|
70609
70800
|
const leaseMs = Math.max(
|
|
@@ -70618,7 +70809,7 @@ async function unitWake(body, deps) {
|
|
|
70618
70809
|
...idle.runId !== void 0 ? { runId: idle.runId } : {},
|
|
70619
70810
|
spendUsd: idle.spendUsd,
|
|
70620
70811
|
...idle.handoff !== void 0 ? { handoff: idle.handoff } : {},
|
|
70621
|
-
texts:
|
|
70812
|
+
texts: wakeEvents.map(attributedText),
|
|
70622
70813
|
senders,
|
|
70623
70814
|
leaseMs
|
|
70624
70815
|
};
|
|
@@ -70643,7 +70834,7 @@ async function unitWake(body, deps) {
|
|
|
70643
70834
|
...idle.runId !== void 0 ? { runId: idle.runId } : {},
|
|
70644
70835
|
spendUsd: idle.spendUsd,
|
|
70645
70836
|
...idle.handoff !== void 0 ? { handoff: idle.handoff } : {},
|
|
70646
|
-
texts:
|
|
70837
|
+
texts: wakeEvents.map(attributedText),
|
|
70647
70838
|
senders
|
|
70648
70839
|
};
|
|
70649
70840
|
} else {
|
|
@@ -70654,7 +70845,7 @@ async function unitWake(body, deps) {
|
|
|
70654
70845
|
}
|
|
70655
70846
|
}
|
|
70656
70847
|
}
|
|
70657
|
-
const countedIdle =
|
|
70848
|
+
const countedIdle = wakeEvents.length > 0 ? { ...idle, wakes: idle.wakes + 1 } : idle;
|
|
70658
70849
|
const segments2 = row2.segments ?? [];
|
|
70659
70850
|
const updated = {
|
|
70660
70851
|
...row2,
|
|
@@ -70837,12 +71028,14 @@ ${rendered}` : "");
|
|
|
70837
71028
|
)
|
|
70838
71029
|
);
|
|
70839
71030
|
}
|
|
70840
|
-
|
|
70841
|
-
|
|
71031
|
+
const parkedHumanGate = ending.kind === "idle" && idle?.humanGate !== void 0;
|
|
71032
|
+
if ((ending.kind === "held" || parkedHumanGate) && updated.pr !== void 0) {
|
|
71033
|
+
const state = parkedHumanGate ? "waiting for a person" : "held";
|
|
71034
|
+
await deps.github.commentIssue(instance.repo, updated.pr.number, `**Plan runner \u2014 ${row2.unit} ${state}**
|
|
70842
71035
|
|
|
70843
71036
|
${ending.report}`).catch(
|
|
70844
71037
|
(err2) => (deps.log ?? console.warn)(
|
|
70845
|
-
`[coordinator] ${instance.id} ${row2.unit}: the
|
|
71038
|
+
`[coordinator] ${instance.id} ${row2.unit}: the human-gated report could not be posted on the pull request: ${describe11(err2)}`
|
|
70846
71039
|
)
|
|
70847
71040
|
);
|
|
70848
71041
|
}
|
|
@@ -70877,7 +71070,10 @@ async function rebaseStep(body, deps) {
|
|
|
70877
71070
|
if (!row2) return json3(404, { ok: false, error: "unknown_unit", at });
|
|
70878
71071
|
if (row2.pr?.number !== void 0 && row2.pr.number !== body.prNumber)
|
|
70879
71072
|
return json3(409, { ok: false, error: "pull_request_moved", at });
|
|
70880
|
-
deps.runnerOwnership?.claim(instance.repo, body.prNumber
|
|
71073
|
+
deps.runnerOwnership?.claim(instance.repo, body.prNumber, {
|
|
71074
|
+
instanceId: instance.id,
|
|
71075
|
+
unit: row2.unit
|
|
71076
|
+
});
|
|
70881
71077
|
if (deps.runnerRebase === void 0)
|
|
70882
71078
|
return json3(200, { ok: true, outcome: "refused", reason: "the runner's rebase resolver is unavailable", at });
|
|
70883
71079
|
try {
|
|
@@ -71324,7 +71520,7 @@ function createAdminCoordinatorHandler(deps) {
|
|
|
71324
71520
|
})();
|
|
71325
71521
|
};
|
|
71326
71522
|
}
|
|
71327
|
-
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
71523
|
+
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
71328
71524
|
var init_adminCoordinator = __esm({
|
|
71329
71525
|
"../../src/channels/adminCoordinator.ts"() {
|
|
71330
71526
|
"use strict";
|
|
@@ -71376,6 +71572,7 @@ var init_adminCoordinator = __esm({
|
|
|
71376
71572
|
EVERY_RUN = { kind: "all" };
|
|
71377
71573
|
REVIEW_POSTED_CHECKS = 3;
|
|
71378
71574
|
REVIEW_POSTED_RECHECK_MS = 2e3;
|
|
71575
|
+
githubSecondStart = (at) => Math.floor(at / SECOND_MS) * SECOND_MS;
|
|
71379
71576
|
ROUND_OUTCOMES = [
|
|
71380
71577
|
"started",
|
|
71381
71578
|
"pr_opened",
|
|
@@ -71479,6 +71676,56 @@ async function handleCheckRunIntake(headers, rawBody, deps) {
|
|
|
71479
71676
|
body: { ok: true, settled: true, sent: sends.filter((s) => s.kind === "sent").length, of: instances.length }
|
|
71480
71677
|
};
|
|
71481
71678
|
}
|
|
71679
|
+
async function handleIssueCommentIntake(headers, rawBody, deps) {
|
|
71680
|
+
if (deps.secret === void 0 || deps.secret === "") return { status: 503, body: { error: "disabled" } };
|
|
71681
|
+
if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
|
|
71682
|
+
return { status: 401, body: { error: "unauthorized" } };
|
|
71683
|
+
if (headers.event !== "issue_comment") return { status: 200, body: { ok: true, ignored: "event" } };
|
|
71684
|
+
let payload;
|
|
71685
|
+
try {
|
|
71686
|
+
payload = JSON.parse(rawBody);
|
|
71687
|
+
} catch {
|
|
71688
|
+
return { status: 400, body: { error: "invalid_json" } };
|
|
71689
|
+
}
|
|
71690
|
+
if (typeof payload !== "object" || payload === null) return { status: 400, body: { error: "invalid_body" } };
|
|
71691
|
+
const p = payload;
|
|
71692
|
+
if (p.action !== "created") return { status: 200, body: { ok: true, ignored: "action" } };
|
|
71693
|
+
if (p.issue?.pull_request === void 0) return { status: 200, body: { ok: true, ignored: "issue" } };
|
|
71694
|
+
const repo = p.repository?.full_name;
|
|
71695
|
+
const prNumber = p.issue.number;
|
|
71696
|
+
const comment = p.comment;
|
|
71697
|
+
if (typeof repo !== "string" || repo === "" || typeof prNumber !== "number" || !Number.isInteger(prNumber) || typeof comment?.id !== "number" || typeof comment.body !== "string" || typeof comment.created_at !== "string" || typeof comment.user?.login !== "string" || typeof comment.user.type !== "string")
|
|
71698
|
+
return { status: 400, body: { error: "invalid_body" } };
|
|
71699
|
+
if (comment.user.type !== "User") return { status: 200, body: { ok: true, ignored: "sender" } };
|
|
71700
|
+
let owner;
|
|
71701
|
+
try {
|
|
71702
|
+
owner = deps.ownerOf(repo, prNumber);
|
|
71703
|
+
} catch {
|
|
71704
|
+
return { status: 503, body: { error: "ownership_unavailable" } };
|
|
71705
|
+
}
|
|
71706
|
+
if (owner === void 0) return { status: 200, body: { ok: true, ignored: "unowned" } };
|
|
71707
|
+
const instance = await deps.instances.get(owner.instanceId);
|
|
71708
|
+
if (instance === null) return { status: 200, body: { ok: true, ignored: "ended" } };
|
|
71709
|
+
const authorized = await deps.commenterAuthorized(instance.userId, {
|
|
71710
|
+
login: comment.user.login,
|
|
71711
|
+
...typeof comment.user.id === "number" ? { id: comment.user.id } : {}
|
|
71712
|
+
}).catch(() => false);
|
|
71713
|
+
if (!authorized) return { status: 200, body: { ok: true, ignored: "sender" } };
|
|
71714
|
+
const row2 = (await deps.instances.listUnits(owner.instanceId)).find((unit) => unit.unit === owner.unit);
|
|
71715
|
+
if (row2 === void 0 || row2.ending !== void 0) return { status: 200, body: { ok: true, ignored: "ended" } };
|
|
71716
|
+
const created = Date.parse(comment.created_at);
|
|
71717
|
+
const appended = await deps.instances.appendEvent(owner, {
|
|
71718
|
+
id: `github:issue-comment:${comment.id}`,
|
|
71719
|
+
sender: `github:${typeof comment.user.id === "number" ? comment.user.id : comment.user.login}`,
|
|
71720
|
+
senderName: comment.user.login,
|
|
71721
|
+
text: comment.body,
|
|
71722
|
+
mode: row2.idle !== void 0 ? "wake" : "steer",
|
|
71723
|
+
at: Number.isFinite(created) ? created : deps.now()
|
|
71724
|
+
});
|
|
71725
|
+
if (!appended.ok) return { status: 503, body: { error: "store_unavailable" } };
|
|
71726
|
+
const sent = await sendUnitNudge(deps.workflow, owner);
|
|
71727
|
+
return { status: 200, body: { ok: true, appended: true, seq: appended.seq, nudge: sent.kind } };
|
|
71728
|
+
}
|
|
71482
71729
|
async function handlePushIntake(headers, rawBody, deps) {
|
|
71483
71730
|
if (deps.secret === void 0 || deps.secret === "") return { status: 503, body: { error: "disabled" } };
|
|
71484
71731
|
if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
|
|
@@ -71528,7 +71775,14 @@ function createGithubWebhookHandler(deps) {
|
|
|
71528
71775
|
event: one(req.headers[GITHUB_EVENT_HEADER]),
|
|
71529
71776
|
signature: one(req.headers[GITHUB_SIGNATURE_HEADER])
|
|
71530
71777
|
};
|
|
71531
|
-
const result = headers.event === "push" ? await handlePushIntake(headers, body.body, { secret: deps.secret, watch: deps.watch }) : await
|
|
71778
|
+
const result = headers.event === "push" ? await handlePushIntake(headers, body.body, { secret: deps.secret, watch: deps.watch }) : headers.event === "issue_comment" && deps.ownerOf !== void 0 && deps.instances !== void 0 && deps.commenterAuthorized !== void 0 && deps.now !== void 0 ? await handleIssueCommentIntake(headers, body.body, {
|
|
71779
|
+
secret: deps.secret,
|
|
71780
|
+
ownerOf: deps.ownerOf,
|
|
71781
|
+
instances: deps.instances,
|
|
71782
|
+
commenterAuthorized: deps.commenterAuthorized,
|
|
71783
|
+
workflow: deps.workflow,
|
|
71784
|
+
now: deps.now
|
|
71785
|
+
}) : await handleCheckRunIntake(headers, body.body, deps);
|
|
71532
71786
|
answer(result.status, result.body);
|
|
71533
71787
|
};
|
|
71534
71788
|
}
|
|
@@ -72598,9 +72852,16 @@ async function runBot() {
|
|
|
72598
72852
|
);
|
|
72599
72853
|
}
|
|
72600
72854
|
});
|
|
72855
|
+
const commenterAuthorized = async (requester, author) => {
|
|
72856
|
+
const binding = await bindingOf(requester, config);
|
|
72857
|
+
return binding !== void 0 && author.id === binding.id && author.login.toLowerCase() === binding.login.toLowerCase();
|
|
72858
|
+
};
|
|
72601
72859
|
const githubWebhook = createGithubWebhookHandler({
|
|
72602
72860
|
secret: processSecrets.get("GITHUB_WEBHOOK_SECRET")?.reveal(),
|
|
72603
72861
|
watch: mergeWatch,
|
|
72862
|
+
ownerOf: (repo, prNumber) => runnerOwnership.owner(repo, prNumber),
|
|
72863
|
+
instances: coordinatorInstances,
|
|
72864
|
+
commenterAuthorized,
|
|
72604
72865
|
checksSettled: async (repo, headSha) => {
|
|
72605
72866
|
const checks = await fetchCommitChecks(repo, headSha);
|
|
72606
72867
|
return checks !== void 0 && checks.total > 0 && checks.pending.length === 0;
|
|
@@ -72662,6 +72923,8 @@ async function runBot() {
|
|
|
72662
72923
|
github: deps.githubApi ?? new RestGithubApi(),
|
|
72663
72924
|
createBranchRef,
|
|
72664
72925
|
fetchPrReviews: fetchPullRequestReviews,
|
|
72926
|
+
fetchPrComments: fetchPullRequestComments,
|
|
72927
|
+
commenterAuthorized,
|
|
72665
72928
|
// The merge step (record 0031's merge grant): the pull request as GitHub has it, the checks at its head, the squash.
|
|
72666
72929
|
fetchPrFacts: fetchPullRequestFacts,
|
|
72667
72930
|
fetchCommitChecks,
|
|
@@ -73288,6 +73551,7 @@ var init_index = __esm({
|
|
|
73288
73551
|
init_pullSweepWiring();
|
|
73289
73552
|
init_githubApi();
|
|
73290
73553
|
init_githubApp();
|
|
73554
|
+
init_authorBinding();
|
|
73291
73555
|
init_identityRewrite();
|
|
73292
73556
|
init_run2();
|
|
73293
73557
|
init_record();
|