@coreplane/switchboard 1.261.2 → 1.261.4
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.
|
@@ -2732,6 +2732,38 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2732
2732
|
return { ok: true };
|
|
2733
2733
|
}
|
|
2734
2734
|
|
|
2735
|
+
/** One compare-and-replace transaction updates a unit for exactly one
|
|
2736
|
+
* caller. The whole expected JSON is the fence: any intervening unit write
|
|
2737
|
+
* makes this caller stale. */
|
|
2738
|
+
async compareAndReplaceUnit(
|
|
2739
|
+
expected: CoordinatorUnit,
|
|
2740
|
+
replacement: CoordinatorUnit,
|
|
2741
|
+
now: number,
|
|
2742
|
+
): Promise<{ ok: true } | { ok: false; reason: "stale" }> {
|
|
2743
|
+
let out: { ok: true } | { ok: false; reason: "stale" } = { ok: true };
|
|
2744
|
+
this.ctx.storage.transactionSync(() => {
|
|
2745
|
+
const row = this.sql
|
|
2746
|
+
.exec<{ json: string }>(
|
|
2747
|
+
`SELECT json FROM coordinator_units WHERE instance_id = ? AND unit = ?`,
|
|
2748
|
+
expected.instanceId,
|
|
2749
|
+
expected.unit,
|
|
2750
|
+
)
|
|
2751
|
+
.toArray()[0];
|
|
2752
|
+
if (row?.json !== JSON.stringify(expected)) {
|
|
2753
|
+
out = { ok: false, reason: "stale" };
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
this.sql.exec(
|
|
2757
|
+
`UPDATE coordinator_units SET json = ?, updated_at = ? WHERE instance_id = ? AND unit = ?`,
|
|
2758
|
+
JSON.stringify(replacement),
|
|
2759
|
+
now,
|
|
2760
|
+
expected.instanceId,
|
|
2761
|
+
expected.unit,
|
|
2762
|
+
);
|
|
2763
|
+
});
|
|
2764
|
+
return out;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2735
2767
|
async listUnits(instanceId: string): Promise<CoordinatorUnit[]> {
|
|
2736
2768
|
return this.sql
|
|
2737
2769
|
.exec<{ json: string }>(`SELECT json FROM coordinator_units WHERE instance_id = ? ORDER BY rowid`, instanceId)
|
|
@@ -5187,6 +5219,7 @@ const LEDGER_ROUTES = new Set([
|
|
|
5187
5219
|
"/runs/coordinator/get",
|
|
5188
5220
|
"/runs/coordinator/stop",
|
|
5189
5221
|
"/runs/coordinator/units/put",
|
|
5222
|
+
"/runs/coordinator/units/claim-legacy-continuation",
|
|
5190
5223
|
"/runs/coordinator/units/list",
|
|
5191
5224
|
"/runs/coordinator/events/append",
|
|
5192
5225
|
"/runs/coordinator/events/list",
|
|
@@ -5886,6 +5919,17 @@ async function handleLedger(pathname: string, body: unknown, env: Env): Promise<
|
|
|
5886
5919
|
console.log(`[runs/coordinator/units/put] ${key.value} ${units[0]!.instanceId} ${units.length} row(s)`);
|
|
5887
5920
|
return json(r);
|
|
5888
5921
|
}
|
|
5922
|
+
if (pathname === "/runs/coordinator/units/claim-legacy-continuation") {
|
|
5923
|
+
if (!isCoordinatorUnit(b.expected) || !isCoordinatorUnit(b.recovered))
|
|
5924
|
+
return json({ error: "expected and recovered must be coordinator unit rows" }, 400);
|
|
5925
|
+
if (b.expected.instanceId !== b.recovered.instanceId || b.expected.unit !== b.recovered.unit)
|
|
5926
|
+
return json({ error: "expected and recovered must name the same unit" }, 400);
|
|
5927
|
+
const r = await stub.compareAndReplaceUnit(b.expected, b.recovered, now);
|
|
5928
|
+
console.log(
|
|
5929
|
+
`[runs/coordinator/units/claim-legacy-continuation] ${key.value} ${b.expected.instanceId}:${b.expected.unit} → ${r.ok ? "replaced" : r.reason}`,
|
|
5930
|
+
);
|
|
5931
|
+
return r.ok ? json(r) : json(r, 409);
|
|
5932
|
+
}
|
|
5889
5933
|
if (pathname === "/runs/coordinator/units/list") {
|
|
5890
5934
|
if (typeof b.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(b.instanceId))
|
|
5891
5935
|
return json({ error: "instanceId must be a Workflow instance id" }, 400);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.261.
|
|
3
|
+
"version": "1.261.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.261.
|
|
9
|
+
"version": "1.261.4",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -21060,7 +21060,7 @@
|
|
|
21060
21060
|
},
|
|
21061
21061
|
"packages/switchboard": {
|
|
21062
21062
|
"name": "@coreplane/switchboard",
|
|
21063
|
-
"version": "1.261.
|
|
21063
|
+
"version": "1.261.4",
|
|
21064
21064
|
"license": "Apache-2.0",
|
|
21065
21065
|
"dependencies": {
|
|
21066
21066
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.261.
|
|
3
|
+
"version": "1.261.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -1137,14 +1137,16 @@ async function runUnit(
|
|
|
1137
1137
|
: {}),
|
|
1138
1138
|
},
|
|
1139
1139
|
...(state.pr !== undefined ? { pr: state.pr } : {}),
|
|
1140
|
-
// A
|
|
1141
|
-
//
|
|
1142
|
-
//
|
|
1143
|
-
...(ending.kind === "
|
|
1144
|
-
? { headSha:
|
|
1145
|
-
: ending.kind === "
|
|
1146
|
-
? { headSha: ending.
|
|
1147
|
-
:
|
|
1140
|
+
// A merge_ready ending retains the exact head its final approval
|
|
1141
|
+
// reviewed. Review-pending endings retain the coding child's push;
|
|
1142
|
+
// both become the row's durable lastPush for a later attempt.
|
|
1143
|
+
...(ending.kind === "merge_ready" && state.lastReviewHead !== undefined
|
|
1144
|
+
? { headSha: state.lastReviewHead }
|
|
1145
|
+
: ending.kind === "review_pending" && ending.headSha !== undefined
|
|
1146
|
+
? { headSha: ending.headSha }
|
|
1147
|
+
: ending.kind === "idle" && ending.why === "review_pending" && ending.from !== undefined
|
|
1148
|
+
? { headSha: ending.from }
|
|
1149
|
+
: {}),
|
|
1148
1150
|
...(state.lastCodingRunId !== undefined ? { codingRunId: state.lastCodingRunId } : {}),
|
|
1149
1151
|
// A continued ending is a segment's end, not the unit's: the bot
|
|
1150
1152
|
// writes the renewal as a row keyed by the next segment's index
|
package/dist/cli.js
CHANGED
|
@@ -34687,6 +34687,9 @@ var init_instanceStore = __esm({
|
|
|
34687
34687
|
async putUnits(_units) {
|
|
34688
34688
|
return { ok: false, reason: "unavailable" };
|
|
34689
34689
|
}
|
|
34690
|
+
async compareAndReplaceUnit(_expected, _replacement) {
|
|
34691
|
+
return { ok: false, reason: "unavailable" };
|
|
34692
|
+
}
|
|
34690
34693
|
async listUnits(_instanceId) {
|
|
34691
34694
|
return [];
|
|
34692
34695
|
}
|
|
@@ -34761,6 +34764,18 @@ var init_instanceStore = __esm({
|
|
|
34761
34764
|
if (d.ok === true) return { ok: true };
|
|
34762
34765
|
throw new Error(`coordinator store /runs/coordinator/units/put: unexpected answer (HTTP ${r.status})`);
|
|
34763
34766
|
}
|
|
34767
|
+
async compareAndReplaceUnit(expected, replacement) {
|
|
34768
|
+
const r = await this.post("/runs/coordinator/units/claim-legacy-continuation", {
|
|
34769
|
+
expected,
|
|
34770
|
+
recovered: replacement
|
|
34771
|
+
});
|
|
34772
|
+
const d = r.data;
|
|
34773
|
+
if (r.status === 409 && d.reason === "stale") return { ok: false, reason: "stale" };
|
|
34774
|
+
if (d.ok === true) return { ok: true };
|
|
34775
|
+
throw new Error(
|
|
34776
|
+
`coordinator store /runs/coordinator/units/claim-legacy-continuation: unexpected answer (HTTP ${r.status})`
|
|
34777
|
+
);
|
|
34778
|
+
}
|
|
34764
34779
|
async listUnits(instanceId) {
|
|
34765
34780
|
const r = await this.post("/runs/coordinator/units/list", { instanceId });
|
|
34766
34781
|
const d = r.data;
|
|
@@ -54774,75 +54789,103 @@ async function handOffToCoordinatorUnchecked(deps, input) {
|
|
|
54774
54789
|
}
|
|
54775
54790
|
async function start(deps, input, instance, units, where, write) {
|
|
54776
54791
|
const log = deps.log ?? console.log;
|
|
54777
|
-
const
|
|
54778
|
-
if (
|
|
54779
|
-
|
|
54792
|
+
const reservation = await input.beforeStart?.();
|
|
54793
|
+
if (reservation?.ok === false)
|
|
54794
|
+
return { status: "aborted", reply: reservation.refusal.text, refusal: reservation.refusal };
|
|
54795
|
+
let started = false;
|
|
54796
|
+
try {
|
|
54797
|
+
const put = write === "replace" ? await deps.instances.replace(instance) : await deps.instances.put(instance);
|
|
54798
|
+
if (!put.ok) {
|
|
54799
|
+
if (put.reason === "unavailable")
|
|
54800
|
+
return refused5(
|
|
54801
|
+
"plan_history_unavailable",
|
|
54802
|
+
"\u26A0\uFE0F The plan runner needs run history on the state Worker (`runHistory.worker`): the instance record could not be written, so nothing ran."
|
|
54803
|
+
);
|
|
54780
54804
|
return refused5(
|
|
54781
|
-
"
|
|
54782
|
-
|
|
54805
|
+
"plan_runner_conflict",
|
|
54806
|
+
`\u{1F6AB} A runner for \`${instance.id}\` was just recorded by another request, so this request started nothing; the recorded runner owns the pipeline.`
|
|
54783
54807
|
);
|
|
54784
|
-
|
|
54785
|
-
|
|
54786
|
-
|
|
54787
|
-
);
|
|
54788
|
-
}
|
|
54789
|
-
const rows = await deps.instances.putUnits(units);
|
|
54790
|
-
if (!rows.ok)
|
|
54791
|
-
return refused5(
|
|
54792
|
-
"plan_history_unavailable",
|
|
54793
|
-
"\u26A0\uFE0F The plan runner needs run history on the state Worker: the unit rows could not be written, so nothing ran."
|
|
54794
|
-
);
|
|
54795
|
-
const accepted = [...input.msg.images ?? [], ...input.msg.documents ?? []];
|
|
54796
|
-
if (instance.plan?.path === void 0 && accepted.length > 0) {
|
|
54797
|
-
const unit = units[0];
|
|
54798
|
-
const seeded = await deps.instances.appendEvent(
|
|
54799
|
-
{ instanceId: instance.id, unit: unit.unit },
|
|
54800
|
-
{
|
|
54801
|
-
id: `${instance.id}:${unit.unit}:ship-request`,
|
|
54802
|
-
sender: input.msg.userId,
|
|
54803
|
-
...input.msg.userName !== void 0 ? { senderName: input.msg.userName } : {},
|
|
54804
|
-
text: "Attachments from the ship request.",
|
|
54805
|
-
attachments: accepted,
|
|
54806
|
-
mode: "steer",
|
|
54807
|
-
at: input.now
|
|
54808
|
-
}
|
|
54809
|
-
);
|
|
54810
|
-
if (!seeded.ok)
|
|
54808
|
+
}
|
|
54809
|
+
const rows = await deps.instances.putUnits(units);
|
|
54810
|
+
if (!rows.ok)
|
|
54811
54811
|
return refused5(
|
|
54812
54812
|
"plan_history_unavailable",
|
|
54813
|
-
"\u26A0\uFE0F The plan runner needs run history on the state Worker: the
|
|
54813
|
+
"\u26A0\uFE0F The plan runner needs run history on the state Worker: the unit rows could not be written, so nothing ran."
|
|
54814
54814
|
);
|
|
54815
|
-
|
|
54816
|
-
|
|
54817
|
-
|
|
54818
|
-
|
|
54819
|
-
|
|
54820
|
-
|
|
54821
|
-
|
|
54822
|
-
|
|
54823
|
-
|
|
54824
|
-
|
|
54825
|
-
|
|
54826
|
-
|
|
54827
|
-
|
|
54828
|
-
|
|
54829
|
-
instanceId: instance.id
|
|
54830
|
-
};
|
|
54831
|
-
}
|
|
54832
|
-
case "duplicate": {
|
|
54833
|
-
const status3 = answer.status !== void 0 ? `, status: ${answer.status}` : "";
|
|
54834
|
-
return refused5(
|
|
54835
|
-
"plan_instance_orphaned",
|
|
54836
|
-
`\u{1F6AB} A Workflow instance \`${instance.id}\` already exists on the platform${status3} but the state Worker knew nothing of it \u2014 a person decides; re-issuing will not resume it.`
|
|
54815
|
+
const accepted = [...input.msg.images ?? [], ...input.msg.documents ?? []];
|
|
54816
|
+
if (instance.plan?.path === void 0 && accepted.length > 0) {
|
|
54817
|
+
const unit = units[0];
|
|
54818
|
+
const seeded = await deps.instances.appendEvent(
|
|
54819
|
+
{ instanceId: instance.id, unit: unit.unit },
|
|
54820
|
+
{
|
|
54821
|
+
id: `${instance.id}:${unit.unit}:ship-request`,
|
|
54822
|
+
sender: input.msg.userId,
|
|
54823
|
+
...input.msg.userName !== void 0 ? { senderName: input.msg.userName } : {},
|
|
54824
|
+
text: "Attachments from the ship request.",
|
|
54825
|
+
attachments: accepted,
|
|
54826
|
+
mode: "steer",
|
|
54827
|
+
at: input.now
|
|
54828
|
+
}
|
|
54837
54829
|
);
|
|
54830
|
+
if (!seeded.ok)
|
|
54831
|
+
return refused5(
|
|
54832
|
+
"plan_history_unavailable",
|
|
54833
|
+
"\u26A0\uFE0F The plan runner needs run history on the state Worker: the ship request's attachments could not be written, so nothing ran."
|
|
54834
|
+
);
|
|
54838
54835
|
}
|
|
54839
|
-
|
|
54840
|
-
|
|
54841
|
-
|
|
54842
|
-
|
|
54843
|
-
|
|
54844
|
-
|
|
54845
|
-
|
|
54836
|
+
if (reservation?.ok === true) {
|
|
54837
|
+
const unit = units[0];
|
|
54838
|
+
if (unit === void 0)
|
|
54839
|
+
return refused5(
|
|
54840
|
+
"setup_failed",
|
|
54841
|
+
"\u26A0\uFE0F The plan runner had no durable unit to own at its final start gate, so nothing ran."
|
|
54842
|
+
);
|
|
54843
|
+
try {
|
|
54844
|
+
await reservation.commit({ instanceId: instance.id, unit: unit.unit });
|
|
54845
|
+
} catch (err2) {
|
|
54846
|
+
return refused5(
|
|
54847
|
+
"setup_failed",
|
|
54848
|
+
`\u26A0\uFE0F The plan runner could not transfer existing-pull-request ownership to its new attempt (${describe7(err2)}), so nothing ran.`
|
|
54849
|
+
);
|
|
54850
|
+
}
|
|
54851
|
+
}
|
|
54852
|
+
let answer;
|
|
54853
|
+
try {
|
|
54854
|
+
answer = await deps.create(instance.id);
|
|
54855
|
+
} catch (err2) {
|
|
54856
|
+
answer = { kind: "unanswered", reason: describe7(err2) };
|
|
54857
|
+
}
|
|
54858
|
+
switch (answer.kind) {
|
|
54859
|
+
case "created": {
|
|
54860
|
+
if (reservation?.ok === true) reservation.complete();
|
|
54861
|
+
started = true;
|
|
54862
|
+
log(`[ship] ${input.msg.threadKey}: handed to the plan runner ${instance.id} (${units.length} unit(s))`);
|
|
54863
|
+
const replaced = write === "replace" ? ["the records of an earlier attempt that never started were replaced"] : [];
|
|
54864
|
+
return {
|
|
54865
|
+
status: "completed",
|
|
54866
|
+
reply: handedOff([...where, ...replaced]),
|
|
54867
|
+
instanceId: instance.id
|
|
54868
|
+
};
|
|
54869
|
+
}
|
|
54870
|
+
case "duplicate": {
|
|
54871
|
+
const status3 = answer.status !== void 0 ? `, status: ${answer.status}` : "";
|
|
54872
|
+
return refused5(
|
|
54873
|
+
"plan_instance_orphaned",
|
|
54874
|
+
`\u{1F6AB} A Workflow instance \`${instance.id}\` already exists on the platform${status3} but the state Worker knew nothing of it \u2014 a person decides; re-issuing will not resume it.`
|
|
54875
|
+
);
|
|
54876
|
+
}
|
|
54877
|
+
case "failed":
|
|
54878
|
+
case "unanswered":
|
|
54879
|
+
log(`[ship] ${input.msg.threadKey}: the plan runner ${instance.id} could not be started \u2014 ${answer.reason}`);
|
|
54880
|
+
return refused5(
|
|
54881
|
+
"plan_start_failed",
|
|
54882
|
+
`\u26A0\uFE0F This is a bug: the plan runner could not be started (${answer.reason}), nothing ran, and no automatic start retry was scheduled.`
|
|
54883
|
+
);
|
|
54884
|
+
}
|
|
54885
|
+
const unreachable3 = answer;
|
|
54886
|
+
return unreachable3;
|
|
54887
|
+
} finally {
|
|
54888
|
+
if (!started && reservation?.ok === true) await reservation.abort();
|
|
54846
54889
|
}
|
|
54847
54890
|
}
|
|
54848
54891
|
function handedOff(facts2) {
|
|
@@ -55296,6 +55339,7 @@ async function runShipBranch(deps, msg, io, ctx) {
|
|
|
55296
55339
|
entry,
|
|
55297
55340
|
requestText: directives.text,
|
|
55298
55341
|
...ctx.reissuePlanId !== void 0 ? { reissuePlanId: ctx.reissuePlanId } : {},
|
|
55342
|
+
...ctx.beforeCoordinatorStart !== void 0 ? { beforeStart: ctx.beforeCoordinatorStart } : {},
|
|
55299
55343
|
msg,
|
|
55300
55344
|
agentSource: ctx.agentSource,
|
|
55301
55345
|
runId: run2.id,
|
|
@@ -56089,6 +56133,35 @@ function continuationBudgetOf(instance, unit) {
|
|
|
56089
56133
|
if (remainingRounds <= 0) return { kind: "exhausted", budget: "review-round" };
|
|
56090
56134
|
return { kind: "remaining", caps: { maxRounds: remainingRounds, maxMinutes: remainingMinutes } };
|
|
56091
56135
|
}
|
|
56136
|
+
async function legacyReviewedHeadOf(runs, instance, unit, prNumber) {
|
|
56137
|
+
const listed = await runs.listUnitRuns(unitKeyOf({ instanceId: instance.id, unit: unit.unit }), { kind: "all" }).catch(() => void 0);
|
|
56138
|
+
if (listed === void 0 || !listed.ok || listed.value.unit !== unitKeyOf({ instanceId: instance.id, unit: unit.unit }) || listed.value.instanceId !== instance.id || listed.value.id !== unit.unit || listed.value.branch !== unit.branch || listed.value.pr?.number !== prNumber || listed.value.instance.id !== instance.id || listed.value.instance.repo.toLowerCase() !== instance.repo.toLowerCase())
|
|
56139
|
+
return void 0;
|
|
56140
|
+
const prefix = `${instance.id}:${unit.unit}/`;
|
|
56141
|
+
const owned = listed.value.runs.filter(
|
|
56142
|
+
(run2) => run2.finished && run2.status === "completed" && run2.parentInstanceId === instance.id && run2.idempotencyKey?.startsWith(prefix) === true && run2.userId === instance.userId && run2.repo?.toLowerCase() === instance.repo.toLowerCase()
|
|
56143
|
+
);
|
|
56144
|
+
const coding = new Set(
|
|
56145
|
+
owned.flatMap((run2) => {
|
|
56146
|
+
if (run2.agent !== "coding" || run2.pr?.number !== prNumber) return [];
|
|
56147
|
+
return [
|
|
56148
|
+
...typeof run2.headSha === "string" && FULL_SHA2.test(run2.headSha) ? [run2.headSha.toLowerCase()] : [],
|
|
56149
|
+
...(run2.pushed ?? []).flatMap(
|
|
56150
|
+
(pushed) => pushed.ref === unit.branch && FULL_SHA2.test(pushed.sha) ? [pushed.sha.toLowerCase()] : []
|
|
56151
|
+
)
|
|
56152
|
+
];
|
|
56153
|
+
})
|
|
56154
|
+
);
|
|
56155
|
+
const approved = new Set(
|
|
56156
|
+
owned.flatMap((run2) => {
|
|
56157
|
+
const head = run2.reviewHead;
|
|
56158
|
+
const post = run2.reviewPost;
|
|
56159
|
+
return run2.agent === "review" && typeof head === "string" && FULL_SHA2.test(head) && run2.verdict?.verdict === "approve" && run2.verdict.head?.toLowerCase() === head.toLowerCase() && post?.posted === true && post.target.repo.toLowerCase() === instance.repo.toLowerCase() && post.target.number === prNumber && post.head.toLowerCase() === head.toLowerCase() && post.verdict === "approve" ? [head.toLowerCase()] : [];
|
|
56160
|
+
})
|
|
56161
|
+
);
|
|
56162
|
+
const exact = [...approved].filter((head) => coding.has(head));
|
|
56163
|
+
return exact.length === 1 ? exact[0] : void 0;
|
|
56164
|
+
}
|
|
56092
56165
|
async function dispatch(deps, msg, io, opts = {}) {
|
|
56093
56166
|
const ended = { status: "completed" };
|
|
56094
56167
|
const resume = opts.resume;
|
|
@@ -56173,6 +56246,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56173
56246
|
let stoppedWhileAttaching;
|
|
56174
56247
|
let resumeRowClosed = false;
|
|
56175
56248
|
let restartRequest;
|
|
56249
|
+
let releaseLegacyOwnership;
|
|
56176
56250
|
const reservationHooks = {
|
|
56177
56251
|
onStop: (mode) => void registered?.control.requestStop(mode),
|
|
56178
56252
|
onFenced: () => {
|
|
@@ -56191,6 +56265,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56191
56265
|
let operatorRepo;
|
|
56192
56266
|
let reissuePlanId;
|
|
56193
56267
|
let reissueCaps;
|
|
56268
|
+
let beforeCoordinatorStart;
|
|
56269
|
+
let reserveLegacyOwnership;
|
|
56194
56270
|
let operatorThread;
|
|
56195
56271
|
let pageOwner;
|
|
56196
56272
|
if (operatorMode !== "off") {
|
|
@@ -56408,7 +56484,19 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56408
56484
|
await recordPendingOperator();
|
|
56409
56485
|
return ended;
|
|
56410
56486
|
}
|
|
56411
|
-
|
|
56487
|
+
let expectedHead = owner.unit.lastPush;
|
|
56488
|
+
const recoverLegacyBinding = owner.unit.ending?.kind === "merge_ready" && (expectedHead === void 0 || owner.unit.publication === void 0);
|
|
56489
|
+
if (recoverLegacyBinding) {
|
|
56490
|
+
const recovered = await legacyReviewedHeadOf(runsService, instance, owner.unit, recordedPr.number);
|
|
56491
|
+
if (recovered === void 0 || expectedHead !== void 0 && expectedHead.toLowerCase() !== recovered) {
|
|
56492
|
+
await io.reply(
|
|
56493
|
+
`Unit ${owner.unit.unit} has no single exact reviewed head in its durable child records, so continuation did not start. Nothing else ran.`
|
|
56494
|
+
);
|
|
56495
|
+
await recordPendingOperator();
|
|
56496
|
+
return ended;
|
|
56497
|
+
}
|
|
56498
|
+
expectedHead = recovered;
|
|
56499
|
+
}
|
|
56412
56500
|
if (expectedHead === void 0) {
|
|
56413
56501
|
await io.reply(
|
|
56414
56502
|
`Unit ${owner.unit.unit} does not retain the pull request's durable expected head, so continuation did not start. Nothing else ran.`
|
|
@@ -56447,6 +56535,140 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56447
56535
|
await recordPendingOperator();
|
|
56448
56536
|
return ended;
|
|
56449
56537
|
}
|
|
56538
|
+
if (recoverLegacyBinding) {
|
|
56539
|
+
const baseRef = instance.base;
|
|
56540
|
+
if (baseRef === void 0 || facts2.baseRef !== baseRef) {
|
|
56541
|
+
await io.reply(
|
|
56542
|
+
`${instance.repo}#${recordedPr.number} no longer has the pipeline's verifiable base ref, so continuation did not start. Nothing else ran.`
|
|
56543
|
+
);
|
|
56544
|
+
await recordPendingOperator();
|
|
56545
|
+
return ended;
|
|
56546
|
+
}
|
|
56547
|
+
const existingBinding = owner.unit.publication;
|
|
56548
|
+
if (existingBinding !== void 0 && (existingBinding.repo.toLowerCase() !== instance.repo.toLowerCase() || existingBinding.pr !== recordedPr.number || existingBinding.headRef !== owner.unit.branch || existingBinding.baseRef !== baseRef || existingBinding.expectedHeadSha.toLowerCase() !== expectedHead || existingBinding.publicationRef !== owner.unit.branch || existingBinding.owner.instanceId !== owner.instanceId || existingBinding.owner.unit !== owner.unit.unit)) {
|
|
56549
|
+
await io.reply(
|
|
56550
|
+
`Unit ${owner.unit.unit}'s durable publication binding no longer matches this pipeline, so continuation did not start. Nothing else ran.`
|
|
56551
|
+
);
|
|
56552
|
+
await recordPendingOperator();
|
|
56553
|
+
return ended;
|
|
56554
|
+
}
|
|
56555
|
+
let currentOwner;
|
|
56556
|
+
const runnerOwnership = deps.runnerOwnership;
|
|
56557
|
+
try {
|
|
56558
|
+
if (runnerOwnership === void 0) throw new Error("runner ownership is unavailable");
|
|
56559
|
+
currentOwner = runnerOwnership.owner(instance.repo, recordedPr.number);
|
|
56560
|
+
} catch {
|
|
56561
|
+
await io.reply(
|
|
56562
|
+
`Runner ownership for ${instance.repo}#${recordedPr.number} could not be verified, so continuation did not start. Nothing else ran.`
|
|
56563
|
+
);
|
|
56564
|
+
await recordPendingOperator();
|
|
56565
|
+
return ended;
|
|
56566
|
+
}
|
|
56567
|
+
if (currentOwner !== void 0 && (currentOwner.instanceId !== owner.instanceId || currentOwner.unit !== owner.unit.unit)) {
|
|
56568
|
+
await io.reply(
|
|
56569
|
+
`${instance.repo}#${recordedPr.number} is owned by another runner, so continuation of ${owner.instanceId}:${owner.unit.unit} did not start. Nothing else ran.`
|
|
56570
|
+
);
|
|
56571
|
+
await recordPendingOperator();
|
|
56572
|
+
return ended;
|
|
56573
|
+
}
|
|
56574
|
+
const legacyUnit = owner.unit;
|
|
56575
|
+
const coordinatorInstances = deps.coordinatorInstances;
|
|
56576
|
+
const reservationOwner = { instanceId: owner.instanceId, unit: owner.unit.unit };
|
|
56577
|
+
const recoveredUnit = {
|
|
56578
|
+
...legacyUnit,
|
|
56579
|
+
lastPush: expectedHead,
|
|
56580
|
+
publication: {
|
|
56581
|
+
repo: instance.repo,
|
|
56582
|
+
pr: recordedPr.number,
|
|
56583
|
+
headRef: legacyUnit.branch,
|
|
56584
|
+
baseRef,
|
|
56585
|
+
expectedHeadSha: expectedHead,
|
|
56586
|
+
publicationRef: legacyUnit.branch,
|
|
56587
|
+
owner: reservationOwner
|
|
56588
|
+
}
|
|
56589
|
+
};
|
|
56590
|
+
let token2;
|
|
56591
|
+
let transferredOwner;
|
|
56592
|
+
releaseLegacyOwnership = () => {
|
|
56593
|
+
if (token2 !== void 0) {
|
|
56594
|
+
const released = runnerOwnership.releaseReservation(instance.repo, recordedPr.number, token2);
|
|
56595
|
+
if (released) token2 = void 0;
|
|
56596
|
+
return released;
|
|
56597
|
+
}
|
|
56598
|
+
if (transferredOwner !== void 0) {
|
|
56599
|
+
const released = runnerOwnership.release(instance.repo, recordedPr.number, transferredOwner);
|
|
56600
|
+
if (released) transferredOwner = void 0;
|
|
56601
|
+
return released;
|
|
56602
|
+
}
|
|
56603
|
+
return true;
|
|
56604
|
+
};
|
|
56605
|
+
reserveLegacyOwnership = () => {
|
|
56606
|
+
token2 = runnerOwnership.reserve(instance.repo, recordedPr.number, reservationOwner);
|
|
56607
|
+
if (token2 !== void 0) return void 0;
|
|
56608
|
+
const competing = runnerOwnership.owner(instance.repo, recordedPr.number);
|
|
56609
|
+
const sameContinuation = competing?.instanceId === reservationOwner.instanceId && competing.unit === reservationOwner.unit;
|
|
56610
|
+
return refusalOf(
|
|
56611
|
+
"setup_failed",
|
|
56612
|
+
sameContinuation ? `Unit ${legacyUnit.unit} changed while its legacy continuation was being reserved, so continuation did not start. Nothing else ran.` : `${instance.repo}#${recordedPr.number} is owned by another runner, so continuation of ${owner.instanceId}:${legacyUnit.unit} did not start. Nothing else ran.`
|
|
56613
|
+
);
|
|
56614
|
+
};
|
|
56615
|
+
beforeCoordinatorStart = async () => {
|
|
56616
|
+
const activeToken = token2;
|
|
56617
|
+
if (activeToken === void 0)
|
|
56618
|
+
return {
|
|
56619
|
+
ok: false,
|
|
56620
|
+
refusal: refusalOf(
|
|
56621
|
+
"setup_failed",
|
|
56622
|
+
`Unit ${legacyUnit.unit}'s ownership reservation was lost, so continuation did not start. Nothing else ran.`
|
|
56623
|
+
)
|
|
56624
|
+
};
|
|
56625
|
+
const persisted = await coordinatorInstances.compareAndReplaceUnit(legacyUnit, recoveredUnit).catch(() => void 0);
|
|
56626
|
+
if (persisted?.ok !== true) {
|
|
56627
|
+
const released = releaseLegacyOwnership?.() ?? true;
|
|
56628
|
+
const reason2 = persisted?.reason === "stale" ? `Unit ${legacyUnit.unit} changed while its legacy continuation was being reserved, so continuation did not start.` : `Unit ${legacyUnit.unit}'s recovered continuation binding could not be stored, so continuation did not start.`;
|
|
56629
|
+
const text = released ? `${reason2} Nothing else ran.` : `${reason2} Its ownership reservation could not be released. Nothing else ran.`;
|
|
56630
|
+
return { ok: false, refusal: refusalOf("setup_failed", text) };
|
|
56631
|
+
}
|
|
56632
|
+
const reservedOwner = runnerOwnership.owner(instance.repo, recordedPr.number);
|
|
56633
|
+
if (reservedOwner?.instanceId !== reservationOwner.instanceId || reservedOwner.unit !== reservationOwner.unit) {
|
|
56634
|
+
const restored = await coordinatorInstances.compareAndReplaceUnit(recoveredUnit, legacyUnit).catch(() => void 0);
|
|
56635
|
+
const released = releaseLegacyOwnership?.() ?? true;
|
|
56636
|
+
const rollbackFailure = restored?.ok === true ? void 0 : restored?.reason ?? "unavailable";
|
|
56637
|
+
const cleanupFailure = released ? "" : " and its ownership reservation could not be released";
|
|
56638
|
+
const text = rollbackFailure === void 0 ? `${instance.repo}#${recordedPr.number} changed runner ownership while its legacy continuation was being reserved${cleanupFailure}, so continuation did not start. Nothing else ran.` : `${instance.repo}#${recordedPr.number} changed runner ownership while its legacy continuation was being reserved, and the exact legacy row could not be restored (${rollbackFailure})${cleanupFailure}; continuation did not start. Nothing else ran.`;
|
|
56639
|
+
return { ok: false, refusal: refusalOf("setup_failed", text) };
|
|
56640
|
+
}
|
|
56641
|
+
owner.unit = recoveredUnit;
|
|
56642
|
+
return {
|
|
56643
|
+
ok: true,
|
|
56644
|
+
commit: async (nextOwner) => {
|
|
56645
|
+
if (!runnerOwnership.transferReservation(
|
|
56646
|
+
instance.repo,
|
|
56647
|
+
recordedPr.number,
|
|
56648
|
+
activeToken,
|
|
56649
|
+
reservationOwner,
|
|
56650
|
+
nextOwner
|
|
56651
|
+
))
|
|
56652
|
+
throw new Error("legacy continuation ownership reservation or current owner changed");
|
|
56653
|
+
token2 = void 0;
|
|
56654
|
+
transferredOwner = nextOwner;
|
|
56655
|
+
},
|
|
56656
|
+
complete: () => {
|
|
56657
|
+
transferredOwner = void 0;
|
|
56658
|
+
},
|
|
56659
|
+
abort: async () => {
|
|
56660
|
+
const restored = await coordinatorInstances.compareAndReplaceUnit(recoveredUnit, legacyUnit).catch(() => void 0);
|
|
56661
|
+
const released = releaseLegacyOwnership?.() ?? true;
|
|
56662
|
+
owner.unit = legacyUnit;
|
|
56663
|
+
const failures = [
|
|
56664
|
+
...restored?.ok === true ? [] : [`legacy continuation rollback failed (${restored?.reason ?? "unavailable"})`],
|
|
56665
|
+
...released ? [] : ["legacy continuation ownership cleanup failed"]
|
|
56666
|
+
];
|
|
56667
|
+
if (failures.length > 0) throw new Error(failures.join("; "));
|
|
56668
|
+
}
|
|
56669
|
+
};
|
|
56670
|
+
};
|
|
56671
|
+
}
|
|
56450
56672
|
reissuePlanId = instance.plan.id;
|
|
56451
56673
|
reissueCaps = continuationBudget.caps;
|
|
56452
56674
|
const durableRequest = parseDirectives(original);
|
|
@@ -56499,6 +56721,12 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56499
56721
|
});
|
|
56500
56722
|
if (profileGate.kind === "refused") return ended;
|
|
56501
56723
|
const { profile } = profileGate;
|
|
56724
|
+
const legacyOwnershipRefusal = reserveLegacyOwnership?.();
|
|
56725
|
+
if (legacyOwnershipRefusal !== void 0) {
|
|
56726
|
+
await io.reply(legacyOwnershipRefusal.text);
|
|
56727
|
+
await recordPendingOperator();
|
|
56728
|
+
return ended;
|
|
56729
|
+
}
|
|
56502
56730
|
const earlyRepoTarget = agent.name === "review" ? resolveRepoTarget(deps, {
|
|
56503
56731
|
msg,
|
|
56504
56732
|
history,
|
|
@@ -56677,6 +56905,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56677
56905
|
card,
|
|
56678
56906
|
directives,
|
|
56679
56907
|
...reissuePlanId !== void 0 ? { reissuePlanId } : {},
|
|
56908
|
+
...beforeCoordinatorStart !== void 0 ? { beforeCoordinatorStart } : {},
|
|
56680
56909
|
...reissueCaps !== void 0 ? { reissueCaps: { ...reissueCaps, maxMinutes: Math.min(reissueCaps.maxMinutes, profile.minutes) } } : {},
|
|
56681
56910
|
sticky,
|
|
56682
56911
|
history,
|
|
@@ -57389,6 +57618,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
57389
57618
|
});
|
|
57390
57619
|
} finally {
|
|
57391
57620
|
clearInterval(setupHeartbeat);
|
|
57621
|
+
releaseLegacyOwnership?.();
|
|
57392
57622
|
ending.drain(void 0);
|
|
57393
57623
|
if (registered) deps.runBearers?.revoke(registered.id);
|
|
57394
57624
|
if (resume && ledgerRun && !runLoopStarted && !resumeRowClosed) {
|
|
@@ -57576,7 +57806,7 @@ async function dispatchClick(deps, click) {
|
|
|
57576
57806
|
activeRuns--;
|
|
57577
57807
|
}
|
|
57578
57808
|
}
|
|
57579
|
-
var activeRuns;
|
|
57809
|
+
var activeRuns, FULL_SHA2;
|
|
57580
57810
|
var init_dispatcher = __esm({
|
|
57581
57811
|
"../../src/core/dispatcher.ts"() {
|
|
57582
57812
|
"use strict";
|
|
@@ -57642,6 +57872,7 @@ var init_dispatcher = __esm({
|
|
|
57642
57872
|
init_types5();
|
|
57643
57873
|
init_decisionRecordReservation();
|
|
57644
57874
|
activeRuns = 0;
|
|
57875
|
+
FULL_SHA2 = /^[0-9a-f]{40}$/i;
|
|
57645
57876
|
}
|
|
57646
57877
|
});
|
|
57647
57878
|
|
|
@@ -71522,22 +71753,73 @@ var init_runnerOwnership = __esm({
|
|
|
71522
71753
|
recovered = /* @__PURE__ */ new Set();
|
|
71523
71754
|
claimedOwners = /* @__PURE__ */ new Map();
|
|
71524
71755
|
recoveredOwners = /* @__PURE__ */ new Map();
|
|
71756
|
+
reservations = /* @__PURE__ */ new Map();
|
|
71525
71757
|
localRecoveredInstances = /* @__PURE__ */ new Set();
|
|
71526
71758
|
recoveryComplete;
|
|
71527
71759
|
constructor(requiresRecovery) {
|
|
71528
71760
|
this.recoveryComplete = !requiresRecovery;
|
|
71529
71761
|
}
|
|
71762
|
+
/** Claim without displacing a different live runner. The read and write are
|
|
71763
|
+
* synchronous, so a caller owns the process-local fence before its next
|
|
71764
|
+
* awaited durable transition. Repeating the same owner's claim is idempotent. */
|
|
71530
71765
|
claim(repo, prNumber, owner) {
|
|
71531
71766
|
const key = runnerOwnedPullKey(repo, prNumber);
|
|
71767
|
+
const current = this.claimedOwners.get(key) ?? this.recoveredOwners.get(key);
|
|
71768
|
+
const alreadyOwned = this.claimed.has(key) || this.recovered.has(key);
|
|
71769
|
+
if (this.reservations.has(key)) return false;
|
|
71770
|
+
if (alreadyOwned && (owner === void 0 || current === void 0 || current.instanceId !== owner.instanceId || current.unit !== owner.unit))
|
|
71771
|
+
return false;
|
|
71532
71772
|
this.claimed.add(key);
|
|
71533
71773
|
if (owner !== void 0) this.claimedOwners.set(key, owner);
|
|
71774
|
+
return true;
|
|
71775
|
+
}
|
|
71776
|
+
/** Exclusively reserve an unowned pull request across an awaited durable
|
|
71777
|
+
* transition. Even the same runner identity cannot reserve it twice: the
|
|
71778
|
+
* returned token, not the owner fields, identifies the one caller. */
|
|
71779
|
+
reserve(repo, prNumber, owner) {
|
|
71780
|
+
const key = runnerOwnedPullKey(repo, prNumber);
|
|
71781
|
+
if (this.claimed.has(key) || this.recovered.has(key) || this.reservations.has(key)) return void 0;
|
|
71782
|
+
const token2 = Symbol(key);
|
|
71783
|
+
this.reservations.set(key, token2);
|
|
71784
|
+
this.claimed.add(key);
|
|
71785
|
+
this.claimedOwners.set(key, owner);
|
|
71786
|
+
return token2;
|
|
71787
|
+
}
|
|
71788
|
+
/** Move an exclusive recovery reservation to the durable attempt that is
|
|
71789
|
+
* about to start. Both the unforgeable token and the currently recorded
|
|
71790
|
+
* owner must still match; a stale caller can neither displace nor adopt a
|
|
71791
|
+
* successor. The transfer consumes the reservation but retains ownership. */
|
|
71792
|
+
transferReservation(repo, prNumber, token2, currentOwner, nextOwner) {
|
|
71793
|
+
const key = runnerOwnedPullKey(repo, prNumber);
|
|
71794
|
+
const current = this.claimedOwners.get(key);
|
|
71795
|
+
if (this.reservations.get(key) !== token2 || !this.claimed.has(key) || current?.instanceId !== currentOwner.instanceId || current.unit !== currentOwner.unit)
|
|
71796
|
+
return false;
|
|
71797
|
+
this.reservations.delete(key);
|
|
71798
|
+
this.claimedOwners.set(key, nextOwner);
|
|
71799
|
+
return true;
|
|
71534
71800
|
}
|
|
71535
|
-
|
|
71801
|
+
releaseReservation(repo, prNumber, token2) {
|
|
71536
71802
|
const key = runnerOwnedPullKey(repo, prNumber);
|
|
71803
|
+
if (this.reservations.get(key) !== token2) return false;
|
|
71804
|
+
this.reservations.delete(key);
|
|
71805
|
+
this.claimed.delete(key);
|
|
71806
|
+
this.claimedOwners.delete(key);
|
|
71807
|
+
return true;
|
|
71808
|
+
}
|
|
71809
|
+
/** Release unconditionally for the runner's ordinary end, or conditionally
|
|
71810
|
+
* for a provisional claim whose cleanup must not erase a successor. */
|
|
71811
|
+
release(repo, prNumber, owner) {
|
|
71812
|
+
const key = runnerOwnedPullKey(repo, prNumber);
|
|
71813
|
+
if (owner !== void 0) {
|
|
71814
|
+
const current = this.claimedOwners.get(key) ?? this.recoveredOwners.get(key);
|
|
71815
|
+
if (current?.instanceId !== owner.instanceId || current.unit !== owner.unit) return false;
|
|
71816
|
+
}
|
|
71817
|
+
this.reservations.delete(key);
|
|
71537
71818
|
this.claimed.delete(key);
|
|
71538
71819
|
this.recovered.delete(key);
|
|
71539
71820
|
this.claimedOwners.delete(key);
|
|
71540
71821
|
this.recoveredOwners.delete(key);
|
|
71822
|
+
return true;
|
|
71541
71823
|
}
|
|
71542
71824
|
owns(repo, prNumber) {
|
|
71543
71825
|
if (!this.recoveryComplete) throw new Error("runner ownership recovery is still in progress");
|
|
@@ -73090,6 +73372,68 @@ async function steerChild(body, deps) {
|
|
|
73090
73372
|
const steered = await deps.steerChild?.(body.runId, text, instance) ?? false;
|
|
73091
73373
|
return json3(200, { ok: true, outcome: steered ? "steered" : "not_live", at });
|
|
73092
73374
|
}
|
|
73375
|
+
async function bindOpenPullRequest(deps, instance, row2, pr, facts2) {
|
|
73376
|
+
if (row2 === void 0) return;
|
|
73377
|
+
const verified = facts2.verifiedHead;
|
|
73378
|
+
if (instance.base === void 0 || facts2.state !== "open" || facts2.sameRepoHead !== true || facts2.headBranchExists !== true || facts2.headRef !== row2.branch || facts2.baseRef !== instance.base || verified === void 0 || verified.repo.toLowerCase() !== instance.repo.toLowerCase() || verified.ref !== row2.branch || !/^[0-9a-f]{40}$/i.test(verified.sha) || facts2.headSha !== verified.sha || row2.pr !== void 0 && row2.pr.number !== pr.number)
|
|
73379
|
+
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
73380
|
+
const owner = { instanceId: instance.id, unit: row2.unit };
|
|
73381
|
+
const publication = {
|
|
73382
|
+
repo: instance.repo,
|
|
73383
|
+
pr: pr.number,
|
|
73384
|
+
headRef: row2.branch,
|
|
73385
|
+
baseRef: instance.base,
|
|
73386
|
+
expectedHeadSha: verified.sha,
|
|
73387
|
+
publicationRef: row2.branch,
|
|
73388
|
+
owner
|
|
73389
|
+
};
|
|
73390
|
+
const existing = row2.publication;
|
|
73391
|
+
if (existing !== void 0 && (existing.repo !== publication.repo || existing.pr !== publication.pr || existing.headRef !== publication.headRef || existing.baseRef !== publication.baseRef || existing.expectedHeadSha !== publication.expectedHeadSha || existing.publicationRef !== publication.publicationRef || !samePublicationOwner(existing.owner, owner)))
|
|
73392
|
+
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
73393
|
+
const fence = deps.runnerOwnership;
|
|
73394
|
+
if (fence === void 0) throw new PublicationBindingRefusal("publication_ownership_unknown");
|
|
73395
|
+
let priorOwner;
|
|
73396
|
+
try {
|
|
73397
|
+
priorOwner = fence.owner(instance.repo, pr.number);
|
|
73398
|
+
} catch {
|
|
73399
|
+
throw new PublicationBindingRefusal("publication_ownership_unknown");
|
|
73400
|
+
}
|
|
73401
|
+
if (priorOwner !== void 0 && !samePublicationOwner(priorOwner, owner))
|
|
73402
|
+
throw new PublicationBindingRefusal("publication_ownership_changed");
|
|
73403
|
+
if (!fence.claim(instance.repo, pr.number, owner))
|
|
73404
|
+
throw new PublicationBindingRefusal("publication_ownership_changed");
|
|
73405
|
+
let claimedHere = priorOwner === void 0;
|
|
73406
|
+
const releaseClaim = () => {
|
|
73407
|
+
if (!claimedHere) return;
|
|
73408
|
+
fence.release(instance.repo, pr.number, owner);
|
|
73409
|
+
claimedHere = false;
|
|
73410
|
+
};
|
|
73411
|
+
try {
|
|
73412
|
+
if (!samePublicationOwner(fence.owner(instance.repo, pr.number), owner)) {
|
|
73413
|
+
releaseClaim();
|
|
73414
|
+
throw new PublicationBindingRefusal("publication_ownership_unknown");
|
|
73415
|
+
}
|
|
73416
|
+
} catch (err2) {
|
|
73417
|
+
releaseClaim();
|
|
73418
|
+
if (err2 instanceof PublicationBindingRefusal) throw err2;
|
|
73419
|
+
throw new PublicationBindingRefusal("publication_ownership_unknown");
|
|
73420
|
+
}
|
|
73421
|
+
const replacement = { ...row2, pr, publication };
|
|
73422
|
+
if (JSON.stringify(replacement) === JSON.stringify(row2)) return;
|
|
73423
|
+
let replaced;
|
|
73424
|
+
try {
|
|
73425
|
+
replaced = await deps.instances.compareAndReplaceUnit(row2, replacement);
|
|
73426
|
+
} catch {
|
|
73427
|
+
releaseClaim();
|
|
73428
|
+
throw new PublicationBindingRefusal("publication_store_unavailable");
|
|
73429
|
+
}
|
|
73430
|
+
if (!replaced.ok) {
|
|
73431
|
+
releaseClaim();
|
|
73432
|
+
throw new PublicationBindingRefusal(
|
|
73433
|
+
replaced.reason === "stale" ? "publication_binding_stale" : "publication_store_unavailable"
|
|
73434
|
+
);
|
|
73435
|
+
}
|
|
73436
|
+
}
|
|
73093
73437
|
async function prCheck(body, deps) {
|
|
73094
73438
|
const id = parseInstanceId(body.parentInstanceId);
|
|
73095
73439
|
if (!id.ok) return json3(400, { ok: false, error: id.error });
|
|
@@ -73106,19 +73450,9 @@ async function prCheck(body, deps) {
|
|
|
73106
73450
|
const unit = await unitRowOf(deps, instance, body.unit);
|
|
73107
73451
|
if (!unit.ok) return unit.response;
|
|
73108
73452
|
const branch2 = unit.row?.branch ?? instance.branch;
|
|
73109
|
-
const
|
|
73110
|
-
if (!unit.row) return;
|
|
73111
|
-
|
|
73112
|
-
repo: instance.repo,
|
|
73113
|
-
pr: pr.number,
|
|
73114
|
-
headRef: facts2.headRef,
|
|
73115
|
-
baseRef: facts2.baseRef,
|
|
73116
|
-
expectedHeadSha: facts2.headSha,
|
|
73117
|
-
publicationRef: facts2.headRef,
|
|
73118
|
-
owner: { instanceId: instance.id, unit: unit.row.unit }
|
|
73119
|
-
} : void 0);
|
|
73120
|
-
if (unit.row.pr?.number !== pr.number || unit.row.pr.url !== pr.url || unit.row.publication === void 0 && publication !== void 0)
|
|
73121
|
-
await deps.instances.putUnits([{ ...unit.row, pr, ...publication !== void 0 ? { publication } : {} }]);
|
|
73453
|
+
const rememberTerminal = async (pr) => {
|
|
73454
|
+
if (!unit.row || unit.row.pr?.number === pr.number && unit.row.pr.url === pr.url) return;
|
|
73455
|
+
await deps.instances.putUnits([{ ...unit.row, pr }]);
|
|
73122
73456
|
};
|
|
73123
73457
|
try {
|
|
73124
73458
|
if (follow !== void 0) {
|
|
@@ -73127,12 +73461,12 @@ async function prCheck(body, deps) {
|
|
|
73127
73461
|
const fallbackUrl = unit.row?.pr?.url ?? `https://github.com/${instance.repo}/pull/${follow}`;
|
|
73128
73462
|
const state2 = pullRequestState(follow, fallbackUrl, facts2);
|
|
73129
73463
|
if (state2 === void 0) throw new Error(headBranchStateError(instance.repo, follow));
|
|
73130
|
-
|
|
73131
|
-
if (state2.state !== "open")
|
|
73132
|
-
|
|
73133
|
-
|
|
73134
|
-
|
|
73135
|
-
|
|
73464
|
+
const followedPr = { number: follow, url: String(state2.url) };
|
|
73465
|
+
if (state2.state !== "open") {
|
|
73466
|
+
await rememberTerminal(followedPr);
|
|
73467
|
+
return json3(200, { ok: true, ...state2, at });
|
|
73468
|
+
}
|
|
73469
|
+
await bindOpenPullRequest(deps, instance, unit.row, followedPr, facts2);
|
|
73136
73470
|
const prRef = { repo: instance.repo, number: follow };
|
|
73137
73471
|
const headSha = facts2.headSha;
|
|
73138
73472
|
const approved = entry && headSha !== void 0 ? await reviewPostedAt(deps, prRef, "approve", headSha) : void 0;
|
|
@@ -73163,16 +73497,16 @@ async function prCheck(body, deps) {
|
|
|
73163
73497
|
}
|
|
73164
73498
|
const open2 = await deps.findOpenPrByHead(instance.repo, branch2);
|
|
73165
73499
|
if (open2) {
|
|
73166
|
-
deps.runnerOwnership?.claim(instance.repo, open2.number, {
|
|
73167
|
-
instanceId: instance.id,
|
|
73168
|
-
unit: unit.row?.unit ?? body.unit
|
|
73169
|
-
});
|
|
73170
73500
|
const liveFacts = await deps.fetchPrFacts({ repo: instance.repo, number: open2.number });
|
|
73171
73501
|
if (liveFacts === void 0) throw new Error(`could not read ${instance.repo}#${open2.number}`);
|
|
73172
|
-
await remember({ number: open2.number, url: open2.htmlUrl }, liveFacts);
|
|
73173
73502
|
const current = pullRequestState(open2.number, open2.htmlUrl, liveFacts);
|
|
73174
73503
|
if (current === void 0) throw new Error(headBranchStateError(instance.repo, open2.number));
|
|
73175
|
-
|
|
73504
|
+
const discoveredPr = { number: open2.number, url: open2.htmlUrl };
|
|
73505
|
+
if (current.state !== "open") {
|
|
73506
|
+
await rememberTerminal(discoveredPr);
|
|
73507
|
+
return json3(200, { ok: true, ...current, at });
|
|
73508
|
+
}
|
|
73509
|
+
await bindOpenPullRequest(deps, instance, unit.row, discoveredPr, liveFacts);
|
|
73176
73510
|
const entryFacts = entry ? liveFacts : void 0;
|
|
73177
73511
|
const branchHead = entryFacts?.headSha;
|
|
73178
73512
|
const entryHead = branchHead ?? open2.headSha;
|
|
@@ -73223,22 +73557,27 @@ async function prCheck(body, deps) {
|
|
|
73223
73557
|
}
|
|
73224
73558
|
const recovered = await recoverPushedBranch(deps, instance, unit.row, branch2, recover.runId);
|
|
73225
73559
|
if (recovered.kind === "opened") {
|
|
73226
|
-
await remember({ number: recovered.pr.number, url: recovered.pr.htmlUrl });
|
|
73227
73560
|
const recoveredFacts = await deps.fetchPrFacts({ repo: instance.repo, number: recovered.pr.number });
|
|
73228
73561
|
if (recoveredFacts === void 0) throw new Error(`could not read ${instance.repo}#${recovered.pr.number}`);
|
|
73229
73562
|
const recoveredState = pullRequestState(recovered.pr.number, recovered.pr.htmlUrl, recoveredFacts);
|
|
73230
73563
|
if (recoveredState === void 0) throw new Error(headBranchStateError(instance.repo, recovered.pr.number));
|
|
73564
|
+
const recoveredPr = { number: recovered.pr.number, url: recovered.pr.htmlUrl };
|
|
73565
|
+
if (recoveredState.state === "open")
|
|
73566
|
+
await bindOpenPullRequest(deps, instance, unit.row, recoveredPr, recoveredFacts);
|
|
73567
|
+
else await rememberTerminal(recoveredPr);
|
|
73231
73568
|
return json3(200, { ok: true, ...recoveredState, at });
|
|
73232
73569
|
}
|
|
73233
73570
|
return json3(200, { ok: true, state: "none", unrecovered: recovered.why, at });
|
|
73234
73571
|
}
|
|
73235
|
-
await remember({ number: merged.number, url: merged.htmlUrl });
|
|
73236
73572
|
const mergedFacts = await deps.fetchPrFacts({ repo: instance.repo, number: merged.number });
|
|
73237
73573
|
if (mergedFacts === void 0) throw new Error(`could not read ${instance.repo}#${merged.number}`);
|
|
73238
73574
|
const state = pullRequestState(merged.number, merged.htmlUrl, mergedFacts);
|
|
73239
73575
|
if (state?.state !== "merged") throw new Error(`${instance.repo}#${merged.number} no longer reads merged`);
|
|
73576
|
+
await rememberTerminal({ number: merged.number, url: merged.htmlUrl });
|
|
73240
73577
|
return json3(200, { ok: true, ...state, at });
|
|
73241
73578
|
} catch (err2) {
|
|
73579
|
+
if (err2 instanceof PublicationBindingRefusal)
|
|
73580
|
+
return json3(409, { ok: false, error: err2.reason, message: "the open pull request was not durably bound", at });
|
|
73242
73581
|
return json3(502, { ok: false, error: "github_unavailable", message: describe12(err2), at });
|
|
73243
73582
|
}
|
|
73244
73583
|
}
|
|
@@ -74461,7 +74800,7 @@ function createAdminCoordinatorHandler(deps) {
|
|
|
74461
74800
|
})();
|
|
74462
74801
|
};
|
|
74463
74802
|
}
|
|
74464
|
-
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe12, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, CHILD_SUPERSESSION_REASONS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
74803
|
+
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe12, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, CHILD_SUPERSESSION_REASONS, PublicationBindingRefusal, samePublicationOwner, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
74465
74804
|
var init_adminCoordinator = __esm({
|
|
74466
74805
|
"../../src/channels/adminCoordinator.ts"() {
|
|
74467
74806
|
"use strict";
|
|
@@ -74517,6 +74856,14 @@ var init_adminCoordinator = __esm({
|
|
|
74517
74856
|
REVIEW_POSTED_RECHECK_MS = 2e3;
|
|
74518
74857
|
githubSecondStart = (at) => Math.floor(at / SECOND_MS) * SECOND_MS;
|
|
74519
74858
|
CHILD_SUPERSESSION_REASONS = /* @__PURE__ */ new Set(["merged", "closed", "head_moved", "branch_deleted"]);
|
|
74859
|
+
PublicationBindingRefusal = class extends Error {
|
|
74860
|
+
constructor(reason2) {
|
|
74861
|
+
super(reason2);
|
|
74862
|
+
this.reason = reason2;
|
|
74863
|
+
}
|
|
74864
|
+
reason;
|
|
74865
|
+
};
|
|
74866
|
+
samePublicationOwner = (left, right) => left?.instanceId === right.instanceId && left.unit === right.unit;
|
|
74520
74867
|
ROUND_OUTCOMES = [
|
|
74521
74868
|
"started",
|
|
74522
74869
|
"pr_opened",
|
|
@@ -75544,6 +75891,7 @@ async function runBot() {
|
|
|
75544
75891
|
) : new NullMetricsService();
|
|
75545
75892
|
const sweepState = { roundSpent: /* @__PURE__ */ new Set() };
|
|
75546
75893
|
const runnerOwnership = new RunnerOwnershipFence(capabilities.runLedger);
|
|
75894
|
+
deps.runnerOwnership = runnerOwnership;
|
|
75547
75895
|
const pullsWiring = /* @__PURE__ */ (() => {
|
|
75548
75896
|
const state = sweepState;
|
|
75549
75897
|
const chains = /* @__PURE__ */ new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreplane/switchboard",
|
|
3
|
-
"version": "1.261.
|
|
3
|
+
"version": "1.261.4",
|
|
4
4
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://openswitchboard.dev",
|