@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.2",
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.2",
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.2",
21063
+ "version": "1.261.4",
21064
21064
  "license": "Apache-2.0",
21065
21065
  "dependencies": {
21066
21066
  "@earendil-works/pi-ai": "0.85.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.261.2",
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",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.261.2",
3
- "commit": "652eb70199d24be067ee91e1c9e14f48770a594c",
4
- "builtAt": "2026-09-24T02:09:02.047Z"
2
+ "version": "1.261.4",
3
+ "commit": "9f4d0f4c9645ea67ecab3ede2455e658f38a7d4c",
4
+ "builtAt": "2026-09-24T16:28:36.099Z"
5
5
  }
@@ -1137,14 +1137,16 @@ async function runUnit(
1137
1137
  : {}),
1138
1138
  },
1139
1139
  ...(state.pr !== undefined ? { pr: state.pr } : {}),
1140
- // A review_pending ending names the child's own last push so the next
1141
- // attempt's pre-check can start at the review round (the row's lastPush)
1142
- // — an idled one the same, off the idle's `from` (record 0051).
1143
- ...(ending.kind === "review_pending" && ending.headSha !== undefined
1144
- ? { headSha: ending.headSha }
1145
- : ending.kind === "idle" && ending.why === "review_pending" && ending.from !== undefined
1146
- ? { headSha: ending.from }
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 put = write === "replace" ? await deps.instances.replace(instance) : await deps.instances.put(instance);
54778
- if (!put.ok) {
54779
- if (put.reason === "unavailable")
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
- "plan_history_unavailable",
54782
- "\u26A0\uFE0F The plan runner needs run history on the state Worker (`runHistory.worker`): the instance record could not be written, so nothing ran."
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
- return refused5(
54785
- "plan_runner_conflict",
54786
- `\u{1F6AB} A runner for \`${instance.id}\` was just recorded by another request, so this request started nothing; the recorded runner owns the pipeline.`
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 ship request's attachments could not be written, so nothing ran."
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
- let answer;
54817
- try {
54818
- answer = await deps.create(instance.id);
54819
- } catch (err2) {
54820
- answer = { kind: "unanswered", reason: describe7(err2) };
54821
- }
54822
- switch (answer.kind) {
54823
- case "created": {
54824
- log(`[ship] ${input.msg.threadKey}: handed to the plan runner ${instance.id} (${units.length} unit(s))`);
54825
- const replaced = write === "replace" ? ["the records of an earlier attempt that never started were replaced"] : [];
54826
- return {
54827
- status: "completed",
54828
- reply: handedOff([...where, ...replaced]),
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
- case "failed":
54840
- case "unanswered":
54841
- log(`[ship] ${input.msg.threadKey}: the plan runner ${instance.id} could not be started \u2014 ${answer.reason}`);
54842
- return refused5(
54843
- "plan_start_failed",
54844
- `\u26A0\uFE0F This is a bug: the plan runner could not be started (${answer.reason}), nothing ran, and no automatic start retry was scheduled.`
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
- const expectedHead = owner.unit.lastPush;
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
- release(repo, prNumber) {
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 remember = async (pr, facts2) => {
73110
- if (!unit.row) return;
73111
- const publication = unit.row.publication ?? (facts2?.state === "open" && facts2.sameRepoHead === true && facts2.headBranchExists === true && typeof facts2.headRef === "string" && typeof facts2.baseRef === "string" && typeof facts2.headSha === "string" && /^[0-9a-f]{40}$/i.test(facts2.headSha) ? {
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
- await remember({ number: follow, url: String(state2.url) }, facts2);
73131
- if (state2.state !== "open") return json3(200, { ok: true, ...state2, at });
73132
- deps.runnerOwnership?.claim(instance.repo, follow, {
73133
- instanceId: instance.id,
73134
- unit: unit.row?.unit ?? body.unit
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
- if (current.state !== "open") return json3(200, { ok: true, ...current, at });
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.2",
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",