@coreplane/switchboard 1.261.1 → 1.261.2

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.261.1",
3
+ "version": "1.261.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "switchboard",
9
- "version": "1.261.1",
9
+ "version": "1.261.2",
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.1",
21063
+ "version": "1.261.2",
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.1",
3
+ "version": "1.261.2",
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.1",
3
- "commit": "3746a1c5fa2d30bebd66baec1fb3534a1f927e3a",
4
- "builtAt": "2026-09-23T21:48:10.555Z"
2
+ "version": "1.261.2",
3
+ "commit": "652eb70199d24be067ee91e1c9e14f48770a594c",
4
+ "builtAt": "2026-09-24T02:09:02.047Z"
5
5
  }
@@ -293,6 +293,20 @@ export interface RunFinishedPayload {
293
293
  parentInstanceId: string;
294
294
  }
295
295
 
296
+ /** Durable authority for publishing another commit to an existing pull
297
+ * request. Every identity field is repeated deliberately: publication is
298
+ * allowed only when the unit, checkout and a fresh remote read all agree with
299
+ * this exact record. */
300
+ export interface ExistingPrPublicationBinding {
301
+ repo: string;
302
+ pr: number;
303
+ headRef: string;
304
+ baseRef: string;
305
+ expectedHeadSha: string;
306
+ publicationRef: string;
307
+ owner: { instanceId: string; unit: string };
308
+ }
309
+
296
310
  /** What a coordinator's spawn stamps on the child's every row: the instance
297
311
  * the child belongs to and the key the spawn carried — and, for the child's
298
312
  * own post-step, the base its pull request targets. */
@@ -310,6 +324,10 @@ export interface CoordinatorTag {
310
324
  * back off its ledger events; `coordinatorFields` still leaves it off the
311
325
  * rows, so rows and records keep the shape written before it existed. */
312
326
  base?: string;
327
+ /** The existing-PR publication authority this child received from its
328
+ * durable unit row and current Workflow round. Published on the run's event
329
+ * stream so a rehost retains the same fence. */
330
+ publication?: ExistingPrPublicationBinding;
313
331
  }
314
332
 
315
333
  /** The tag as the two flat record fields, or nothing — so a row, a summary
@@ -489,6 +507,9 @@ export interface CoordinatorUnit {
489
507
  * — no pre-check, no branch, no round 0. A task string's row only; written by
490
508
  * the hand-off, read by the driver into the machine's input. */
491
509
  resume?: { pr: number; headSha?: string; url?: string };
510
+ /** Exact existing-PR publication authority. Absent for a fresh unit branch;
511
+ * an adopted or resumed PR may publish only through this binding. */
512
+ publication?: ExistingPrPublicationBinding;
492
513
  /** The decision-record number reserved at admission for this unit. A unit
493
514
  * that writes a record carries it through every attempt and briefs its child
494
515
  * as `record: NNNN`; the child never scans the directory for a number. */
@@ -549,6 +570,23 @@ const isResume = (v: unknown): boolean =>
549
570
  isFinite(v.pr) &&
550
571
  (v.headSha === undefined || isText(v.headSha)) &&
551
572
  (v.url === undefined || isText(v.url, 2048));
573
+ const isFullSha = (v: unknown): v is string => typeof v === "string" && /^[0-9a-f]{40}$/i.test(v);
574
+ const isPublication = (v: unknown): v is ExistingPrPublicationBinding =>
575
+ isObject(v) &&
576
+ typeof v.repo === "string" &&
577
+ REPO_SLUG.test(v.repo) &&
578
+ typeof v.pr === "number" &&
579
+ Number.isInteger(v.pr) &&
580
+ v.pr > 0 &&
581
+ isText(v.headRef) &&
582
+ isText(v.baseRef) &&
583
+ isFullSha(v.expectedHeadSha) &&
584
+ isText(v.publicationRef) &&
585
+ isObject(v.owner) &&
586
+ typeof v.owner.instanceId === "string" &&
587
+ INSTANCE_ID_PATTERN.test(v.owner.instanceId) &&
588
+ typeof v.owner.unit === "string" &&
589
+ UNIT_PATTERN.test(v.owner.unit);
552
590
  const isThread = (v: unknown): boolean => isObject(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
553
591
 
554
592
  /** Structural check on a record from outside the process (a Worker response, an HTTP body). */
@@ -663,6 +701,7 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
663
701
  if (r.issue !== undefined && !isFinite(r.issue)) return false;
664
702
  if (r.pr !== undefined && !isPr(r.pr)) return false;
665
703
  if (r.resume !== undefined && !isResume(r.resume)) return false;
704
+ if (r.publication !== undefined && !isPublication(r.publication)) return false;
666
705
  if (r.record !== undefined && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
667
706
  if (r.lastPush !== undefined && !isText(r.lastPush)) return false;
668
707
  if (r.segments !== undefined && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
@@ -208,6 +208,11 @@ export type RunNoteKind =
208
208
  * after the model's last turn — the release itself runs after the record
209
209
  * is sealed — and set on the card's label too, so the loss is never silent. */
210
210
  | "work_left_behind"
211
+ /** An existing pull request's exact publication fence denied the push while
212
+ * the workspace held a local checkpoint. The workspace stays attached
213
+ * instead of being discarded or redirected; the summary names the head,
214
+ * counts, denial and that retention beyond the run remains unverified. */
215
+ | "publication_blocked"
211
216
  /** The run's ending may have left a command running in its workspace — a
212
217
  * call cut by the ending's abort or interrupt, or open when the run failed —
213
218
  * so the release tears the workspace down rather than pair it for the
@@ -356,6 +361,7 @@ export const RUN_NOTE_KINDS = [
356
361
  "ledger_untracked",
357
362
  "rebind_refused",
358
363
  "work_left_behind",
364
+ "publication_blocked",
359
365
  "workspace_torn_down",
360
366
  "pr_not_opened",
361
367
  "pr_head_unverified",
@@ -953,7 +959,15 @@ export type RunEvent =
953
959
  * instead of letting the binding ref — the unit branch itself — stand in.
954
960
  * `base` is absent when the spawn knew none; the post-step then falls to
955
961
  * the coordinator store's `instance.base`. Additive: unknown → ignored. */
956
- | { type: "coordinator_tag"; parentInstanceId: string; unit?: string; base?: string; seq?: number; at?: number }
962
+ | {
963
+ type: "coordinator_tag";
964
+ parentInstanceId: string;
965
+ unit?: string;
966
+ base?: string;
967
+ publication?: import("./coordinator/contract.js").ExistingPrPublicationBinding;
968
+ seq?: number;
969
+ at?: number;
970
+ }
957
971
  /** The plan runner instance a ship run's hand-off created (record 0051 R2;
958
972
  * docs/reference/specs/run-history.md item 2): published by the ship branch
959
973
  * after `handOffToCoordinator` succeeds, straight to the registry like
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ChatMessage } from "../chatMessage.js";
7
7
  import { PROVIDER_FAILURE_CAUSES, type ProviderFailureCause, type ToolDef } from "../provider.js";
8
+ import type { Verbosity } from "../verbosity.js";
8
9
  import type { ChannelVisibility } from "../authz/types.js";
9
10
  import type { RunProfile } from "../../config/profile.js";
10
11
  import type { RunEvent } from "../runEvents.js";
@@ -60,6 +61,9 @@ export interface LiveRunMeta {
60
61
  /** The app that relayed the request for the person (authorization.md item 14): a resume or restart keeps app ∩ person at the gates. */
61
62
  postedBy?: string;
62
63
  effort?: string;
64
+ /** The request's resolved display level. Hosted parents have no model loop
65
+ * to rebuild it from, and their input event deliberately omits directives. */
66
+ verbosity?: Verbosity;
63
67
  /** Decision-record reservation assigned before the attach, so a restart of
64
68
  * the admitted task keeps the same brief and process environment. */
65
69
  record?: string;
@@ -502,6 +502,8 @@ export type Brief =
502
502
  kind: "findings";
503
503
  unit: string;
504
504
  pr: number;
505
+ /** The exact reviewed head the fix round may replace. */
506
+ headSha?: string;
505
507
  /** The review run normally supplies the findings. An adopted attempt
506
508
  * can recover them from the last posted verdict marker instead. */
507
509
  reviewRunId?: string;
@@ -1466,6 +1468,7 @@ function briefFor(s: UnitPipelineState, round: RoundRef): Brief {
1466
1468
  kind: "findings",
1467
1469
  unit,
1468
1470
  pr,
1471
+ ...(s.lastReviewHead !== undefined ? { headSha: s.lastReviewHead } : {}),
1469
1472
  ...(reviewRunId !== undefined ? { reviewRunId } : {}),
1470
1473
  ...(reviewRunId === undefined || (answers !== undefined && answers.length > 0) ? { findings: issued } : {}),
1471
1474
  ...(answers !== undefined && answers.length > 0 ? { answers } : {}),
@@ -1,3 +1,5 @@
1
+ import type { Verbosity } from "./verbosity.js";
2
+
1
3
  // Channel abstraction. A channel (Slack, CLI, Discord, HTTP, ...) is only a
2
4
  // transport: it receives text from a user somewhere, hands it to the core
3
5
  // dispatcher as an IncomingMessage, and provides a ChannelIO for the core to
@@ -306,8 +308,10 @@ export interface ChannelIO {
306
308
  * the row lives in the config object until the click or the expiry.
307
309
  */
308
310
  offer?(offer: ConfirmationOffer): Promise<void>;
309
- /** Create a progress indicator. Adapters may return a no-op handle. */
310
- status(initial: StatusUpdate): Promise<StatusHandle>;
311
+ /** Create a progress indicator. Adapters may return a no-op handle. The
312
+ * resolved verbosity reaches the transport so native lifecycle chrome can
313
+ * follow the same display contract as replies and cards. */
314
+ status(initial: StatusUpdate, display?: { verbosity?: Verbosity }): Promise<StatusHandle>;
311
315
  /**
312
316
  * Prior turns of this conversation, oldest first, excluding the triggering
313
317
  * message and any bot status noise. Adapters without history return [].
package/dist/cli.js CHANGED
@@ -4186,6 +4186,7 @@ var init_runEvents = __esm({
4186
4186
  "ledger_untracked",
4187
4187
  "rebind_refused",
4188
4188
  "work_left_behind",
4189
+ "publication_blocked",
4189
4190
  "workspace_torn_down",
4190
4191
  "pr_not_opened",
4191
4192
  "pr_head_unverified",
@@ -5792,6 +5793,7 @@ function isCoordinatorUnit(v) {
5792
5793
  if (r.issue !== void 0 && !isFinite(r.issue)) return false;
5793
5794
  if (r.pr !== void 0 && !isPr(r.pr)) return false;
5794
5795
  if (r.resume !== void 0 && !isResume(r.resume)) return false;
5796
+ if (r.publication !== void 0 && !isPublication(r.publication)) return false;
5795
5797
  if (r.record !== void 0 && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
5796
5798
  if (r.lastPush !== void 0 && !isText(r.lastPush)) return false;
5797
5799
  if (r.segments !== void 0 && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
@@ -5855,7 +5857,7 @@ async function sendChildSignal(workflow, signal2) {
5855
5857
  return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
5856
5858
  }
5857
5859
  }
5858
- var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, UNIT_PATTERN, UNIT_KEY_PATTERN, CHILD_INTERRUPTED_EVENT_PREFIX, CHILD_RESUMED_EVENT_PREFIX, THREAD_EVENT_MAX_BYTES, serializedBytes, isThreadEventStagedFile, isThreadEventAttachment, isThreadEventMode, UNIT_NUDGE_EVENT_PREFIX, CHECKS_SETTLED_EVENT_PREFIX, PULL_MERGED_EVENT_PREFIX, IDLE_WHY_MAX, REPO_SLUG, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isCount, isObject, isPr, isResume, isThread, isRoundGate, isHumanGatePending, isUnitIdle, isSegment, isUnitWakeAnswer;
5860
+ var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, UNIT_PATTERN, UNIT_KEY_PATTERN, CHILD_INTERRUPTED_EVENT_PREFIX, CHILD_RESUMED_EVENT_PREFIX, THREAD_EVENT_MAX_BYTES, serializedBytes, isThreadEventStagedFile, isThreadEventAttachment, isThreadEventMode, UNIT_NUDGE_EVENT_PREFIX, CHECKS_SETTLED_EVENT_PREFIX, PULL_MERGED_EVENT_PREFIX, IDLE_WHY_MAX, REPO_SLUG, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isCount, isObject, isPr, isResume, isFullSha, isPublication, isThread, isRoundGate, isHumanGatePending, isUnitIdle, isSegment, isUnitWakeAnswer;
5859
5861
  var init_contract2 = __esm({
5860
5862
  "../../src/core/coordinator/contract.ts"() {
5861
5863
  "use strict";
@@ -5895,6 +5897,8 @@ var init_contract2 = __esm({
5895
5897
  isObject = (v) => typeof v === "object" && v !== null;
5896
5898
  isPr = (v) => isObject(v) && isFinite(v.number) && isText(v.url, 2048);
5897
5899
  isResume = (v) => isObject(v) && isFinite(v.pr) && (v.headSha === void 0 || isText(v.headSha)) && (v.url === void 0 || isText(v.url, 2048));
5900
+ isFullSha = (v) => typeof v === "string" && /^[0-9a-f]{40}$/i.test(v);
5901
+ isPublication = (v) => isObject(v) && typeof v.repo === "string" && REPO_SLUG.test(v.repo) && typeof v.pr === "number" && Number.isInteger(v.pr) && v.pr > 0 && isText(v.headRef) && isText(v.baseRef) && isFullSha(v.expectedHeadSha) && isText(v.publicationRef) && isObject(v.owner) && typeof v.owner.instanceId === "string" && INSTANCE_ID_PATTERN.test(v.owner.instanceId) && typeof v.owner.unit === "string" && UNIT_PATTERN.test(v.owner.unit);
5898
5902
  isThread = (v) => isObject(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
5899
5903
  isRoundGate = (v) => isObject(v) && isAddressSeverity(v.level) && Array.isArray(v.findings) && v.findings.every((f) => typeof f === "string");
5900
5904
  isHumanGatePending = (v) => isObject(v) && isObject(v.pr) && typeof v.pr.number === "number" && Number.isInteger(v.pr.number) && v.pr.number > 0 && isText(v.pr.url) && typeof v.round === "number" && Number.isInteger(v.round) && v.round >= 1 && Array.isArray(v.findings) && v.findings.every((finding) => isFindingShape(finding)) && (v.verdict === "approve" || v.verdict === "request_changes") && (v.reviewRunId === void 0 || isText(v.reviewRunId)) && (v.headSha === void 0 || isText(v.headSha)) && (v.askedAt === void 0 || isFinite(v.askedAt));
@@ -7128,9 +7132,9 @@ function makeSsrfLookup(resolve13) {
7128
7132
  const family = (ip) => ip.includes(":") ? 6 : 4;
7129
7133
  resolve13(hostname3).then(
7130
7134
  (ips) => {
7131
- const blocked = ips.find((ip) => ipInBlockedRange(ip));
7132
- if (blocked) {
7133
- cb(new BlockedUrlError(`host ${hostname3} resolves to blocked address ${blocked}`));
7135
+ const blocked2 = ips.find((ip) => ipInBlockedRange(ip));
7136
+ if (blocked2) {
7137
+ cb(new BlockedUrlError(`host ${hostname3} resolves to blocked address ${blocked2}`));
7134
7138
  return;
7135
7139
  }
7136
7140
  if (ips.length === 0) {
@@ -36133,7 +36137,7 @@ function cardActivity(e) {
36133
36137
  if (e.type === "tool_call" && e.command !== void 0) return { kind: "command", tool: e.tool, command: e.command };
36134
36138
  return { kind: "line", text: activityLine(e) };
36135
36139
  }
36136
- function quietActivity(activity) {
36140
+ function compactActivity(activity) {
36137
36141
  if (activity?.kind === "command") return { kind: "line", text: `\u2192 ${activity.tool}` };
36138
36142
  return activity;
36139
36143
  }
@@ -36238,7 +36242,9 @@ function cardLines(trace, opts) {
36238
36242
  });
36239
36243
  return { ...shape ? { shape } : {}, ...opts.queued ? { queued: opts.queued } : {} };
36240
36244
  }
36241
- async function replyCommandOutput(io, parsed, text) {
36245
+ async function replyCommandOutput(io, parsed, text, display = {}) {
36246
+ if (parsed.kind === "invoke" && parsed.id === "steer.run" && display.ok === true && !shows(display.verbosity ?? "quiet", "verbose"))
36247
+ return;
36242
36248
  if (!io.attach || text.length <= LONG_COMMAND_REPLY_CHARS) return io.reply(text);
36243
36249
  const nl = text.indexOf("\n");
36244
36250
  const lead = nl === -1 ? text : text.slice(0, nl);
@@ -38783,6 +38789,79 @@ var init_commandChat = __esm({
38783
38789
  }
38784
38790
  });
38785
38791
 
38792
+ // ../../src/directives.ts
38793
+ function parseBudgetMinutes(value) {
38794
+ if (!/^\d+$/.test(value)) return void 0;
38795
+ const minutes = Number(value);
38796
+ return minutes >= MIN_BOUNDARY_MINUTES ? minutes : void 0;
38797
+ }
38798
+ function lastThreadDirectives(history) {
38799
+ const out = {};
38800
+ for (const h of history) {
38801
+ if (h.role !== "user") continue;
38802
+ for (const m of h.text.matchAll(DIRECTIVE_RE)) {
38803
+ if (m[1] === "model") out.model = m[2];
38804
+ else if (m[1] === "effort" && isEffort(m[2])) out.effort = m[2];
38805
+ else if (m[1] === "verbosity" && isVerbosity(m[2])) out.verbosity = m[2];
38806
+ }
38807
+ }
38808
+ return out;
38809
+ }
38810
+ function parseRenewals(value) {
38811
+ if (!/^\d+$/.test(value)) return void 0;
38812
+ const count = Number(value);
38813
+ return count <= GRANT_RENEWALS_MAX ? count : void 0;
38814
+ }
38815
+ function parseDirectives(input) {
38816
+ const out = { text: input };
38817
+ let text = input;
38818
+ const head = HEAD_AGENT_RE.exec(input);
38819
+ if (head !== null && AGENTS[head[1]] !== void 0) {
38820
+ out.agent = head[1];
38821
+ text = text.replace(head[0], " ");
38822
+ }
38823
+ for (const m of input.matchAll(DIRECTIVE_RE)) {
38824
+ const [token2, key, value] = m;
38825
+ if (key === "model") {
38826
+ out.model = value;
38827
+ } else if (key === "effort") {
38828
+ if (!isEffort(value)) continue;
38829
+ out.effort = value;
38830
+ } else if (key === "budget") {
38831
+ const minutes = parseBudgetMinutes(value);
38832
+ if (minutes === void 0) continue;
38833
+ out.budget = minutes;
38834
+ } else if (key === "severity") {
38835
+ if (!isAddressSeverity(value)) continue;
38836
+ out.severity = value;
38837
+ } else if (key === "renewals") {
38838
+ const count = parseRenewals(value);
38839
+ if (count === void 0) continue;
38840
+ out.renewals = count;
38841
+ } else {
38842
+ if (!isVerbosity(value)) continue;
38843
+ out.verbosity = value;
38844
+ }
38845
+ text = text.replace(token2, " ");
38846
+ }
38847
+ out.text = text.replace(/\s+/g, " ").trim();
38848
+ return out;
38849
+ }
38850
+ var DIRECTIVE_RE, HEAD_AGENT_RE;
38851
+ var init_directives = __esm({
38852
+ "../../src/directives.ts"() {
38853
+ "use strict";
38854
+ init_registry();
38855
+ init_validate();
38856
+ init_budgets();
38857
+ init_effort();
38858
+ init_shipPipeline();
38859
+ init_verbosity();
38860
+ DIRECTIVE_RE = /(?:^|\s)(model|effort|budget|severity|renewals|verbosity)[:=](\S+)/g;
38861
+ HEAD_AGENT_RE = /^\s*agent[:=](\S+)/;
38862
+ }
38863
+ });
38864
+
38786
38865
  // ../../src/core/runRegistry/runControl.ts
38787
38866
  var RunControl;
38788
38867
  var init_runControl = __esm({
@@ -39782,6 +39861,7 @@ var init_commandRun = __esm({
39782
39861
  async function answerChatCommand(deps, ctx) {
39783
39862
  const { msg, io, ending, trace } = ctx;
39784
39863
  const root = trace.root;
39864
+ const verbosity2 = deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity);
39785
39865
  if (deps.commands) {
39786
39866
  const chatCmd = parseChatCommand(msg.text, deps.commands);
39787
39867
  if (chatCmd) {
@@ -39801,7 +39881,7 @@ async function answerChatCommand(deps, ctx) {
39801
39881
  confirmations: deps.confirmations
39802
39882
  })
39803
39883
  );
39804
- } : () => root.span("post.reply", () => replyCommandOutput(io, chatCmd, res.text));
39884
+ } : () => root.span("post.reply", () => replyCommandOutput(io, chatCmd, res.text, { verbosity: verbosity2, ok: res.ok }));
39805
39885
  await ending.sealAfterReply(async () => {
39806
39886
  }, replyFn);
39807
39887
  if (res.followUp) postSettledOutcome(res.followUp, io, root);
@@ -39817,6 +39897,7 @@ var init_fastPath = __esm({
39817
39897
  init_commandChat();
39818
39898
  init_reply();
39819
39899
  init_refusal();
39900
+ init_directives();
39820
39901
  init_commandRun();
39821
39902
  init_commandRun();
39822
39903
  }
@@ -40110,79 +40191,6 @@ var init_references = __esm({
40110
40191
  }
40111
40192
  });
40112
40193
 
40113
- // ../../src/directives.ts
40114
- function parseBudgetMinutes(value) {
40115
- if (!/^\d+$/.test(value)) return void 0;
40116
- const minutes = Number(value);
40117
- return minutes >= MIN_BOUNDARY_MINUTES ? minutes : void 0;
40118
- }
40119
- function lastThreadDirectives(history) {
40120
- const out = {};
40121
- for (const h of history) {
40122
- if (h.role !== "user") continue;
40123
- for (const m of h.text.matchAll(DIRECTIVE_RE)) {
40124
- if (m[1] === "model") out.model = m[2];
40125
- else if (m[1] === "effort" && isEffort(m[2])) out.effort = m[2];
40126
- else if (m[1] === "verbosity" && isVerbosity(m[2])) out.verbosity = m[2];
40127
- }
40128
- }
40129
- return out;
40130
- }
40131
- function parseRenewals(value) {
40132
- if (!/^\d+$/.test(value)) return void 0;
40133
- const count = Number(value);
40134
- return count <= GRANT_RENEWALS_MAX ? count : void 0;
40135
- }
40136
- function parseDirectives(input) {
40137
- const out = { text: input };
40138
- let text = input;
40139
- const head = HEAD_AGENT_RE.exec(input);
40140
- if (head !== null && AGENTS[head[1]] !== void 0) {
40141
- out.agent = head[1];
40142
- text = text.replace(head[0], " ");
40143
- }
40144
- for (const m of input.matchAll(DIRECTIVE_RE)) {
40145
- const [token2, key, value] = m;
40146
- if (key === "model") {
40147
- out.model = value;
40148
- } else if (key === "effort") {
40149
- if (!isEffort(value)) continue;
40150
- out.effort = value;
40151
- } else if (key === "budget") {
40152
- const minutes = parseBudgetMinutes(value);
40153
- if (minutes === void 0) continue;
40154
- out.budget = minutes;
40155
- } else if (key === "severity") {
40156
- if (!isAddressSeverity(value)) continue;
40157
- out.severity = value;
40158
- } else if (key === "renewals") {
40159
- const count = parseRenewals(value);
40160
- if (count === void 0) continue;
40161
- out.renewals = count;
40162
- } else {
40163
- if (!isVerbosity(value)) continue;
40164
- out.verbosity = value;
40165
- }
40166
- text = text.replace(token2, " ");
40167
- }
40168
- out.text = text.replace(/\s+/g, " ").trim();
40169
- return out;
40170
- }
40171
- var DIRECTIVE_RE, HEAD_AGENT_RE;
40172
- var init_directives = __esm({
40173
- "../../src/directives.ts"() {
40174
- "use strict";
40175
- init_registry();
40176
- init_validate();
40177
- init_budgets();
40178
- init_effort();
40179
- init_shipPipeline();
40180
- init_verbosity();
40181
- DIRECTIVE_RE = /(?:^|\s)(model|effort|budget|severity|renewals|verbosity)[:=](\S+)/g;
40182
- HEAD_AGENT_RE = /^\s*agent[:=](\S+)/;
40183
- }
40184
- });
40185
-
40186
40194
  // ../../src/execution/githubRepoProbe.ts
40187
40195
  function githubRepoProbe(opts) {
40188
40196
  const fetchImpl = opts.fetch ?? fetch;
@@ -41563,10 +41571,8 @@ function presetRequestOf(line4) {
41563
41571
  async function executeOperatorDecision(deps, ctx) {
41564
41572
  const { event, io, msg } = ctx;
41565
41573
  const surface = io.offer ? "chat" : "typed";
41566
- const verbose = shows(
41567
- deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity),
41568
- "verbose"
41569
- );
41574
+ const verbosity2 = deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity);
41575
+ const verbose = shows(verbosity2, "verbose");
41570
41576
  const answered = { kind: "answered" };
41571
41577
  if (ctx.owner?.kind === "pipeline") return { kind: "fold" };
41572
41578
  if (event.outcome === "question") {
@@ -41673,7 +41679,7 @@ async function executeOperatorDecision(deps, ctx) {
41673
41679
  if (verbose) await io.reply(receipt);
41674
41680
  const res = await runChatCommand(deps, msg, io, invocation, ctx.ending, ctx.trace, { operator: executedEvent });
41675
41681
  carried = true;
41676
- if (res.text.length > 0) await io.reply(res.text);
41682
+ if (res.text.length > 0) await replyCommandOutput(io, invocation, res.text, { verbosity: verbosity2, ok: res.ok });
41677
41683
  }
41678
41684
  if (!carried) await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
41679
41685
  return answered;
@@ -42352,7 +42358,7 @@ async function openAckCard(deps, ctx) {
42352
42358
  });
42353
42359
  if (route2) shell.note("debug", routeReasonLabel(route2.reason, route2.collapsed));
42354
42360
  const card = coalesceStatus(
42355
- await root.span("dispatch.ack_card", () => io.status(shell.ack())),
42361
+ await root.span("dispatch.ack_card", () => io.status(shell.ack(), { verbosity: resolved.verbosity })),
42356
42362
  deps.statusUpdateMinMs ?? STATUS_UPDATE_MIN_MS
42357
42363
  );
42358
42364
  trace.bindCard({ setupLabel: (label) => shell.setSetupLabel(label) });
@@ -44079,7 +44085,7 @@ function watchedChild(io, on) {
44079
44085
  on.replied(text);
44080
44086
  await io.reply(text);
44081
44087
  },
44082
- status: (initial) => io.status(initial),
44088
+ status: (initial, display) => io.status(initial, display),
44083
44089
  history: () => io.history(),
44084
44090
  runStarted: (started) => {
44085
44091
  io.runStarted?.(started);
@@ -46164,6 +46170,7 @@ async function claimRun(deps, ctx) {
46164
46170
  parentInstanceId: coordinator.parentInstanceId,
46165
46171
  ...unit !== void 0 ? { unit } : {},
46166
46172
  ...coordinator.base !== void 0 ? { base: coordinator.base } : {},
46173
+ ...coordinator.publication !== void 0 ? { publication: coordinator.publication } : {},
46167
46174
  at: clock4()
46168
46175
  });
46169
46176
  }
@@ -47446,7 +47453,7 @@ function established(facts2) {
47446
47453
  else if (w?.kind === "salvaged")
47447
47454
  tree = `What the tree held was pushed to \`${w.branch}\`${w.head ? ` at \`${shortSha2(w.head)}\`` : ""} by the budget salvage, unreviewed \u2014 a follow-up starts from it.`;
47448
47455
  else if (w?.kind === "left")
47449
- tree = w.fate === "kept" ? `${counted(w)} sit in the workspace, kept for this thread until it idles out \u2014 a follow-up here reuses them.` : w.fate === "discarded" ? `${counted(w)} were left in the tree and discarded at the run's end.` : `${counted(w)} were left in the tree, which is torn down since a command may still be running in it.`;
47456
+ tree = w.fate === "kept" ? `${counted(w)} sit in the workspace, kept for this thread until it idles out \u2014 a follow-up here reuses them.` : w.fate === "retained_unverified" ? `${counted(w)} remain in the attached workspace after the existing-PR push was blocked; retention beyond this run is unverified, so no continuation is promised.` : w.fate === "discarded" ? `${counted(w)} were left in the tree and discarded at the run's end.` : `${counted(w)} were left in the tree, which is torn down since a command may still be running in it.`;
47450
47457
  const description = facts2?.description === "submitted" ? "The PR description was submitted." : facts2?.description === "not_submitted" ? "No PR description was submitted." : "";
47451
47458
  return [tree, description].filter((s) => s !== "").join(" ");
47452
47459
  }
@@ -49979,10 +49986,28 @@ function judgeBashTimeout(timeout, ctx) {
49979
49986
  return refused4(commandPastLoopEndRefusal(Math.round(timeout), Math.max(0, Math.floor(leftMs / 1e3))));
49980
49987
  }
49981
49988
  function judgePush(tail, ctx) {
49982
- const [remote, refspec] = pushArguments(tail);
49989
+ const [remote, refspec, ...additionalRefspecs] = pushArguments(tail);
49983
49990
  if (remote !== void 0 && remote !== "origin") {
49984
49991
  return refused4(`repo:use \u2014 push to remote \`${remote}\`, not the run's repository (origin)`);
49985
49992
  }
49993
+ if (ctx.publication !== void 0) {
49994
+ const publication = ctx.publication.authority;
49995
+ if ("blocked" in publication) return refused4(`repo:use \u2014 existing-PR publication blocked: ${publication.blocked}`);
49996
+ if (refspec === void 0)
49997
+ return refused4("repo:use \u2014 existing-PR publication requires the explicit owned destination");
49998
+ if (additionalRefspecs.length > 0)
49999
+ return refused4("repo:use \u2014 existing-PR publication allows exactly one owned destination");
50000
+ const destination2 = refspec.replace(/^\+/, "").split(":").pop() ?? refspec;
50001
+ const branch3 = destination2 === "HEAD" ? ctx.branch : destination2.replace(/^refs\/heads\//, "");
50002
+ if (branch3 !== publication.ref)
50003
+ return refused4(`repo:use \u2014 push to \`${branch3}\`, not the owned publication ref ${publication.ref}`);
50004
+ const lease = `--force-with-lease=refs/heads/${publication.ref}:${publication.expectedHeadSha}`;
50005
+ if (!tail.split(/\s+/).includes(lease))
50006
+ return refused4(
50007
+ `repo:use \u2014 existing-PR publication requires \`${lease}\` so concurrent movement fails atomically`
50008
+ );
50009
+ return allowed;
50010
+ }
49986
50011
  if (refspec === void 0) return allowed;
49987
50012
  const destination = refspec.replace(/^\+/, "").split(":").pop() ?? refspec;
49988
50013
  if (ctx.branch !== void 0) {
@@ -50102,7 +50127,7 @@ function relayedToolDefinitions(harness) {
50102
50127
  }
50103
50128
  function authorizeToolCall(harness, ask) {
50104
50129
  harness.gateSaw(ask.toolCallId);
50105
- const blocked = harness.toolsBlocked();
50130
+ const blocked2 = harness.toolsBlocked();
50106
50131
  const refuse3 = (reason2) => {
50107
50132
  harness.emit({
50108
50133
  type: "run_note",
@@ -50111,7 +50136,7 @@ function authorizeToolCall(harness, ask) {
50111
50136
  });
50112
50137
  return { allow: false, reason: reason2 };
50113
50138
  };
50114
- if (blocked !== void 0) return refuse3(blocked);
50139
+ if (blocked2 !== void 0) return refuse3(blocked2);
50115
50140
  if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
50116
50141
  const verdict = judgeToolCall(ask.tool, ask.input, harness.rules);
50117
50142
  if (verdict.verdict === "allowed") return { allow: true };
@@ -51991,6 +52016,49 @@ var init_resume = __esm({
51991
52016
  }
51992
52017
  });
51993
52018
 
52019
+ // ../../src/core/existingPrPublication.ts
52020
+ function verifyExistingPrPublication(durable, local, remote) {
52021
+ if (durable === void 0) return blocked("the durable existing-PR binding is missing");
52022
+ if (!fullSha(durable.expectedHeadSha)) return blocked("the durable expected head is not a full commit SHA");
52023
+ if (durable.headRef !== durable.publicationRef)
52024
+ return blocked("the durable head ref and owned publication ref do not agree");
52025
+ if (local.repo !== durable.repo) return blocked("the resolved repository does not match the durable binding");
52026
+ if (local.pr !== durable.pr) return blocked("the resolved pull request does not match the durable binding");
52027
+ if (local.ref !== durable.headRef) return blocked("the resolved head ref does not match the durable binding");
52028
+ if (local.baseRef !== durable.baseRef) return blocked("the resolved base ref does not match the durable binding");
52029
+ if (!fullSha(local.requestHeadSha)) return blocked("the request head is missing or not a full commit SHA");
52030
+ if (local.requestHeadSha.toLowerCase() !== durable.expectedHeadSha.toLowerCase())
52031
+ return blocked("the request head does not match the durable expected commit");
52032
+ if (local.workspaceRef !== durable.publicationRef)
52033
+ return blocked("the checked-out publication ref does not match the durable binding");
52034
+ if (!fullSha(local.workspaceHeadSha)) return blocked("the checked-out head is missing or not a full commit SHA");
52035
+ if (local.workspaceHeadSha.toLowerCase() !== durable.expectedHeadSha.toLowerCase())
52036
+ return blocked("the checked-out head does not match the durable expected commit");
52037
+ if (!sameOwner(local.owner, durable.owner)) return blocked("the publication owner changed");
52038
+ if (remote === void 0) return blocked("the fresh pull-request read is unavailable");
52039
+ if (remote.state !== "open") return blocked("the bound pull request is no longer open");
52040
+ if (remote.sameRepoHead !== true) return blocked("the pull request head is not in the bound repository");
52041
+ if (remote.headBranchExists !== true) return blocked("the pull request head branch is missing or unknown");
52042
+ if (remote.headRef !== durable.headRef) return blocked("the pull request head ref moved");
52043
+ if (remote.baseRef !== durable.baseRef) return blocked("the pull request base ref moved");
52044
+ if (!fullSha(remote.headSha)) return blocked("the fresh pull-request head is missing or not a full commit SHA");
52045
+ if (remote.headSha.toLowerCase() !== durable.expectedHeadSha.toLowerCase())
52046
+ return blocked("the pull request head moved from the durable expected commit");
52047
+ return {
52048
+ ok: true,
52049
+ publication: { ref: durable.publicationRef, expectedHeadSha: durable.expectedHeadSha.toLowerCase() }
52050
+ };
52051
+ }
52052
+ var fullSha, sameOwner, blocked;
52053
+ var init_existingPrPublication = __esm({
52054
+ "../../src/core/existingPrPublication.ts"() {
52055
+ "use strict";
52056
+ fullSha = (value) => value !== void 0 && /^[0-9a-f]{40}$/i.test(value);
52057
+ sameOwner = (left, right) => left?.instanceId === right.instanceId && left.unit === right.unit;
52058
+ blocked = (reason2) => ({ ok: false, reason: reason2 });
52059
+ }
52060
+ });
52061
+
51994
52062
  // ../../src/execution/githubComments.ts
51995
52063
  async function postReviewComment(target, body) {
51996
52064
  const codePoints = [...body].length;
@@ -52234,7 +52302,26 @@ async function salvageBudgetPush(executor, opts, span) {
52234
52302
  }
52235
52303
  const unpushed = parseCountOutput(await run2("git rev-list --count HEAD --not --remotes")) ?? 0;
52236
52304
  if (!dirty && !endingCheckpoint && unpushed === 0) return { pushed: false, summary: words.nothing };
52237
- await run2(`git push origin ${shellQuote(`HEAD:refs/heads/${opts.branch}`)}`);
52305
+ if (opts.publication !== void 0 && "blocked" in opts.publication)
52306
+ return {
52307
+ pushed: false,
52308
+ summary: `${words.failedLead}: existing-PR publication is blocked (${opts.publication.blocked}) \u2014 the commit remains unpublished in the bound workspace; no alternate ref was created`
52309
+ };
52310
+ const lease = opts.publication !== void 0 ? ` --force-with-lease=${shellQuote(`refs/heads/${opts.publication.ref}:${opts.publication.expectedHeadSha}`)}` : "";
52311
+ try {
52312
+ await run2(`git push${lease} origin ${shellQuote(`HEAD:refs/heads/${opts.branch}`)}`);
52313
+ } catch (err2) {
52314
+ if (opts.publication !== void 0) {
52315
+ const detail = err2 instanceof Error ? err2.message : String(err2);
52316
+ const publicationBlocked = `the atomic leased push was rejected: ${detail}`;
52317
+ return {
52318
+ pushed: false,
52319
+ publicationBlocked,
52320
+ summary: `${words.failedLead}: existing-PR publication is blocked (${publicationBlocked}) \u2014 the commit remains unpublished in the bound workspace; no alternate ref was created`
52321
+ };
52322
+ }
52323
+ throw err2;
52324
+ }
52238
52325
  const head = parseRevParseOutput(await probe("git rev-parse HEAD"));
52239
52326
  return {
52240
52327
  pushed: true,
@@ -52961,6 +53048,12 @@ async function runLoop(deps, ctx) {
52961
53048
  return void 0;
52962
53049
  }
52963
53050
  })();
53051
+ let existingPrPublication;
53052
+ let existingPrPublicationFence;
53053
+ const blockExistingPrPublication = (reason2) => {
53054
+ existingPrPublication = { blocked: reason2 };
53055
+ if (existingPrPublicationFence !== void 0) existingPrPublicationFence.authority = existingPrPublication;
53056
+ };
52964
53057
  const identitySeam = deps.identityRewrite;
52965
53058
  const startStateRead = identitySeam !== void 0 && isCodingPrRun && repoCtx.repo !== void 0 ? (async () => {
52966
53059
  const repo = repoCtx.repo;
@@ -52997,14 +53090,14 @@ async function runLoop(deps, ctx) {
52997
53090
  );
52998
53091
  return pace ? ` \xB7 ${pace}` : "";
52999
53092
  };
53093
+ const showsActivity = shows(resolved.verbosity, "verbose");
53000
53094
  const chatty = shows(resolved.verbosity, "debug");
53001
53095
  const showBookkeeping = shows(resolved.verbosity, "verbose");
53002
53096
  const currentFrame = () => shell.live({
53003
- // the pace mark rides every level (the stall signal is a fact, not chatter); the quiet suffix is debug's
53004
- suffix: chatty ? `${quietSuffix(clock4() - lastActivityAt, inFlightTool)}${cardPace()}` : cardPace(),
53097
+ suffix: showsActivity ? chatty ? `${quietSuffix(clock4() - lastActivityAt, inFlightTool)}${cardPace()}` : cardPace() : "",
53005
53098
  notice: shutdownNotice(),
53006
53099
  detail: [checklist],
53007
- activity: chatty ? lastActivity : quietActivity(lastActivity)
53100
+ activity: showsActivity ? chatty ? lastActivity : compactActivity(lastActivity) : void 0
53008
53101
  });
53009
53102
  const checklistAsLeft = () => checklist;
53010
53103
  const checklistCheckedOff = () => checklist?.replace(/^(\s*)[○✱](?=\s)/gm, "$1\u2713");
@@ -53239,8 +53332,18 @@ async function runLoop(deps, ctx) {
53239
53332
  });
53240
53333
  const salvaged = "skipped" in target ? { pushed: false, summary: target.skipped } : await root.span(
53241
53334
  cue === "budget" ? "run.budget_salvage" : "run.work_salvage",
53242
- (span) => salvageBudgetPush(executor, { branch: target.branch, cue }, span)
53335
+ (span) => salvageBudgetPush(
53336
+ executor,
53337
+ {
53338
+ branch: target.branch,
53339
+ cue,
53340
+ ...existingPrPublication !== void 0 ? { publication: existingPrPublication } : {}
53341
+ },
53342
+ span
53343
+ )
53243
53344
  );
53345
+ if ("publicationBlocked" in salvaged && salvaged.publicationBlocked !== void 0)
53346
+ blockExistingPrPublication(salvaged.publicationBlocked);
53244
53347
  const cleanCompletion = cue === "completion" && !salvaged.pushed && (/ended with nothing to preserve/i.test(salvaged.summary) || "skipped" in target && (observedUncommitted ?? 0) === 0 && (observedUnpushed ?? 0) === 0);
53245
53348
  if (cleanCompletion) return;
53246
53349
  workSalvageAttempted = true;
@@ -53255,7 +53358,10 @@ async function runLoop(deps, ctx) {
53255
53358
  salvagedTo = { branch: target.branch, head: salvaged.head };
53256
53359
  } else if (salvaged.pushed && !("skipped" in target)) salvagedTo = { branch: target.branch };
53257
53360
  if (salvaged.pushed) await observeWorkspaceNow();
53258
- else if ("skipped" in target || /failed|not attempted/i.test(salvaged.summary))
53361
+ else if (existingPrPublication !== void 0 && "blocked" in existingPrPublication) {
53362
+ await observeWorkspaceNow();
53363
+ shell.note("quiet", salvaged.summary);
53364
+ } else if ("skipped" in target || /failed|not attempted/i.test(salvaged.summary))
53259
53365
  shell.note("quiet", salvaged.summary);
53260
53366
  };
53261
53367
  let relaunchEndedRun = false;
@@ -53281,7 +53387,9 @@ async function runLoop(deps, ctx) {
53281
53387
  return true;
53282
53388
  };
53283
53389
  const tailSkipped = () => run2.control.requested === "hard" || relaunchEndedRun || latchReviewStoppedBeforeStart();
53390
+ const keepBlockedPublicationWorkspace = () => existingPrPublication !== void 0 && "blocked" in existingPrPublication && run2.control.requested !== "hard" && !commandInFlight && !gateBypassed && ((observedUncommitted ?? 0) > 0 || (observedUnpushed ?? 0) > 0);
53284
53391
  const releaseWorkspace = (span) => {
53392
+ if (keepBlockedPublicationWorkspace()) return Promise.resolve();
53285
53393
  const events2 = recordEvents();
53286
53394
  const pushed = pushedBranchesOf(events2);
53287
53395
  return round2.release({
@@ -53434,7 +53542,38 @@ async function runLoop(deps, ctx) {
53434
53542
  if (!providerCfg)
53435
53543
  throw new Error(`the ${harness.name} harness found no provider named ${providerName} in the config`);
53436
53544
  const prBase = repoCtx.baseRef ?? await coordinatorBase;
53437
- const ownBranch = prBase !== void 0 ? binding?.ref ?? repoCtx.ref : void 0;
53545
+ if (coordinator?.publication !== void 0) {
53546
+ const publication = coordinator.publication;
53547
+ const fresh = await (deps.fetchPrFacts ?? fetchPullRequestFacts)({
53548
+ repo: publication.repo,
53549
+ number: publication.pr
53550
+ }).catch(() => void 0);
53551
+ const verified = verifyExistingPrPublication(
53552
+ publication,
53553
+ {
53554
+ repo: repoCtx.repo,
53555
+ pr: repoCtx.pr,
53556
+ ref: repoCtx.ref,
53557
+ baseRef: prBase,
53558
+ requestHeadSha: repoCtx.headSha,
53559
+ workspaceRef: binding?.ref,
53560
+ workspaceHeadSha: binding?.sha,
53561
+ owner: {
53562
+ instanceId: coordinator.parentInstanceId,
53563
+ unit: unitOfIdempotencyKey(coordinator.idempotencyKey) ?? ""
53564
+ }
53565
+ },
53566
+ fresh
53567
+ );
53568
+ existingPrPublication = verified.ok ? verified.publication : { blocked: verified.reason };
53569
+ existingPrPublicationFence = { authority: existingPrPublication };
53570
+ if (!verified.ok)
53571
+ shell.note(
53572
+ "quiet",
53573
+ `Existing-PR publication is blocked: ${verified.reason}. Work stays on the bound checkout; no alternate branch or pull request will be published.`
53574
+ );
53575
+ }
53576
+ const ownBranch = coordinator?.publication !== void 0 ? coordinator.publication.publicationRef : prBase !== void 0 ? binding?.ref ?? repoCtx.ref : void 0;
53438
53577
  const protectedBranches = [
53439
53578
  ...new Set(
53440
53579
  (prBase !== void 0 ? [prBase] : [binding?.ref, repoCtx.ref]).filter(
@@ -53494,8 +53633,18 @@ async function runLoop(deps, ctx) {
53494
53633
  const branch2 = ownBranch;
53495
53634
  const salvaged = await root.span(
53496
53635
  "run.compaction_salvage",
53497
- (span) => salvageBudgetPush(executor, { branch: branch2, cue: "compaction" }, span)
53636
+ (span) => salvageBudgetPush(
53637
+ executor,
53638
+ {
53639
+ branch: branch2,
53640
+ cue: "compaction",
53641
+ ...existingPrPublication !== void 0 ? { publication: existingPrPublication } : {}
53642
+ },
53643
+ span
53644
+ )
53498
53645
  );
53646
+ if (salvaged.publicationBlocked !== void 0)
53647
+ blockExistingPrPublication(salvaged.publicationBlocked);
53499
53648
  onEvent({
53500
53649
  type: "run_note",
53501
53650
  kind: "compaction_salvage",
@@ -53508,7 +53657,8 @@ async function runLoop(deps, ctx) {
53508
53657
  rules: {
53509
53658
  checkout: binding?.workspace ?? "/workspace",
53510
53659
  ...ownBranch !== void 0 ? { branch: ownBranch } : {},
53511
- protectedBranches
53660
+ protectedBranches,
53661
+ ...existingPrPublicationFence !== void 0 ? { publication: existingPrPublicationFence } : {}
53512
53662
  },
53513
53663
  ...round2.selection.backend ? { backend: round2.selection.backend } : {},
53514
53664
  span: root,
@@ -53724,6 +53874,12 @@ async function runLoop(deps, ctx) {
53724
53874
  if (descriptionTurnRan) await preserveCodingChildWork("completion");
53725
53875
  await endHarness();
53726
53876
  const leftBehind = isCodingPrRun && !tailSkipped() && !workSalvageAttempted ? workLeftBehindOf({ uncommittedChanges: observedUncommitted, unpushedCommits: observedUnpushed }) : void 0;
53877
+ const blockedCheckpoint = keepBlockedPublicationWorkspace() && existingPrPublication !== void 0 && "blocked" in existingPrPublication && observedUncommitted !== void 0 && observedUnpushed !== void 0 && (observedUncommitted > 0 || observedUnpushed > 0) ? {
53878
+ reason: existingPrPublication.blocked,
53879
+ uncommitted: observedUncommitted,
53880
+ unpushed: observedUnpushed,
53881
+ ...observedHead !== void 0 ? { head: observedHead } : {}
53882
+ } : void 0;
53727
53883
  const endingFacts = () => {
53728
53884
  if (profile.machine === "none") return { workspace: { kind: "none" } };
53729
53885
  if (!isCodingPrRun || tailSkipped()) return { workspace: { kind: "unread" } };
@@ -53742,11 +53898,16 @@ async function runLoop(deps, ctx) {
53742
53898
  kind: "left",
53743
53899
  uncommitted: observedUncommitted,
53744
53900
  unpushed: observedUnpushed,
53745
- fate: commandInFlight ? "torn_down" : profile.machine === "repo-resident" ? "discarded" : "kept"
53901
+ fate: blockedCheckpoint !== void 0 ? "retained_unverified" : commandInFlight ? "torn_down" : profile.machine === "repo-resident" ? "discarded" : "kept"
53746
53902
  };
53747
53903
  return { workspace, description: prDescription !== void 0 ? "submitted" : "not_submitted" };
53748
53904
  };
53749
- if (leftBehind) {
53905
+ if (blockedCheckpoint !== void 0) {
53906
+ const checkpoint = blockedCheckpoint.head ? ` at ${blockedCheckpoint.head.slice(0, 7)}` : "";
53907
+ const summary = `the existing-PR push is blocked (${blockedCheckpoint.reason}); kept the local checkpoint${checkpoint} with ${blockedCheckpoint.uncommitted} uncommitted change(s) and ${blockedCheckpoint.unpushed} unpushed commit(s) in the run workspace; no alternate ref or pull request was created, and retention beyond this run is unverified`;
53908
+ events.publish({ type: "run_note", kind: "publication_blocked", summary: oneLine(summary), at: clock4() });
53909
+ shell.note("quiet", `\u26A0\uFE0F ${summary}`);
53910
+ } else if (leftBehind) {
53750
53911
  events.publish({
53751
53912
  type: "run_note",
53752
53913
  kind: "work_left_behind",
@@ -53762,7 +53923,7 @@ async function runLoop(deps, ctx) {
53762
53923
  at: clock4()
53763
53924
  });
53764
53925
  }
53765
- if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted) {
53926
+ if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted && !(existingPrPublication !== void 0 && "blocked" in existingPrPublication)) {
53766
53927
  const instance = coordinator && deps.coordinatorInstances ? await deps.coordinatorInstances.get(coordinator.parentInstanceId).catch(() => void 0) : void 0;
53767
53928
  const requester = instance?.userId === msg.userId ? instance : msg;
53768
53929
  const requestedLogin = identitySeam !== void 0 ? await identitySeam.requestedLogin(requester.userId).catch(() => void 0) : void 0;
@@ -54033,6 +54194,7 @@ var init_runLoop = __esm({
54033
54194
  "use strict";
54034
54195
  init_actor();
54035
54196
  init_predicate();
54197
+ init_contract2();
54036
54198
  init_decisionRecordReservation();
54037
54199
  init_profile();
54038
54200
  init_provider();
@@ -54048,6 +54210,7 @@ var init_runLoop = __esm({
54048
54210
  init_resume();
54049
54211
  init_githubPulls();
54050
54212
  init_repoContext();
54213
+ init_existingPrPublication();
54051
54214
  init_prDescription();
54052
54215
  init_handoff();
54053
54216
  init_reviewVerdict();
@@ -54202,7 +54365,7 @@ async function shipPreflight(input) {
54202
54365
  `\u{1F6AB} ${where}'s head branch lives on a fork, not on \`${repo}\` \u2014 ship cannot drive it.`
54203
54366
  );
54204
54367
  }
54205
- const branch2 = facts2.headRef ?? repoCtx.ref;
54368
+ const branch2 = facts2.headRef;
54206
54369
  if (!branch2) {
54207
54370
  return refuse2(
54208
54371
  "ship_preflight_head_unknown",
@@ -54211,10 +54374,34 @@ async function shipPreflight(input) {
54211
54374
  `\u{1F6AB} Could not determine ${where}'s head branch, so ship cannot bind the thread's worktree to it \u2014 refusing fail-closed.`
54212
54375
  );
54213
54376
  }
54214
- const ownBase = facts2.baseRef ?? repoCtx.baseRef;
54377
+ const headSha = facts2.headSha;
54378
+ if (headSha === void 0 || !/^[0-9a-f]{40}$/i.test(headSha)) {
54379
+ return refuse2(
54380
+ "ship_preflight_head_unknown",
54381
+ "full head commit unknown",
54382
+ "not started (head commit unknown)",
54383
+ `\u{1F6AB} Could not determine ${where}'s full head commit, so ship cannot fence publication to it \u2014 refusing fail-closed.`
54384
+ );
54385
+ }
54386
+ if (facts2.baseRef === void 0) {
54387
+ return refuse2(
54388
+ "ship_preflight_head_unknown",
54389
+ "base branch unknown",
54390
+ "not started (base branch unknown)",
54391
+ `\u{1F6AB} Could not determine ${where}'s base branch, so ship cannot bind publication to it \u2014 refusing fail-closed.`
54392
+ );
54393
+ }
54394
+ const ownBase = facts2.baseRef;
54215
54395
  const ownBaseExists = ownBase !== void 0 && input.refExists ? await input.refExists(repo, ownBase).catch(() => void 0) : void 0;
54216
- const baseFallback = ownBase !== void 0 && ownBaseExists === false ? { requested: ownBase } : void 0;
54217
- const base = resolveBaseRef([ownBaseExists === false ? void 0 : ownBase], info?.defaultBranch);
54396
+ if (ownBaseExists === false) {
54397
+ return refuse2(
54398
+ "ship_preflight_head_unknown",
54399
+ "pull request base branch missing",
54400
+ "not started (base branch missing)",
54401
+ `\u{1F6AB} ${where}'s base branch \`${ownBase}\` is not present in \`${repo}\`, so ship cannot establish an exact publication binding \u2014 refusing fail-closed.`
54402
+ );
54403
+ }
54404
+ const base = resolveBaseRef([ownBase], info?.defaultBranch);
54218
54405
  const autoMerge = facts2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: facts2.autoMergeEnabled } : {};
54219
54406
  if (task) {
54220
54407
  return {
@@ -54223,8 +54410,7 @@ async function shipPreflight(input) {
54223
54410
  repo,
54224
54411
  branch: branch2,
54225
54412
  base,
54226
- adopt: { pr: repoCtx.pr, ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {} },
54227
- ...baseFallback !== void 0 ? { baseFallback } : {},
54413
+ adopt: { pr: repoCtx.pr, headSha, ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {} },
54228
54414
  ...autoMerge
54229
54415
  }
54230
54416
  };
@@ -54237,10 +54423,9 @@ async function shipPreflight(input) {
54237
54423
  base,
54238
54424
  resume: {
54239
54425
  pr: repoCtx.pr,
54240
- headSha: facts2.headSha ?? repoCtx.headSha,
54426
+ headSha,
54241
54427
  ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {}
54242
54428
  },
54243
- ...baseFallback !== void 0 ? { baseFallback } : {},
54244
54429
  ...autoMerge
54245
54430
  }
54246
54431
  };
@@ -54432,15 +54617,27 @@ async function rowsFor(deps, p, selected, instanceId, carried) {
54432
54617
  const previous = carried?.get(u.id);
54433
54618
  const taskKey = p.recordTasks?.[u.id];
54434
54619
  const record3 = taskKey !== void 0 ? await reserve(p.identity.repo, taskKey, previous?.record) : previous?.record;
54620
+ const branch2 = p.entryBranch ?? u.branch;
54621
+ const existing = p.adopt ?? p.resume;
54622
+ const publication = existing !== void 0 && p.entryBranch !== void 0 ? {
54623
+ repo: p.identity.repo,
54624
+ pr: existing.pr,
54625
+ headRef: p.entryBranch,
54626
+ baseRef: p.base,
54627
+ expectedHeadSha: existing.headSha,
54628
+ publicationRef: p.entryBranch,
54629
+ owner: { instanceId, unit: u.id }
54630
+ } : void 0;
54435
54631
  return {
54436
54632
  instanceId,
54437
54633
  unit: u.id,
54438
54634
  slug: u.slug,
54439
54635
  title: u.title,
54440
- branch: p.entryBranch ?? u.branch,
54636
+ branch: branch2,
54441
54637
  dependsOn: u.dependsOn,
54442
54638
  rounds: [],
54443
54639
  ...p.resume !== void 0 ? { resume: p.resume } : {},
54640
+ ...publication !== void 0 ? { publication } : {},
54444
54641
  ...previous?.lastPush !== void 0 ? { lastPush: previous.lastPush } : {},
54445
54642
  ...record3 !== void 0 ? { record: record3 } : {}
54446
54643
  };
@@ -55006,6 +55203,7 @@ async function runShipBranch(deps, msg, io, ctx) {
55006
55203
  ...msg.authenticatedAs !== void 0 ? { authenticatedAs: msg.authenticatedAs } : {},
55007
55204
  ...msg.postedBy !== void 0 ? { postedBy: msg.postedBy } : {},
55008
55205
  ...entry.resume !== void 0 ? { pr: entry.resume.pr } : {},
55206
+ verbosity: ctx.verbosity,
55009
55207
  profile
55010
55208
  },
55011
55209
  card: card.handle ?? null,
@@ -55349,7 +55547,13 @@ function carriedCoordinatorTag(row2, events) {
55349
55547
  if (typeof parentInstanceId !== "string" || typeof idempotencyKey !== "string") return void 0;
55350
55548
  const tag = events.find((e) => e.type === "coordinator_tag");
55351
55549
  const base = tag?.type === "coordinator_tag" ? tag.base : void 0;
55352
- return { parentInstanceId, idempotencyKey, ...base !== void 0 ? { base } : {} };
55550
+ const publication = tag?.type === "coordinator_tag" ? tag.publication : void 0;
55551
+ return {
55552
+ parentInstanceId,
55553
+ idempotencyKey,
55554
+ ...base !== void 0 ? { base } : {},
55555
+ ...publication !== void 0 ? { publication } : {}
55556
+ };
55353
55557
  }
55354
55558
  function lostWorkspaceNote(why, restarts) {
55355
55559
  const lost = `resumed after a restart: the run's workspace could not be re-attached (${why})`;
@@ -65712,7 +65916,8 @@ async function handle(deps, { client, statusClient }, ev, intake) {
65712
65916
  span,
65713
65917
  {
65714
65918
  staging: deps.artifacts !== void 0,
65715
- maxBytesPerMessage: deps.config.config.artifacts?.inbound?.maxBytesPerMessage ?? ARTIFACT_DEFAULTS.maxBytesPerMessage
65919
+ maxBytesPerMessage: deps.config.config.artifacts?.inbound?.maxBytesPerMessage ?? ARTIFACT_DEFAULTS.maxBytesPerMessage,
65920
+ verbosityFor: (channelId, userId, request2) => deps.config.verbosityFor(channelId, userId, request2)
65716
65921
  },
65717
65922
  relayAppsOf(deps),
65718
65923
  intake
@@ -65794,10 +65999,25 @@ async function receiveSlackMessage(client, ev, span, policy, relayApps, intake)
65794
65999
  threadRuns = gated.thread;
65795
66000
  }
65796
66001
  }
65797
- client.reactions.add({ channel: ev.channel, timestamp: ev.ts, name: ACK_EMOJI }).catch((err2) => {
65798
- if (!err2.message.includes("already_reacted")) console.error(`[ack] ${err2.message}`);
65799
- });
65800
- const delayNote = ev.caughtUp ? new SlackIO(client, ev).reply(catchUpDelayNote(ev.ts, systemClock())).catch((err2) => {
66002
+ const requester = await resolveSlackRequester(
66003
+ client,
66004
+ {
66005
+ channel: ev.channel,
66006
+ ts: ev.ts,
66007
+ threadTs: ev.threadTs,
66008
+ ...ev.user !== void 0 ? { user: ev.user } : {},
66009
+ text: ev.rawText ?? ev.text,
66010
+ ...ev.poster !== void 0 ? { poster: ev.poster } : {}
66011
+ },
66012
+ relayApps
66013
+ );
66014
+ span.setAttrs({ requester: requester.resolvedBy });
66015
+ const verbosity2 = policy.verbosityFor?.(`${PLATFORM3}:${ev.channel}`, requester.userId, parseDirectives(ev.text).verbosity) ?? policy.verbosity ?? DEFAULT_VERBOSITY;
66016
+ if (shows(verbosity2, "verbose"))
66017
+ client.reactions.add({ channel: ev.channel, timestamp: ev.ts, name: ACK_EMOJI }).catch((err2) => {
66018
+ if (!err2.message.includes("already_reacted")) console.error(`[ack] ${err2.message}`);
66019
+ });
66020
+ const delayNote = ev.caughtUp && shows(verbosity2, "verbose") ? new SlackIO(client, ev).reply(catchUpDelayNote(ev.ts, systemClock())).catch((err2) => {
65801
66021
  console.error(`[catch-up] ${ev.channel}:${ev.ts} delay note failed: ${err2.message}`);
65802
66022
  }) : Promise.resolve();
65803
66023
  const [imagePass, documentPass] = await Promise.all([
@@ -65820,19 +66040,6 @@ async function receiveSlackMessage(client, ev, span, policy, relayApps, intake)
65820
66040
  });
65821
66041
  const unsupported = imagePass.skippedFiles.filter((f) => documentPass.skippedFiles.includes(f));
65822
66042
  const { staged, skipped } = stagedFiles(unsupported, { ...policy, messageId: ev.ts });
65823
- const requester = await resolveSlackRequester(
65824
- client,
65825
- {
65826
- channel: ev.channel,
65827
- ts: ev.ts,
65828
- threadTs: ev.threadTs,
65829
- ...ev.user !== void 0 ? { user: ev.user } : {},
65830
- text: ev.rawText ?? ev.text,
65831
- ...ev.poster !== void 0 ? { poster: ev.poster } : {}
65832
- },
65833
- relayApps
65834
- );
65835
- span.setAttrs({ requester: requester.resolvedBy });
65836
66043
  const [channelName, userName, team] = await Promise.all([
65837
66044
  resolveChannelName(client, ev.channel),
65838
66045
  requester.slackUserId !== void 0 ? resolveUserName(client, requester.slackUserId) : Promise.resolve(requester.userName),
@@ -66152,6 +66359,8 @@ var init_slack = __esm({
66152
66359
  init_intake();
66153
66360
  init_thread();
66154
66361
  init_operator();
66362
+ init_directives();
66363
+ init_verbosity();
66155
66364
  init_confirmations();
66156
66365
  init_threadTurns();
66157
66366
  init_statusBudget();
@@ -66337,14 +66546,15 @@ ${file.text}`));
66337
66546
  io
66338
66547
  };
66339
66548
  }
66340
- async status(initial) {
66549
+ async status(initial, display = {}) {
66341
66550
  const statusClient = this.opts.statusClient ?? this.client;
66342
66551
  const budget = this.opts.statusBudget ?? processStatusBudget;
66343
- const setShimmer = () => statusClient.assistant.threads.setStatus({
66552
+ const shimmerVisible = shows(display.verbosity ?? DEFAULT_VERBOSITY, "verbose");
66553
+ const setShimmer = (status3 = LOADING_PHRASES[0]) => statusClient.assistant.threads.setStatus({
66344
66554
  channel_id: this.ev.channel,
66345
66555
  thread_ts: this.ev.threadTs,
66346
- status: LOADING_PHRASES[0],
66347
- loading_messages: LOADING_PHRASES
66556
+ status: status3,
66557
+ ...status3 === "" ? {} : { loading_messages: LOADING_PHRASES }
66348
66558
  }).catch((err2) => console.error(`[shimmer] ${err2.message}`));
66349
66559
  let ts;
66350
66560
  if (this.opts.existingCard) {
@@ -66369,11 +66579,17 @@ ${file.text}`));
66369
66579
  liveCards.add(liveCardKey(this.ev.channel, ts));
66370
66580
  const card = `${this.ev.channel}:${ts}`;
66371
66581
  const shimmerKey = `${this.ev.channel}:${this.ev.threadTs}`;
66372
- shimmerOwners.set(shimmerKey, card);
66373
- await setShimmer();
66374
- const shimmerTimer = setInterval(() => {
66375
- if (shimmerOwners.get(shimmerKey) === card) void setShimmer();
66376
- }, 75e3);
66582
+ let shimmerTimer;
66583
+ if (shimmerVisible) {
66584
+ shimmerOwners.set(shimmerKey, card);
66585
+ await setShimmer();
66586
+ shimmerTimer = setInterval(() => {
66587
+ if (shimmerOwners.get(shimmerKey) === card) void setShimmer();
66588
+ }, 75e3);
66589
+ } else {
66590
+ shimmerOwners.delete(shimmerKey);
66591
+ await setShimmer("");
66592
+ }
66377
66593
  budget.open(card);
66378
66594
  const edit = (frame2) => statusClient.chat.update({ channel: this.ev.channel, ts, ...render2(frame2) });
66379
66595
  let dropped = 0;
@@ -66402,7 +66618,7 @@ ${file.text}`));
66402
66618
  });
66403
66619
  },
66404
66620
  done: async (frame2) => {
66405
- clearInterval(shimmerTimer);
66621
+ if (shimmerTimer !== void 0) clearInterval(shimmerTimer);
66406
66622
  liveCards.delete(liveCardKey(this.ev.channel, ts));
66407
66623
  budget.close(card);
66408
66624
  const waitMs = budget.takeTerminal(this.ev.channel);
@@ -66414,7 +66630,7 @@ ${file.text}`));
66414
66630
  if (dropped > 0) console.log(`[slack] card ${card}: ${dropped} progress frames dropped by the status budget`);
66415
66631
  if (shimmerOwners.get(shimmerKey) !== card) return;
66416
66632
  shimmerOwners.delete(shimmerKey);
66417
- await statusClient.assistant.threads.setStatus({ channel_id: this.ev.channel, thread_ts: this.ev.threadTs, status: "" }).catch(() => {
66633
+ await setShimmer("").catch(() => {
66418
66634
  });
66419
66635
  }
66420
66636
  };
@@ -71187,7 +71403,12 @@ async function launchResumes(deps, resumable, opts) {
71187
71403
  startedAt: row2.startedAt,
71188
71404
  now: deps.clock ?? Date.now
71189
71405
  });
71190
- await io2.status(shell.live({ detail: [rehostCardDetail(run2)] }));
71406
+ const verbosity2 = row2.meta.verbosity ?? deps.config.verbosityFor(
71407
+ row2.meta.channelId,
71408
+ row2.meta.userId,
71409
+ parseDirectives(inputTextOf(run2.events)).verbosity
71410
+ );
71411
+ await io2.status(shell.live({ detail: [rehostCardDetail(run2)] }), { verbosity: verbosity2 });
71191
71412
  } catch (err2) {
71192
71413
  warn(
71193
71414
  `[resume] ${row2.runId} ${row2.threadKey}: restart card could not be written: ${err2 instanceof Error ? err2.message : String(err2)}`
@@ -71266,6 +71487,7 @@ var PI_READ_ONLY_TOOLS;
71266
71487
  var init_resumeLaunch = __esm({
71267
71488
  "../../src/core/resumeLaunch.ts"() {
71268
71489
  "use strict";
71490
+ init_directives();
71269
71491
  init_inboxMessage();
71270
71492
  init_resume();
71271
71493
  init_runRegistry();
@@ -72242,7 +72464,7 @@ function watched(io, on) {
72242
72464
  on.replied(text);
72243
72465
  await io.reply(text);
72244
72466
  },
72245
- status: (initial) => io.status(initial),
72467
+ status: (initial, display) => io.status(initial, display),
72246
72468
  history: () => io.history(),
72247
72469
  runStarted: (started) => {
72248
72470
  io.runStarted?.(started);
@@ -72316,6 +72538,33 @@ The thread since the last step:
72316
72538
  ${foldThreadEvents(folded)}` };
72317
72539
  }
72318
72540
  const carried = foldThreadAttachments(folded);
72541
+ const existingPrCodingTarget = req.preset === "coding" && row2 !== void 0 && (row2.publication !== void 0 || row2.pr !== void 0 || row2.resume !== void 0 || parsePlanBranch(row2.branch) === void 0);
72542
+ if (existingPrCodingTarget) {
72543
+ if (row2.publication === void 0)
72544
+ return json3(409, {
72545
+ ok: false,
72546
+ error: "publication_binding_missing",
72547
+ message: "the existing pull request has no durable publication binding",
72548
+ at
72549
+ });
72550
+ try {
72551
+ const owner = deps.runnerOwnership?.owner(instance.repo, row2.publication.pr);
72552
+ if (owner === void 0 || owner.instanceId !== row2.publication.owner.instanceId || owner.unit !== row2.publication.owner.unit)
72553
+ return json3(409, {
72554
+ ok: false,
72555
+ error: "publication_ownership_changed",
72556
+ message: "the durable existing-PR publication owner is no longer the runner's sole owner",
72557
+ at
72558
+ });
72559
+ } catch (err2) {
72560
+ return json3(409, {
72561
+ ok: false,
72562
+ error: "publication_ownership_unknown",
72563
+ message: describe12(err2),
72564
+ at
72565
+ });
72566
+ }
72567
+ }
72319
72568
  const msg = {
72320
72569
  channelId: instance.channelId,
72321
72570
  userId: instance.userId,
@@ -72357,10 +72606,16 @@ ${foldThreadEvents(folded)}` };
72357
72606
  lastReply = text;
72358
72607
  }
72359
72608
  });
72609
+ const roundExpectedHead = req.brief?.kind === "findings" || req.brief?.kind === "rebase" ? req.brief.headSha : req.brief?.kind === "contract" ? req.brief.continue?.from : void 0;
72610
+ const publication = req.preset === "coding" && row2?.publication !== void 0 ? {
72611
+ ...row2.publication,
72612
+ ...roundExpectedHead !== void 0 ? { expectedHeadSha: roundExpectedHead } : {}
72613
+ } : void 0;
72360
72614
  const tag = {
72361
72615
  parentInstanceId: instance.id,
72362
72616
  idempotencyKey: key,
72363
- ...instance.base !== void 0 ? { base: instance.base } : {}
72617
+ ...instance.base !== void 0 ? { base: instance.base } : {},
72618
+ ...publication !== void 0 ? { publication } : {}
72364
72619
  };
72365
72620
  let dispatching;
72366
72621
  try {
@@ -72851,9 +73106,19 @@ async function prCheck(body, deps) {
72851
73106
  const unit = await unitRowOf(deps, instance, body.unit);
72852
73107
  if (!unit.ok) return unit.response;
72853
73108
  const branch2 = unit.row?.branch ?? instance.branch;
72854
- const remember = async (pr) => {
72855
- if (unit.row && (unit.row.pr?.number !== pr.number || unit.row.pr.url !== pr.url))
72856
- await deps.instances.putUnits([{ ...unit.row, pr }]);
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 } : {} }]);
72857
73122
  };
72858
73123
  try {
72859
73124
  if (follow !== void 0) {
@@ -72862,8 +73127,12 @@ async function prCheck(body, deps) {
72862
73127
  const fallbackUrl = unit.row?.pr?.url ?? `https://github.com/${instance.repo}/pull/${follow}`;
72863
73128
  const state2 = pullRequestState(follow, fallbackUrl, facts2);
72864
73129
  if (state2 === void 0) throw new Error(headBranchStateError(instance.repo, follow));
72865
- await remember({ number: follow, url: String(state2.url) });
73130
+ await remember({ number: follow, url: String(state2.url) }, facts2);
72866
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
+ });
72867
73136
  const prRef = { repo: instance.repo, number: follow };
72868
73137
  const headSha = facts2.headSha;
72869
73138
  const approved = entry && headSha !== void 0 ? await reviewPostedAt(deps, prRef, "approve", headSha) : void 0;
@@ -72894,13 +73163,13 @@ async function prCheck(body, deps) {
72894
73163
  }
72895
73164
  const open2 = await deps.findOpenPrByHead(instance.repo, branch2);
72896
73165
  if (open2) {
72897
- await remember({ number: open2.number, url: open2.htmlUrl });
72898
73166
  deps.runnerOwnership?.claim(instance.repo, open2.number, {
72899
73167
  instanceId: instance.id,
72900
73168
  unit: unit.row?.unit ?? body.unit
72901
73169
  });
72902
73170
  const liveFacts = await deps.fetchPrFacts({ repo: instance.repo, number: open2.number });
72903
73171
  if (liveFacts === void 0) throw new Error(`could not read ${instance.repo}#${open2.number}`);
73172
+ await remember({ number: open2.number, url: open2.htmlUrl }, liveFacts);
72904
73173
  const current = pullRequestState(open2.number, open2.htmlUrl, liveFacts);
72905
73174
  if (current === void 0) throw new Error(headBranchStateError(instance.repo, open2.number));
72906
73175
  if (current.state !== "open") return json3(200, { ok: true, ...current, at });
@@ -73209,12 +73478,13 @@ async function drawCard(deps, instance, units, close) {
73209
73478
  source: instance.addressSeveritySource ?? "org"
73210
73479
  } : void 0
73211
73480
  );
73481
+ const display = { verbosity: instance.verbosity ?? DEFAULT_VERBOSITY };
73212
73482
  if (!close) {
73213
- await io.status(shell.live({ detail }));
73483
+ await io.status(shell.live({ detail }), display);
73214
73484
  return;
73215
73485
  }
73216
73486
  shell.freeze(clock4());
73217
- const handle2 = await io.status(shell.live({ detail }));
73487
+ const handle2 = await io.status(shell.live({ detail }), display);
73218
73488
  await handle2.done(shell.close({ kind: "done", icon: close.icon, detail: detail.join("\n") }));
73219
73489
  }
73220
73490
  function parseGate(raw) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreplane/switchboard",
3
- "version": "1.261.1",
3
+ "version": "1.261.2",
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",