@coreplane/switchboard 1.261.0 → 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.
package/dist/cli.js CHANGED
@@ -195,7 +195,9 @@ async function ensureWorkArea(at, version2, workers, deps) {
195
195
  deps.log(`[deploy] materialised the shipped tree (version ${version2}) under ${at.workArea}`);
196
196
  }
197
197
  if (plan3.install.length > 0) {
198
- deps.log(`[deploy] npm ci --workspace ${plan3.install.join(" --workspace ")} under ${at.workArea}`);
198
+ deps.log(
199
+ `[deploy] npm ci --include-workspace-root --workspace ${plan3.install.join(" --workspace ")} under ${at.workArea}`
200
+ );
199
201
  const r = await deps.install(at.workArea, plan3.install);
200
202
  if (r.code !== 0)
201
203
  return {
@@ -241,10 +243,22 @@ function packageSourceOnHost(at = OPERATOR_ROOT) {
241
243
  function cliVersionOnHost(at = OPERATOR_ROOT) {
242
244
  return at.mode === "package" ? packageSourceOnHost(at).version : packageVersion();
243
245
  }
246
+ function workAreaNpmCiArgs(workspaces) {
247
+ return [
248
+ "ci",
249
+ "--include-workspace-root",
250
+ "--no-audit",
251
+ "--no-fund",
252
+ ...workspaces.flatMap((w) => ["--workspace", w])
253
+ ];
254
+ }
244
255
  function npmCi(cwd, workspaces) {
245
256
  return new Promise((resolve13) => {
246
- const args = ["ci", "--no-audit", "--no-fund", ...workspaces.flatMap((w) => ["--workspace", w])];
247
- const child = spawn("npm", args, { cwd, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
257
+ const child = spawn("npm", workAreaNpmCiArgs(workspaces), {
258
+ cwd,
259
+ env: process.env,
260
+ stdio: ["ignore", "pipe", "pipe"]
261
+ });
248
262
  let output = "";
249
263
  child.stdout.on("data", (c) => output += c.toString());
250
264
  child.stderr.on("data", (c) => output += c.toString());
@@ -3264,7 +3278,7 @@ function renewalDecision(input) {
3264
3278
  };
3265
3279
  }
3266
3280
  function renderRenewal(decision, grant2, options = {}) {
3267
- const recourse = options.idle ? "the next reply in this thread continues the unit" : "the next reply in this thread continues the original task";
3281
+ const recourse = options.idle ? "the next reply in this thread continues the unit" : "start ship again with the original task";
3268
3282
  const senders = options.senders?.length ? `, with ${options.senders.length} message${options.senders.length === 1 ? "" : "s"} from ${options.senders.join(", ")}` : "";
3269
3283
  if (decision.renew)
3270
3284
  return `budget renewed, ${decision.segment - 1} of ${grant2.renewals}, continues ${decision.from !== void 0 ? decision.from.slice(0, 7) : "the branch's head"}${senders}`;
@@ -3564,11 +3578,11 @@ function buildReviewPostBody(answer, verdict, target) {
3564
3578
  ...target ? [`head \`${target.head.slice(0, 7)}\``] : [],
3565
3579
  verdict ? findingCounts(verdict.findings) : "the run ended without a submit_verdict call"
3566
3580
  ];
3567
- const parts = [verdictLine(verdict), `> [!${calloutKind(verdict)}]
3581
+ const fixedParts = [verdictLine(verdict), `> [!${calloutKind(verdict)}]
3568
3582
  > ${facts2.join(" \xB7 ")}`];
3569
3583
  const findings = verdict?.findings ?? [];
3570
3584
  if (findings.length > 0) {
3571
- parts.push(
3585
+ fixedParts.push(
3572
3586
  [
3573
3587
  "| Severity | Finding | Where |",
3574
3588
  "| --- | --- | --- |",
@@ -3578,15 +3592,25 @@ function buildReviewPostBody(answer, verdict, target) {
3578
3592
  ].join("\n")
3579
3593
  );
3580
3594
  }
3595
+ const marker = verdictMarker(verdict, target);
3596
+ const fixedBody = [...fixedParts, marker].join("\n\n");
3581
3597
  const prose2 = answer.trim();
3582
- if (prose2) parts.push(`<details>
3598
+ if (!prose2) return fixedBody;
3599
+ const details2 = (text, truncated) => `<details>
3583
3600
  <summary>Full review</summary>
3584
3601
 
3585
- ${prose2}
3602
+ ${text}${truncated ? `${text ? "\n\n" : ""}${REVIEW_TRUNCATION_NOTE}` : ""}
3586
3603
 
3587
- </details>`);
3588
- parts.push(verdictMarker(verdict, target));
3589
- return parts.join("\n\n");
3604
+ </details>`;
3605
+ const render3 = (text, truncated) => [...fixedParts, details2(text, truncated), marker].join("\n\n");
3606
+ const full = render3(prose2, false);
3607
+ if ([...full].length <= MAX_REVIEW_POST_CODE_POINTS) return full;
3608
+ const clippedShell = render3("", true);
3609
+ const proseSeparatorCodePoints = 2;
3610
+ const proseBudget = MAX_REVIEW_POST_CODE_POINTS - [...clippedShell].length - proseSeparatorCodePoints;
3611
+ if (proseBudget > 0) return render3([...prose2].slice(0, proseBudget).join(""), true);
3612
+ if ([...clippedShell].length <= MAX_REVIEW_POST_CODE_POINTS) return clippedShell;
3613
+ return full;
3590
3614
  }
3591
3615
  function buildReviewChannelReply(input) {
3592
3616
  const { answer, verdict, posted, liveUrl } = input;
@@ -3675,7 +3699,7 @@ function redactVerdict(v, redact = redactSecrets) {
3675
3699
  function redactDispositions(dispositions, redact = redactSecrets) {
3676
3700
  return dispositions.map((d) => ({ findingId: d.findingId, disposition: d.disposition, note: redact(d.note) }));
3677
3701
  }
3678
- var FINDING_SEVERITIES, ADDRESS_SEVERITIES, DEFAULT_ADDRESS_SEVERITY, isAddressSeverity, severityRank, LGTM_TOKEN, CHANGES_TOKEN, NO_VERDICT_LINE, escapeMarkdownTableCell, unescapeMarkdownTableCell, isRepoPath, VERDICT_KINDS, DISPOSITION_KINDS, isRecordLike, REVIEW_POST_HEAD;
3702
+ var FINDING_SEVERITIES, ADDRESS_SEVERITIES, DEFAULT_ADDRESS_SEVERITY, isAddressSeverity, severityRank, LGTM_TOKEN, CHANGES_TOKEN, NO_VERDICT_LINE, MAX_REVIEW_POST_CODE_POINTS, REVIEW_TRUNCATION_NOTE, escapeMarkdownTableCell, unescapeMarkdownTableCell, isRepoPath, VERDICT_KINDS, DISPOSITION_KINDS, isRecordLike, REVIEW_POST_HEAD;
3679
3703
  var init_reviewVerdict = __esm({
3680
3704
  "../../src/core/reviewVerdict.ts"() {
3681
3705
  "use strict";
@@ -3690,6 +3714,8 @@ var init_reviewVerdict = __esm({
3690
3714
  LGTM_TOKEN = "LGTM:";
3691
3715
  CHANGES_TOKEN = "Changes requested:";
3692
3716
  NO_VERDICT_LINE = "No verdict submitted \u2014 not approving.";
3717
+ MAX_REVIEW_POST_CODE_POINTS = 65e3;
3718
+ REVIEW_TRUNCATION_NOTE = "_(review truncated to fit GitHub's review size limit)_";
3693
3719
  escapeMarkdownTableCell = (text) => oneLine2(text).replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
3694
3720
  unescapeMarkdownTableCell = (text) => text.replace(/\\([\\|])/g, "$1");
3695
3721
  isRepoPath = (file) => /^[^\s`]+$/.test(file) && !/^[a-z][a-z0-9+.-]*:\/\//i.test(file);
@@ -3711,10 +3737,10 @@ function interruptionCauseOfWords(words) {
3711
3737
  return void 0;
3712
3738
  }
3713
3739
  function shipInterruptedNote(prUrl3, cause, idle = false) {
3714
- const stands = prUrl3 ? `Its work stands on GitHub: ${prUrl3}.` : "Whatever it pushed stands on its pipeline branch; no PR was opened yet.";
3715
- const continuation = idle ? "The next reply in this thread continues the unit." : prUrl3 ? `The pipeline kept its task, branch and pull request; the next reply in this thread continues the review loop at ${prUrl3}.` : "The pipeline kept its task and branch; the next reply in this thread starts round 0 again on that branch.";
3716
- const opening = cause === "container_replaced" ? "\u26A0\uFE0F The resident container running this pipeline's child was replaced (a deploy's image swap) and the child could not resume, so the pipeline stopped." : cause === "sandbox_fault" ? "\u26A0\uFE0F The sandbox running this pipeline's child failed and the child could not resume, so the pipeline stopped." : cause === "bot_restart" ? "\u26A0\uFE0F The bot restarted while this ship pipeline was running, so the pipeline stopped." : "\u26A0\uFE0F This ship pipeline's child was interrupted and could not resume, so the pipeline stopped.";
3717
- return `${opening} ${stands} ${continuation}`;
3740
+ const opening = cause === "container_replaced" ? "\u26A0\uFE0F Work stopped before the next review because the repository container was replaced and the run could not continue." : cause === "sandbox_fault" ? "\u26A0\uFE0F Work stopped before the next review because the sandbox failed and the run could not continue." : cause === "bot_restart" ? "\u26A0\uFE0F Work stopped before the next review because Switchboard restarted and the run could not continue." : "\u26A0\uFE0F Work stopped before the next review because the run could not continue.";
3741
+ const saved = prUrl3 ? `The existing pull request is ${prUrl3}; Switchboard did not verify the stopped run's latest commit there.` : "No open pull request was verified for the stopped run's latest commit.";
3742
+ const next = idle ? "The unit remains live; a reply in this thread is recorded as its continuation action." : "No review was started. Next action: start ship again after the remote branch and pull request state can be verified.";
3743
+ return `${opening} ${saved} ${next}`;
3718
3744
  }
3719
3745
  function planIdOf(planPath) {
3720
3746
  const base = planPath.split("/").pop() ?? "";
@@ -4160,6 +4186,7 @@ var init_runEvents = __esm({
4160
4186
  "ledger_untracked",
4161
4187
  "rebind_refused",
4162
4188
  "work_left_behind",
4189
+ "publication_blocked",
4163
4190
  "workspace_torn_down",
4164
4191
  "pr_not_opened",
4165
4192
  "pr_head_unverified",
@@ -5766,6 +5793,7 @@ function isCoordinatorUnit(v) {
5766
5793
  if (r.issue !== void 0 && !isFinite(r.issue)) return false;
5767
5794
  if (r.pr !== void 0 && !isPr(r.pr)) return false;
5768
5795
  if (r.resume !== void 0 && !isResume(r.resume)) return false;
5796
+ if (r.publication !== void 0 && !isPublication(r.publication)) return false;
5769
5797
  if (r.record !== void 0 && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
5770
5798
  if (r.lastPush !== void 0 && !isText(r.lastPush)) return false;
5771
5799
  if (r.segments !== void 0 && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
@@ -5829,7 +5857,7 @@ async function sendChildSignal(workflow, signal2) {
5829
5857
  return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
5830
5858
  }
5831
5859
  }
5832
- 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;
5833
5861
  var init_contract2 = __esm({
5834
5862
  "../../src/core/coordinator/contract.ts"() {
5835
5863
  "use strict";
@@ -5869,6 +5897,8 @@ var init_contract2 = __esm({
5869
5897
  isObject = (v) => typeof v === "object" && v !== null;
5870
5898
  isPr = (v) => isObject(v) && isFinite(v.number) && isText(v.url, 2048);
5871
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);
5872
5902
  isThread = (v) => isObject(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
5873
5903
  isRoundGate = (v) => isObject(v) && isAddressSeverity(v.level) && Array.isArray(v.findings) && v.findings.every((f) => typeof f === "string");
5874
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));
@@ -6553,15 +6583,15 @@ function capEvent(event, maxBytes) {
6553
6583
  }
6554
6584
  return { event: capped4, bytes: size };
6555
6585
  }
6556
- function fitRecordToBudget(record2, maxBytes = MAX_RECORD_BYTES) {
6557
- const cappedAll = record2.events.map((e) => capEvent(e, MAX_EVENT_BYTES));
6558
- const measure = (evs) => utf8ByteLength(JSON.stringify({ ...record2, events: evs, storedEventCount: evs.length, truncated: true }));
6559
- const whole = { ...record2, events: cappedAll.map((c) => c.event), storedEventCount: cappedAll.length };
6586
+ function fitRecordToBudget(record3, maxBytes = MAX_RECORD_BYTES) {
6587
+ const cappedAll = record3.events.map((e) => capEvent(e, MAX_EVENT_BYTES));
6588
+ const measure = (evs) => utf8ByteLength(JSON.stringify({ ...record3, events: evs, storedEventCount: evs.length, truncated: true }));
6589
+ const whole = { ...record3, events: cappedAll.map((c) => c.event), storedEventCount: cappedAll.length };
6560
6590
  if (utf8ByteLength(JSON.stringify(whole)) <= maxBytes) return whole;
6561
6591
  const baseline = measure([]);
6562
6592
  const capped4 = dropSpanPairsFromTheMiddle(cappedAll, maxBytes - baseline);
6563
6593
  const events = capped4.map((c) => c.event);
6564
- if (measure(events) <= maxBytes) return { ...record2, events, storedEventCount: events.length, truncated: true };
6594
+ if (measure(events) <= maxBytes) return { ...record3, events, storedEventCount: events.length, truncated: true };
6565
6595
  const sizes = capped4.map((c) => c.bytes + 1);
6566
6596
  let remaining = maxBytes - measure([]);
6567
6597
  let head = 0;
@@ -6578,7 +6608,7 @@ function fitRecordToBudget(record2, maxBytes = MAX_RECORD_BYTES) {
6578
6608
  while (kept.length > 0 && measure(kept) > maxBytes) {
6579
6609
  kept.splice(Math.floor(kept.length / 2), 1);
6580
6610
  }
6581
- return { ...record2, events: kept, storedEventCount: kept.length, truncated: true };
6611
+ return { ...record3, events: kept, storedEventCount: kept.length, truncated: true };
6582
6612
  }
6583
6613
  function headLength(events) {
6584
6614
  let n2 = 0;
@@ -6696,9 +6726,9 @@ function dominantFriction(byCategory) {
6696
6726
  }
6697
6727
  return best;
6698
6728
  }
6699
- function pointOf(record2, prices) {
6700
- if (record2.provisional === true) return void 0;
6701
- const usage = record2.usage ?? emptyUsage();
6729
+ function pointOf(record3, prices) {
6730
+ if (record3.provisional === true) return void 0;
6731
+ const usage = record3.usage ?? emptyUsage();
6702
6732
  const priced = llmUsdOfUsage(usage, prices);
6703
6733
  let inputTokens = 0;
6704
6734
  let outputTokens = 0;
@@ -6710,27 +6740,27 @@ function pointOf(record2, prices) {
6710
6740
  cacheReadTokens += m.cacheReadTokens;
6711
6741
  cacheWriteTokens += m.cacheWriteTokens;
6712
6742
  }
6713
- const shape = record2.diagnosis.shape;
6743
+ const shape = record3.diagnosis.shape;
6714
6744
  const blobs = {
6715
6745
  schema: POINT_SCHEMA,
6716
- agent: record2.agent ?? "",
6717
- preset: record2.profile?.preset ?? "",
6718
- model: record2.model ?? "",
6719
- status: record2.status,
6720
- "failure kind": record2.failure?.kind ?? "",
6721
- "dominant friction": dominantFriction(record2.diagnosis.byCategory),
6722
- channel: record2.channelId,
6723
- repository: record2.repo ?? "",
6724
- "machine class": record2.profile?.machine ?? "",
6725
- "route class": record2.route !== void 0 ? "routed" : "chosen",
6726
- reply: record2.replyOk === true ? "ok" : record2.replyOk === false ? "failed" : "none",
6727
- lineage: record2.parentRunId !== void 0 ? "child" : "root",
6728
- requester: record2.userId,
6729
- identity: record2.profile?.identity ?? "",
6730
- "run id": record2.id
6746
+ agent: record3.agent ?? "",
6747
+ preset: record3.profile?.preset ?? "",
6748
+ model: record3.model ?? "",
6749
+ status: record3.status,
6750
+ "failure kind": record3.failure?.kind ?? "",
6751
+ "dominant friction": dominantFriction(record3.diagnosis.byCategory),
6752
+ channel: record3.channelId,
6753
+ repository: record3.repo ?? "",
6754
+ "machine class": record3.profile?.machine ?? "",
6755
+ "route class": record3.route !== void 0 ? "routed" : "chosen",
6756
+ reply: record3.replyOk === true ? "ok" : record3.replyOk === false ? "failed" : "none",
6757
+ lineage: record3.parentRunId !== void 0 ? "child" : "root",
6758
+ requester: record3.userId,
6759
+ identity: record3.profile?.identity ?? "",
6760
+ "run id": record3.id
6731
6761
  };
6732
6762
  const doubles = {
6733
- wall: record2.finishedAt - (record2.receivedAt ?? record2.startedAt),
6763
+ wall: record3.finishedAt - (record3.receivedAt ?? record3.startedAt),
6734
6764
  "getting ready": shape?.gettingReadyMs ?? 0,
6735
6765
  thinking: shape?.thinkingMs ?? 0,
6736
6766
  tools: shape?.toolsMs ?? 0,
@@ -6744,15 +6774,15 @@ function pointOf(record2, prices) {
6744
6774
  "cache read tokens": cacheReadTokens,
6745
6775
  "cache write tokens": cacheWriteTokens,
6746
6776
  dollars: priced.usd,
6747
- steps: record2.stepCount ?? 0,
6748
- "tool calls": isFinite_2(record2.diagnosis.toolCalls) ? record2.diagnosis.toolCalls : 0,
6749
- events: record2.eventCount,
6777
+ steps: record3.stepCount ?? 0,
6778
+ "tool calls": isFinite_2(record3.diagnosis.toolCalls) ? record3.diagnosis.toolCalls : 0,
6779
+ events: record3.eventCount,
6750
6780
  "unpriced tokens": priced.unpricedTokens,
6751
- minutes: record2.profile?.minutes ?? 0,
6752
- "finished at": record2.finishedAt
6781
+ minutes: record3.profile?.minutes ?? 0,
6782
+ "finished at": record3.finishedAt
6753
6783
  };
6754
6784
  return {
6755
- indexes: [capBlob(record2.agent ?? UNKNOWN_AGENT)],
6785
+ indexes: [capBlob(record3.agent ?? UNKNOWN_AGENT)],
6756
6786
  blobs: POINT_COLUMNS.blobs.map((n2) => capBlob(blobs[n2])),
6757
6787
  doubles: POINT_COLUMNS.doubles.map((n2) => doubles[n2])
6758
6788
  };
@@ -7102,9 +7132,9 @@ function makeSsrfLookup(resolve13) {
7102
7132
  const family = (ip) => ip.includes(":") ? 6 : 4;
7103
7133
  resolve13(hostname3).then(
7104
7134
  (ips) => {
7105
- const blocked = ips.find((ip) => ipInBlockedRange(ip));
7106
- if (blocked) {
7107
- 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}`));
7108
7138
  return;
7109
7139
  }
7110
7140
  if (ips.length === 0) {
@@ -10390,12 +10420,8 @@ function containerApplicationName(script, className) {
10390
10420
  return `${script}-${className.toLowerCase()}`;
10391
10421
  }
10392
10422
  function bindLiveGate(gate, script, healthUrl) {
10393
- return gate.kind === "health" ? { kind: "health", healthUrl } : {
10394
- kind: "sandbox",
10395
- healthUrl,
10396
- bearerEnv: gate.bearerEnv,
10397
- containerApp: containerApplicationName(script, gate.containerClass)
10398
- };
10423
+ const containerApp = containerApplicationName(script, gate.containerClass);
10424
+ return gate.kind === "bot" ? { kind: "bot", healthUrl, containerApp } : { kind: "sandbox", healthUrl, bearerEnv: gate.bearerEnv, containerApp };
10399
10425
  }
10400
10426
  function workersFor(profile) {
10401
10427
  return WORKER_SPECS.flatMap(({ preflight, liveGate, ...spec }) => {
@@ -10410,8 +10436,8 @@ function workersFor(profile) {
10410
10436
  script,
10411
10437
  baseUrl,
10412
10438
  healthUrl,
10413
- // A preflighted step with a health gate reads the same URL for its wait heartbeat.
10414
- ...preflight ? { preflight: liveGate?.kind === "health" ? { ...preflight, healthUrl } : preflight } : {},
10439
+ // The bot preflight reads the same URL as its application + health live gate.
10440
+ ...preflight ? { preflight: liveGate?.kind === "bot" ? { ...preflight, healthUrl } : preflight } : {},
10415
10441
  ...liveGate ? { liveGate: bindLiveGate(liveGate, script, healthUrl) } : {}
10416
10442
  }
10417
10443
  ];
@@ -10522,7 +10548,7 @@ function formatPlan(plan3) {
10522
10548
  plan3.steps.forEach((s, i) => {
10523
10549
  const pf = s.retryOnPreflightRefusal ? ` \u2014 preflight (retry every ${plan3.pollMs / 1e3}s up to ${(s.waitMaxMs ?? plan3.waitMaxMs) / 6e4} min)` : s.forcedBy ? ` \u2014 preflight FORCED (${s.forcedBy}=1)` : "";
10524
10550
  const env = s.requiredEnv.length > 0 ? ` \u2014 needs ${s.requiredEnv.map((r) => r.anyOf.length === 1 ? r.anyOf[0] : `one of ${r.anyOf.join(" / ")}`).join(", ")}` : "";
10525
- const live = s.liveGate ? s.liveGate.kind === "health" ? ` \u2014 then wait until live (${s.liveGate.healthUrl} not draining + build.commit == HEAD)` : ` \u2014 then wait until live (${s.liveGate.healthUrl} build.commit == HEAD + every running ${s.liveGate.containerApp} instance on the app version + an /exec probe answers ok from one)` : "";
10551
+ const live = s.liveGate ? s.liveGate.kind === "bot" ? ` \u2014 then wait until live (${s.liveGate.containerApp} advances to the deployed image + ${s.liveGate.healthUrl} build.commit exactly == HEAD)` : ` \u2014 then wait until live (${s.liveGate.healthUrl} build.commit == HEAD + every running ${s.liveGate.containerApp} instance on the app version + an /exec probe answers ok from one)` : "";
10526
10552
  const cap = s.capabilities.length > 0 ? ` \u2014 credential must pass ${s.capabilities.map((c) => `\`${c.command.join(" ")}\` (${c.needs})`).join(" and ")}` : "";
10527
10553
  lines.push(
10528
10554
  ` ${i + 1}. ${s.name} (${s.script}) \u2014 ${s.dir}: ${s.command.join(" ")}${pf}${env}${cap}${live}
@@ -10593,7 +10619,7 @@ function classifyDeployOutput(exitCode, output) {
10593
10619
  const firstError = lines.find((l) => isErrorish(l) && !/^npm ERR!/i.test(l)) ?? lines.find(isErrorish);
10594
10620
  return { kind: "failed", reason: firstError ?? `exit ${exitCode}` };
10595
10621
  }
10596
- var UNSET_ENV, DEPLOY_FORCE_ENV, SANDBOX_CONTAINER_CLASS, SANDBOX_BEARER_ENV, R2_CAPABILITY, RESIDENT_BEARER_ENVS, RESIDENT_WAIT_MAX_MS, CONTAINERS_CAPABILITY, WORKER_DIRS, WORKER_SPECS, DEPLOY_ORDER, CONFIG_DOCUMENT_KEY, hasImage, ANSI_SEQUENCE;
10622
+ var UNSET_ENV, DEPLOY_FORCE_ENV, BOT_CONTAINER_CLASS, SANDBOX_CONTAINER_CLASS, SANDBOX_BEARER_ENV, R2_CAPABILITY, RESIDENT_BEARER_ENVS, RESIDENT_WAIT_MAX_MS, CONTAINERS_CAPABILITY, WORKER_DIRS, WORKER_SPECS, DEPLOY_ORDER, CONFIG_DOCUMENT_KEY, hasImage, ANSI_SEQUENCE;
10597
10623
  var init_plan = __esm({
10598
10624
  "../../src/deploy/plan.ts"() {
10599
10625
  "use strict";
@@ -10605,6 +10631,7 @@ var init_plan = __esm({
10605
10631
  init_profile2();
10606
10632
  UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
10607
10633
  DEPLOY_FORCE_ENV = "SWITCHBOARD_DEPLOY_FORCE";
10634
+ BOT_CONTAINER_CLASS = "SwitchboardServer";
10608
10635
  SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
10609
10636
  SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
10610
10637
  R2_CAPABILITY = {
@@ -10665,10 +10692,10 @@ var init_plan = __esm({
10665
10692
  ]
10666
10693
  },
10667
10694
  preflight: { forceEnv: DEPLOY_FORCE_ENV, baseUrlEnv: "SWITCHBOARD_BASE_URL" },
10668
- liveGate: { kind: "health" },
10695
+ liveGate: { kind: "bot", containerClass: BOT_CONTAINER_CLASS },
10669
10696
  // The preflight reads the container application and the deploy pushes the image: both need Containers.
10670
10697
  capabilities: [CONTAINERS_CAPABILITY],
10671
- why: "container shim \u2014 preflight refuses only over a rollout in progress (runs in flight hand off); done only when the new container is live. A rotated bot secret needs no build: `wrangler secret put` alone leaves the running container on its old env \u2014 `deploy restart` restarts it on the current env"
10698
+ why: "container shim \u2014 preflight refuses only over a rollout in progress (runs in flight hand off); done only when the application advances to the deployed image and /healthz serves the exact commit. A rotated bot secret needs no build: `wrangler secret put` alone leaves the running container on its old env \u2014 `deploy restart` restarts it on the current application target"
10672
10699
  },
10673
10700
  {
10674
10701
  name: "resident",
@@ -11788,11 +11815,11 @@ var init_runStoreWorker = __esm({
11788
11815
  fetchImpl;
11789
11816
  abandoned() {
11790
11817
  }
11791
- async put(record2, trace) {
11792
- if (!RUN_ID_PATTERN.test(record2.id))
11793
- throw new PermanentStoreError(`run store: refusing to put malformed id ${JSON.stringify(record2.id)}`);
11794
- const body = { storeKey: this.opts.storeKey, record: record2 };
11795
- const point = pointOf(record2, this.opts.prices);
11818
+ async put(record3, trace) {
11819
+ if (!RUN_ID_PATTERN.test(record3.id))
11820
+ throw new PermanentStoreError(`run store: refusing to put malformed id ${JSON.stringify(record3.id)}`);
11821
+ const body = { storeKey: this.opts.storeKey, record: record3 };
11822
+ const point = pointOf(record3, this.opts.prices);
11796
11823
  if (point !== void 0) body.point = point;
11797
11824
  if (this.opts.policy) {
11798
11825
  body.policy = this.opts.policy;
@@ -11962,6 +11989,66 @@ var init_supersede = __esm({
11962
11989
  }
11963
11990
  });
11964
11991
 
11992
+ // ../../src/deploy/botLiveGate.ts
11993
+ function judge(input) {
11994
+ if ("error" in input.app)
11995
+ return waiting(`container application ${input.containerApp} unreadable: ${input.app.error}`);
11996
+ const app = input.app.value;
11997
+ let rollout;
11998
+ if (input.target) {
11999
+ if (input.target.image !== null && app.image !== input.target.image)
12000
+ return waiting(
12001
+ `container application ${input.containerApp} still targets ${app.image ?? "an unknown image"}; expected ${input.target.image}`
12002
+ );
12003
+ if ("error" in input.before)
12004
+ return waiting(
12005
+ `container application ${input.containerApp} pre-deploy version unreadable (${input.before.error}); cannot prove the application version advanced`
12006
+ );
12007
+ if (app.version <= input.before.value.version)
12008
+ return waiting(
12009
+ `container application ${input.containerApp} is still at pre-deploy version ${input.before.value.version}; expected a newer application version`
12010
+ );
12011
+ rollout = ` at version ${app.version} (up from ${input.before.value.version})`;
12012
+ } else {
12013
+ rollout = ` at version ${app.version} (Worker-only deploy; no application change printed)`;
12014
+ }
12015
+ if ("error" in input.health) return waiting(`health: GET /healthz failed: ${input.health.error}`);
12016
+ if (input.health.status !== 200)
12017
+ return waiting(
12018
+ `health: GET /healthz \u2192 HTTP ${input.health.status}; expected the deployed container's exact build identity`
12019
+ );
12020
+ if (!input.health.body || input.health.body.ok !== true)
12021
+ return waiting("health: /healthz is not ready or did not answer with JSON");
12022
+ const commit = servedCommit(input.health.body);
12023
+ if (!commit) return waiting("health: /healthz carries no build identity");
12024
+ if (commit !== input.expectedCommit)
12025
+ return waiting(`health: serving commit ${commit}, expected exact ${input.expectedCommit}`);
12026
+ if (input.health.body.draining === true)
12027
+ return waiting("health: the exact deployed commit answers but its container is draining");
12028
+ const target = input.target?.image ?? app.image ?? "an unchanged image";
12029
+ return {
12030
+ kind: "live",
12031
+ summary: `container application ${input.containerApp} targets ${target}${rollout}; /healthz serves exact ${commit}`
12032
+ };
12033
+ }
12034
+ function decideBotLive(input, deadlineMs = LIVE_GATE_DEADLINE_MS) {
12035
+ const decision = judge(input);
12036
+ if (decision.kind !== "waiting" || input.elapsedMs < deadlineMs) return decision;
12037
+ return {
12038
+ kind: "failed",
12039
+ reason: `${decision.reason} \u2014 still not live after ${Math.round(input.elapsedMs / MINUTE_MS)} min (deadline ${deadlineMs / MINUTE_MS} min)`
12040
+ };
12041
+ }
12042
+ var waiting;
12043
+ var init_botLiveGate = __esm({
12044
+ "../../src/deploy/botLiveGate.ts"() {
12045
+ "use strict";
12046
+ init_budgets();
12047
+ init_liveGate();
12048
+ waiting = (reason2) => ({ kind: "waiting", reason: reason2 });
12049
+ }
12050
+ });
12051
+
11965
12052
  // ../../src/execution/sandboxErrors.ts
11966
12053
  function fleetBusyRunEndedLine(run2, thread, err2) {
11967
12054
  if (typeof err2 !== "object" || err2 === null) return null;
@@ -12048,51 +12135,51 @@ function rolloutAdvanced(app, before, target) {
12048
12135
  reason: `rollout: pre-deploy version unreadable (${before.error}); application image ${app.image === null ? "?" : shortImage(app.image)} is not the deploy's ${shortImage(target.image)}`
12049
12136
  };
12050
12137
  }
12051
- function judge(input) {
12138
+ function judge2(input) {
12052
12139
  const worker = decideWorker(input.health, input.deployedCommit);
12053
- if (!worker.ok) return worker.fatal ? { kind: "failed", reason: worker.reason } : waiting(worker.reason);
12054
- if (input.app === null) return waiting("rollout: not read yet");
12055
- if ("error" in input.app) return waiting(`rollout: ${input.app.error}`);
12140
+ if (!worker.ok) return worker.fatal ? { kind: "failed", reason: worker.reason } : waiting2(worker.reason);
12141
+ if (input.app === null) return waiting2("rollout: not read yet");
12142
+ if ("error" in input.app) return waiting2(`rollout: ${input.app.error}`);
12056
12143
  const app = input.app.value;
12057
12144
  const appVersion = app.version;
12058
12145
  let advancedFrom = null;
12059
12146
  if (input.target) {
12060
12147
  const advanced = rolloutAdvanced(app, input.before, input.target);
12061
- if (!advanced.ok) return waiting(advanced.reason);
12148
+ if (!advanced.ok) return waiting2(advanced.reason);
12062
12149
  advancedFrom = advanced.from;
12063
12150
  }
12064
- if (input.instances === null) return waiting("rollout: instances not read yet");
12065
- if ("error" in input.instances) return waiting(`rollout: ${input.instances.error}`);
12151
+ if (input.instances === null) return waiting2("rollout: instances not read yet");
12152
+ if ("error" in input.instances) return waiting2(`rollout: ${input.instances.error}`);
12066
12153
  const running = input.instances.value.filter((i) => i.state === "running");
12067
12154
  const rolloutSummary = advancedFrom === null ? `Worker-only deploy \u2014 no container change (${running.length} running instance(s) on version ${appVersion})` : `rollout complete (${running.length} running instance(s) on version ${appVersion}, ${advancedFrom})`;
12068
12155
  const stale = running.filter((i) => i.version !== appVersion);
12069
12156
  if (stale.length > 0) {
12070
12157
  const versions = [...new Set(stale.map((i) => i.version === null ? "?" : String(i.version)))].join("/");
12071
- return waiting(
12158
+ return waiting2(
12072
12159
  `rollout in progress \u2014 ${stale.length} of ${running.length} running instance(s) still on version ${versions}, app version ${appVersion}`
12073
12160
  );
12074
12161
  }
12075
- if (input.probe === null) return waiting("probe: not sent yet");
12076
- if ("error" in input.probe) return waiting(`probe: ${input.probe.error}`);
12162
+ if (input.probe === null) return waiting2("probe: not sent yet");
12163
+ if ("error" in input.probe) return waiting2(`probe: ${input.probe.error}`);
12077
12164
  const body = input.probe.body;
12078
12165
  if (body.reason === FLEET_BUSY_REASON)
12079
- return waiting("probe: fleet busy \u2014 no free instance for the probe thread (max_instances reached)");
12166
+ return waiting2("probe: fleet busy \u2014 no free instance for the probe thread (max_instances reached)");
12080
12167
  if (typeof body.error === "string" && CONTAINER_STARTING.test(body.error))
12081
- return waiting("probe: container starting");
12168
+ return waiting2("probe: container starting");
12082
12169
  if ("error" in body) {
12083
12170
  const failed = body.error === "" ? "/exec failed with an EMPTY error" : `/exec failed \u2014 ${typeof body.error === "string" ? body.error : JSON.stringify(body.error)}`;
12084
- return waiting(`probe: ${failed} \u2014 the probe's container may still run the previous image`);
12171
+ return waiting2(`probe: ${failed} \u2014 the probe's container may still run the previous image`);
12085
12172
  }
12086
12173
  if (body.exitCode !== 0) {
12087
12174
  const stderr = typeof body.stderr === "string" ? body.stderr.trim() : "";
12088
- return waiting(`probe: \`${PROBE_COMMAND}\` exited ${String(body.exitCode)}${stderr ? `: ${stderr}` : ""}`);
12175
+ return waiting2(`probe: \`${PROBE_COMMAND}\` exited ${String(body.exitCode)}${stderr ? `: ${stderr}` : ""}`);
12089
12176
  }
12090
12177
  const stdout = typeof body.stdout === "string" ? body.stdout.trim() : "";
12091
- if (stdout !== "ok") return waiting(`probe: \`${PROBE_COMMAND}\` printed ${JSON.stringify(stdout)}`);
12178
+ if (stdout !== "ok") return waiting2(`probe: \`${PROBE_COMMAND}\` printed ${JSON.stringify(stdout)}`);
12092
12179
  const mine = input.instances.value.find((i) => i.name === input.probeThreadKey);
12093
- if (!mine) return waiting(`probe instance ${input.probeThreadKey} not listed yet`);
12180
+ if (!mine) return waiting2(`probe instance ${input.probeThreadKey} not listed yet`);
12094
12181
  if (mine.version !== appVersion)
12095
- return waiting(
12182
+ return waiting2(
12096
12183
  `probe instance ${input.probeThreadKey} is on version ${mine.version === null ? "?" : mine.version}, app version ${appVersion} \u2014 the probe landed on a previous image`
12097
12184
  );
12098
12185
  return {
@@ -12101,7 +12188,7 @@ function judge(input) {
12101
12188
  };
12102
12189
  }
12103
12190
  function decideSandboxLive(input, deadlineMs = LIVE_GATE_DEADLINE_MS) {
12104
- const d = judge(input);
12191
+ const d = judge2(input);
12105
12192
  if (d.kind !== "waiting" || input.elapsedMs < deadlineMs) return d;
12106
12193
  return {
12107
12194
  kind: "failed",
@@ -12175,7 +12262,7 @@ function parseExecStream(text) {
12175
12262
  return { error: `/exec answered no JSON document: ${JSON.stringify(text.trim().slice(0, 120))}` };
12176
12263
  }
12177
12264
  }
12178
- var PROBE_COMMAND, PROBE_TIMEOUT_MS, CONTAINER_STARTING, waiting, isRecord2, ANSI_SEQUENCE2, SECTION_GUTTER;
12265
+ var PROBE_COMMAND, PROBE_TIMEOUT_MS, CONTAINER_STARTING, waiting2, isRecord2, ANSI_SEQUENCE2, SECTION_GUTTER;
12179
12266
  var init_sandboxLiveGate = __esm({
12180
12267
  "../../src/deploy/sandboxLiveGate.ts"() {
12181
12268
  "use strict";
@@ -12184,7 +12271,7 @@ var init_sandboxLiveGate = __esm({
12184
12271
  PROBE_COMMAND = "echo ok";
12185
12272
  PROBE_TIMEOUT_MS = 6e4;
12186
12273
  CONTAINER_STARTING = /Container is starting/i;
12187
- waiting = (reason2) => ({ kind: "waiting", reason: reason2 });
12274
+ waiting2 = (reason2) => ({ kind: "waiting", reason: reason2 });
12188
12275
  isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
12189
12276
  ANSI_SEQUENCE2 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
12190
12277
  SECTION_GUTTER = /^[\s│├╭╰|]+/;
@@ -12510,26 +12597,6 @@ async function fetchHealthz(url) {
12510
12597
  const r = await readHealthz(url);
12511
12598
  return "body" in r ? r.body : void 0;
12512
12599
  }
12513
- async function waitUntilLive(step, healthUrl, expectedCommit, io, clock4, previousStartedAt) {
12514
- const started = clock4();
12515
- for (; ; ) {
12516
- const elapsed2 = clock4() - started;
12517
- const body = await fetchHealthz(healthUrl);
12518
- const d = decideLive(body, expectedCommit, elapsed2, LIVE_GATE_DEADLINE_MS, {
12519
- ...previousStartedAt !== void 0 ? { previousStartedAt } : {}
12520
- });
12521
- if (d.kind === "live") return { live: true, detail: `commit ${d.commit.slice(0, 7)}`, waitedMs: elapsed2 };
12522
- if (d.kind === "timeout")
12523
- return {
12524
- live: false,
12525
- reason: `${d.reason} \u2014 gave up after ${Math.round(elapsed2 / 6e4)} min (drain deadline ${LIVE_GATE_DEADLINE_MS / 6e4} min)`
12526
- };
12527
- io.log(
12528
- `[deploy:all] ${step.name}: deployed, not live yet \u2014 ${d.reason} (${Math.floor(elapsed2 / 6e4)}m ${Math.floor(elapsed2 % 6e4 / 1e3)}s)`
12529
- );
12530
- await sleep(LIVE_GATE_POLL_MS);
12531
- }
12532
- }
12533
12600
  async function wranglerJson(dir, args) {
12534
12601
  const r = await run("npx", ["wrangler", ...args], { cwd: workerDir(dir), unset: UNSET_ENV });
12535
12602
  if (r.code !== 0)
@@ -12550,6 +12617,29 @@ async function resolveContainerAppId(dir, containerApp) {
12550
12617
  containerAppIds.set(containerApp, id);
12551
12618
  return { value: id };
12552
12619
  }
12620
+ async function waitUntilBotLive(step, gate, expectedCommit, rollout, io, deps = defaultSandboxGateDeps) {
12621
+ const started = deps.now();
12622
+ for (; ; ) {
12623
+ const elapsed2 = deps.now() - started;
12624
+ const app = await deps.readAppState(step.dir, gate.containerApp);
12625
+ const health = await deps.readHealth(gate.healthUrl);
12626
+ const decision = decideBotLive({
12627
+ containerApp: gate.containerApp,
12628
+ health,
12629
+ app,
12630
+ before: rollout.before,
12631
+ target: rollout.target,
12632
+ expectedCommit,
12633
+ elapsedMs: elapsed2
12634
+ });
12635
+ if (decision.kind === "live") return { live: true, detail: decision.summary, waitedMs: deps.now() - started };
12636
+ if (decision.kind === "failed") return { live: false, reason: decision.reason };
12637
+ io.log(
12638
+ `[deploy:all] ${step.name}: deployed, not live yet \u2014 ${decision.reason} (${Math.floor(elapsed2 / 6e4)}m ${Math.floor(elapsed2 % 6e4 / 1e3)}s)`
12639
+ );
12640
+ await deps.sleep(LIVE_GATE_POLL_MS);
12641
+ }
12642
+ }
12553
12643
  async function waitUntilSandboxLive(step, gate, expectedCommit, rollout, io, deps = defaultSandboxGateDeps) {
12554
12644
  const bearer = deps.env[gate.bearerEnv];
12555
12645
  if (!bearer)
@@ -12590,7 +12680,7 @@ async function waitUntilSandboxLive(step, gate, expectedCommit, rollout, io, dep
12590
12680
  async function readAppBeforeUpload(step, gate, io, deps) {
12591
12681
  const before = await deps.readAppState(step.dir, gate.containerApp);
12592
12682
  io.log(
12593
- "value" in before ? `[deploy:all] ${step.name}: container application at version ${before.value.version}${before.value.image ? ` (image ${shortImage(before.value.image)})` : ""} before the upload` : `[deploy:all] ${step.name}: could not read the container application before the upload \u2014 ${before.error}; the gate will need the deploy's image to show`
12683
+ "value" in before ? `[deploy:all] ${step.name}: container application at version ${before.value.version}${before.value.image ? ` (image ${shortImage(before.value.image)})` : ""} before the upload` : gate.kind === "sandbox" ? `[deploy:all] ${step.name}: could not read the container application before the upload \u2014 ${before.error}; the gate will need the deploy's image to show` : `[deploy:all] ${step.name}: could not read the container application before the upload \u2014 ${before.error}; the gate requires a readable pre-deploy version to prove advancement`
12594
12684
  );
12595
12685
  return before;
12596
12686
  }
@@ -12666,8 +12756,7 @@ async function deployStepLoop(step, plan3, expectedCommit, io, deps, exec, root,
12666
12756
  for (; ; ) {
12667
12757
  io.log(`
12668
12758
  [deploy:all] \u25B6 ${step.name} (${step.script}) \u2014 ${step.dir}: ${step.command.join(" ")}`);
12669
- const sandbox = step.liveGate?.kind === "sandbox" ? { gate: step.liveGate, before: await readAppBeforeUpload(step, step.liveGate, io, deps) } : void 0;
12670
- const previousStartedAt = step.liveGate && step.liveGate.kind !== "sandbox" ? await fetchHealthz(step.liveGate.healthUrl).then((b) => b ? servedStartedAt(b) : void 0) : void 0;
12759
+ const application = step.liveGate ? { before: await readAppBeforeUpload(step, step.liveGate, io, deps) } : void 0;
12671
12760
  const r = await exec(step, io);
12672
12761
  const outcome = classifyDeployOutput(r.code, r.output);
12673
12762
  if (outcome.kind === "deployed") {
@@ -12680,15 +12769,20 @@ async function deployStepLoop(step, plan3, expectedCommit, io, deps, exec, root,
12680
12769
  );
12681
12770
  const liveGate = step.liveGate;
12682
12771
  const gate = await root.span("deploy.wait_live", async (wait2) => {
12683
- let g;
12684
- if (sandbox) {
12685
- const target = rolloutTargetFromDeployOutput(r.output);
12686
- const from = "value" in sandbox.before ? `version ${sandbox.before.value.version}` : "its pre-deploy version";
12687
- io.log(
12688
- target ? `[deploy:all] ${step.name}: wrangler printed a container change \u2014 ${target.image ? `image ${shortImage(target.image)}` : "configuration only, image unchanged"}; the application must leave ${from}` : `[deploy:all] ${step.name}: wrangler printed no container change \u2014 Worker-only deploy, no rollout expected`
12689
- );
12690
- g = await waitUntilSandboxLive(step, sandbox.gate, expectedCommit, { ...sandbox, target }, io, deps);
12691
- } else g = await waitUntilLive(step, liveGate.healthUrl, expectedCommit, io, deps.now, previousStartedAt);
12772
+ if (!application) throw new Error(`${step.name}: live gate has no application state`);
12773
+ const target = rolloutTargetFromDeployOutput(r.output);
12774
+ const from = "value" in application.before ? `version ${application.before.value.version}` : "its pre-deploy version";
12775
+ io.log(
12776
+ target ? `[deploy:all] ${step.name}: wrangler printed a container change \u2014 ${target.image ? `image ${shortImage(target.image)}` : "configuration only, image unchanged"}; the application must leave ${from}` : `[deploy:all] ${step.name}: wrangler printed no container change \u2014 Worker-only deploy, no rollout expected`
12777
+ );
12778
+ const g = liveGate.kind === "sandbox" ? await waitUntilSandboxLive(
12779
+ step,
12780
+ liveGate,
12781
+ expectedCommit,
12782
+ { before: application.before, target },
12783
+ io,
12784
+ deps
12785
+ ) : await waitUntilBotLive(step, liveGate, expectedCommit, { before: application.before, target }, io, deps);
12692
12786
  wait2.setAttrs(g.live ? { outcome: "live", waitedMs: g.waitedMs } : { outcome: "not_live" });
12693
12787
  return g;
12694
12788
  });
@@ -13105,6 +13199,7 @@ var init_run = __esm({
13105
13199
  init_planeDeploy();
13106
13200
  init_wranglerTemplate();
13107
13201
  init_supersede();
13202
+ init_botLiveGate();
13108
13203
  init_sandboxLiveGate();
13109
13204
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
13110
13205
  workerDir = (dir) => workPath(OPERATOR_ROOT, dir);
@@ -17364,7 +17459,7 @@ async function branchHeadSubject(repo, branch2) {
17364
17459
  if (typeof message4 !== "string" || message4.trim() === "") return void 0;
17365
17460
  return message4.split("\n", 1)[0].trim();
17366
17461
  }
17367
- async function headRefTipSha(repo, branch2, headers) {
17462
+ async function readHeadRef(repo, branch2, headers) {
17368
17463
  let res;
17369
17464
  try {
17370
17465
  res = await fetch(`https://api.github.com/repos/${repo}/git/ref/heads/${encodeGithubRef(branch2)}`, {
@@ -17372,13 +17467,17 @@ async function headRefTipSha(repo, branch2, headers) {
17372
17467
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
17373
17468
  });
17374
17469
  } catch {
17375
- return void 0;
17470
+ return { kind: "unverified" };
17376
17471
  }
17377
- if (!res.ok) return void 0;
17472
+ if (res.status === 404) return { kind: "missing" };
17473
+ if (!res.ok) return { kind: "unverified" };
17378
17474
  const data = await res.json().catch(() => null);
17379
- if (data?.object?.type !== "commit") return void 0;
17380
- const sha = data.object.sha;
17381
- return typeof sha === "string" && /^[0-9a-f]{40}$/.test(sha) ? sha : void 0;
17475
+ const sha = data?.object?.sha;
17476
+ return data?.object?.type === "commit" && typeof sha === "string" && /^[0-9a-f]{40}$/.test(sha) ? { kind: "verified", sha } : { kind: "unverified" };
17477
+ }
17478
+ async function headRefTipSha(repo, branch2, headers) {
17479
+ const read = await readHeadRef(repo, branch2, headers);
17480
+ return read.kind === "verified" ? read.sha : void 0;
17382
17481
  }
17383
17482
  function preferRefTip(where, prSha, refTip, branch2) {
17384
17483
  if (refTip === void 0) return prSha;
@@ -17428,8 +17527,11 @@ async function fetchPullRequestFacts(pr) {
17428
17527
  const prSha = typeof data.head?.sha === "string" && /^[0-9a-f]{40}$/.test(data.head.sha) ? data.head.sha : void 0;
17429
17528
  const sameRepoHead = headRepo === pr.repo.toLowerCase();
17430
17529
  const headRef = typeof data.head?.ref === "string" && data.head.ref ? data.head.ref : void 0;
17431
- const headBranchExists = sameRepoHead && headRef !== void 0 ? await fetchRefExists(pr.repo, headRef) : void 0;
17432
- const sha = sameRepoHead && headRef !== void 0 && headBranchExists !== false ? preferRefTip(`${pr.repo}#${pr.number}`, prSha, await headRefTipSha(pr.repo, headRef, headers), headRef) : prSha;
17530
+ const headRead = sameRepoHead && headRef !== void 0 ? await readHeadRef(pr.repo, headRef, headers) : void 0;
17531
+ const headBranchExists = headRead?.kind === "verified" ? true : headRead?.kind === "missing" ? false : void 0;
17532
+ const refTip = headRead?.kind === "verified" ? headRead.sha : void 0;
17533
+ const sha = sameRepoHead && headRef !== void 0 ? preferRefTip(`${pr.repo}#${pr.number}`, prSha, refTip, headRef) : prSha;
17534
+ const verifiedHead = sameRepoHead && headRef !== void 0 && prSha !== void 0 && refTip === prSha ? { repo: pr.repo, ref: headRef, sha: refTip } : void 0;
17433
17535
  let closedBy = typeof data.closed_by?.login === "string" ? data.closed_by.login : void 0;
17434
17536
  if (data.state === "closed" && !(typeof data.merged_at === "string" && data.merged_at) && closedBy === void 0) {
17435
17537
  try {
@@ -17454,6 +17556,7 @@ async function fetchPullRequestFacts(pr) {
17454
17556
  } : {},
17455
17557
  ...headRef !== void 0 ? { headRef } : {},
17456
17558
  ...sha ? { headSha: sha } : {},
17559
+ ...verifiedHead !== void 0 ? { verifiedHead } : {},
17457
17560
  ...headBranchExists !== void 0 ? { headBranchExists } : {},
17458
17561
  sameRepoHead,
17459
17562
  ...typeof data.base?.ref === "string" && data.base.ref ? { baseRef: data.base.ref } : {},
@@ -21081,8 +21184,8 @@ function pageEvents(events, opts) {
21081
21184
  if (page2.length < rest.length && page2.length > 0) out.nextAfterSeq = page2[page2.length - 1].seq;
21082
21185
  return out;
21083
21186
  }
21084
- function toListItem(record2, bytes) {
21085
- const { events: _events, ...rest } = record2;
21187
+ function toListItem(record3, bytes) {
21188
+ const { events: _events, ...rest } = record3;
21086
21189
  return { ...rest, bytes };
21087
21190
  }
21088
21191
  function retentionPolicyOf(cfg) {
@@ -21233,19 +21336,19 @@ var init_runStore = __esm({
21233
21336
  }
21234
21337
  abandoned() {
21235
21338
  }
21236
- async put(record2) {
21237
- if (!isValidRunId(record2.id))
21339
+ async put(record3) {
21340
+ if (!isValidRunId(record3.id))
21238
21341
  return { ok: true, retained: this.readIndex().length, stored: false, rewritten: false };
21239
21342
  this.ensureDir();
21240
- const content = JSON.stringify(record2);
21343
+ const content = JSON.stringify(record3);
21241
21344
  const bytes = utf8ByteLength(content);
21242
21345
  const index = this.readIndex();
21243
- const prev = index.find((r) => r.id === record2.id);
21244
- const rewritten = prev !== void 0 && !sameStoredVersion(prev, { ...record2, bytes });
21245
- this.writeAtomic(this.recordPath(record2.id), content);
21246
- const item = toListItem(record2, bytes);
21247
- const kept = this.compact([...index.filter((r) => r.id !== record2.id), item]);
21248
- return { ok: true, retained: kept.length, stored: kept.some((r) => r.id === record2.id), rewritten };
21346
+ const prev = index.find((r) => r.id === record3.id);
21347
+ const rewritten = prev !== void 0 && !sameStoredVersion(prev, { ...record3, bytes });
21348
+ this.writeAtomic(this.recordPath(record3.id), content);
21349
+ const item = toListItem(record3, bytes);
21350
+ const kept = this.compact([...index.filter((r) => r.id !== record3.id), item]);
21351
+ return { ok: true, retained: kept.length, stored: kept.some((r) => r.id === record3.id), rewritten };
21249
21352
  }
21250
21353
  /** Retention is decided from the index alone (no per-file stat); the one
21251
21354
  * file that matters is then read directly — a torn or missing file lands
@@ -21272,16 +21375,16 @@ var init_runStore = __esm({
21272
21375
  return selectListItems(this.retainedIntact(), opts);
21273
21376
  }
21274
21377
  async events(id, opts) {
21275
- const record2 = await this.get(id);
21276
- return record2 ? pageEvents(record2.events, opts) : null;
21378
+ const record3 = await this.get(id);
21379
+ return record3 ? pageEvents(record3.events, opts) : null;
21277
21380
  }
21278
21381
  async usage(query) {
21279
21382
  const items = this.retainedIntact();
21280
21383
  const records = [];
21281
21384
  for (const item of items) {
21282
21385
  if (item.finishedAt < query.sinceMs || item.finishedAt >= query.untilMs) continue;
21283
- const record2 = await this.get(item.id);
21284
- if (record2) records.push(record2);
21386
+ const record3 = await this.get(item.id);
21387
+ if (record3) records.push(record3);
21285
21388
  }
21286
21389
  const parent = (id) => {
21287
21390
  const p = items.find((i) => i.id === id);
@@ -21999,24 +22102,24 @@ function predicateWith(rules, actor, action, resourceType, kind) {
21999
22102
  function predicateFor(actor, action, resourceType, kind) {
22000
22103
  return predicateWith(POLICY, actor, action, resourceType, kind);
22001
22104
  }
22002
- function matchesPredicate(predicate, record2) {
22105
+ function matchesPredicate(predicate, record3) {
22003
22106
  switch (predicate.kind) {
22004
22107
  case "none":
22005
22108
  return false;
22006
22109
  case "all":
22007
22110
  return true;
22008
22111
  case "channels-in":
22009
- return record2.channelId !== void 0 && holds(predicate.channelIds, record2.channelId);
22112
+ return record3.channelId !== void 0 && holds(predicate.channelIds, record3.channelId);
22010
22113
  case "user-is":
22011
- return record2.userId !== void 0 && record2.userId === predicate.userId;
22114
+ return record3.userId !== void 0 && record3.userId === predicate.userId;
22012
22115
  case "repos-in":
22013
- return record2.repo !== void 0 && holds(predicate.repos, record2.repo);
22116
+ return record3.repo !== void 0 && holds(predicate.repos, record3.repo);
22014
22117
  case "visibility-in":
22015
- return predicate.visibilities.has(record2.channelVisibility ?? "unknown");
22118
+ return predicate.visibilities.has(record3.channelVisibility ?? "unknown");
22016
22119
  case "or":
22017
- return predicate.of.some((p) => matchesPredicate(p, record2));
22120
+ return predicate.of.some((p) => matchesPredicate(p, record3));
22018
22121
  case "and":
22019
- return predicate.of.length > 0 && predicate.of.every((p) => matchesPredicate(p, record2));
22122
+ return predicate.of.length > 0 && predicate.of.every((p) => matchesPredicate(p, record3));
22020
22123
  }
22021
22124
  return false;
22022
22125
  }
@@ -25602,9 +25705,9 @@ function endingCauseWords(cause) {
25602
25705
  function isReissueSteerText(text) {
25603
25706
  return /^the model provider .+ is answering again — re-issue the held turn and continue$/.test(text.trim());
25604
25707
  }
25605
- function waitingWords(waiting2) {
25606
- if (waiting2.length === 0) return "its turn";
25607
- return waiting2.map(
25708
+ function waitingWords(waiting3) {
25709
+ if (waiting3.length === 0) return "its turn";
25710
+ return waiting3.map(
25608
25711
  (c) => c.kind === "thread_free" ? "the thread's live run" : c.kind === "deploy_settled" ? "the pending deploy" : c.kind === "seat" ? `a seat on ${c.resident}` : c.kind === "memory" ? `memory on ${c.resident}` : c.kind === "provider_up" ? `the ${c.provider} provider` : `the ${c.window} window`
25609
25712
  ).join(", then ");
25610
25713
  }
@@ -28876,23 +28979,23 @@ var init_turnEffort = __esm({
28876
28979
  function tokenize2(text) {
28877
28980
  return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
28878
28981
  }
28879
- function keywordMatch(record2, query) {
28982
+ function keywordMatch(record3, query) {
28880
28983
  const queryTokens = typeof query === "string" ? tokenize2(query) : query;
28881
28984
  if (queryTokens.length === 0) return 0;
28882
- const recordTokens = /* @__PURE__ */ new Set([...tokenize2(record2.keywords.join(" ")), ...tokenize2(record2.text)]);
28985
+ const recordTokens = /* @__PURE__ */ new Set([...tokenize2(record3.keywords.join(" ")), ...tokenize2(record3.text)]);
28883
28986
  let hits = 0;
28884
28987
  for (const token2 of queryTokens) {
28885
28988
  if (recordTokens.has(token2)) hits++;
28886
28989
  }
28887
28990
  return hits / queryTokens.length;
28888
28991
  }
28889
- function recencyScore(record2, now, tau = RECENCY_TAU_MS) {
28890
- const ts = record2.lastUsedAt ?? record2.createdAt;
28992
+ function recencyScore(record3, now, tau = RECENCY_TAU_MS) {
28993
+ const ts = record3.lastUsedAt ?? record3.createdAt;
28891
28994
  const age = Math.max(0, now - ts);
28892
28995
  return Math.exp(-age / tau);
28893
28996
  }
28894
- function scoreRecord(record2, query, now, weights = DEFAULT_WEIGHTS, tau = RECENCY_TAU_MS) {
28895
- return weights.keyword * keywordMatch(record2, query) + weights.recency * recencyScore(record2, now, tau);
28997
+ function scoreRecord(record3, query, now, weights = DEFAULT_WEIGHTS, tau = RECENCY_TAU_MS) {
28998
+ return weights.keyword * keywordMatch(record3, query) + weights.recency * recencyScore(record3, now, tau);
28896
28999
  }
28897
29000
  function estimateTokens(text) {
28898
29001
  return Math.ceil(text.length / 4);
@@ -28900,11 +29003,11 @@ function estimateTokens(text) {
28900
29003
  function applyBudget(records, budget) {
28901
29004
  const out = [];
28902
29005
  let tokens = 0;
28903
- for (const record2 of records) {
29006
+ for (const record3 of records) {
28904
29007
  if (out.length >= budget.maxRecords) break;
28905
- const cost = estimateTokens(renderMemoryBullet(record2));
29008
+ const cost = estimateTokens(renderMemoryBullet(record3));
28906
29009
  if (out.length > 0 && tokens + cost > budget.maxTokens) break;
28907
- out.push(record2);
29010
+ out.push(record3);
28908
29011
  tokens += cost;
28909
29012
  }
28910
29013
  return out;
@@ -28915,8 +29018,8 @@ function memoryBlockPrefix(resource) {
28915
29018
  function sanitizeMemoryField(s) {
28916
29019
  return s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, " ").replace(/\s+/g, " ").trim().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
28917
29020
  }
28918
- function renderMemoryBullet(record2) {
28919
- return `- ${sanitizeMemoryField(record2.text)} (source: ${sanitizeMemoryField(record2.sourceThreadKey)})`;
29021
+ function renderMemoryBullet(record3) {
29022
+ return `- ${sanitizeMemoryField(record3.text)} (source: ${sanitizeMemoryField(record3.sourceThreadKey)})`;
28920
29023
  }
28921
29024
  function renderMemoryBlock(resource, records) {
28922
29025
  const bullets = records.map(renderMemoryBullet);
@@ -29502,7 +29605,7 @@ function parseConfirmation(v) {
29502
29605
  function isRefusal(v) {
29503
29606
  return typeof v === "string" && CONFIRMATION_REFUSALS.includes(v);
29504
29607
  }
29505
- function judge2(rows, id, actorIds, now, kind) {
29608
+ function judge3(rows, id, actorIds, now, kind) {
29506
29609
  const row2 = rows.get(id);
29507
29610
  if (!row2) return { ok: false, refused: "used" };
29508
29611
  if (kind === "consume" && row2.expiresAt <= now) {
@@ -29572,13 +29675,13 @@ var init_confirmations = __esm({
29572
29675
  }
29573
29676
  async consume(id, actorIds) {
29574
29677
  const rows = this.read();
29575
- const out = judge2(rows, id, actorIds, this.clock(), "consume");
29678
+ const out = judge3(rows, id, actorIds, this.clock(), "consume");
29576
29679
  this.write(rows);
29577
29680
  return out;
29578
29681
  }
29579
29682
  async cancel(id, actorIds) {
29580
29683
  const rows = this.read();
29581
- const out = judge2(rows, id, actorIds, this.clock(), "cancel");
29684
+ const out = judge3(rows, id, actorIds, this.clock(), "cancel");
29582
29685
  this.write(rows);
29583
29686
  return out;
29584
29687
  }
@@ -29586,7 +29689,7 @@ var init_confirmations = __esm({
29586
29689
  const rows = this.read();
29587
29690
  const id = threadRowId(rows, threadKey);
29588
29691
  if (id === void 0) return { ok: false, refused: "used" };
29589
- const out = judge2(rows, id, actorIds, this.clock(), "cancel");
29692
+ const out = judge3(rows, id, actorIds, this.clock(), "cancel");
29590
29693
  this.write(rows);
29591
29694
  return out;
29592
29695
  }
@@ -30067,16 +30170,16 @@ async function reflect(deps) {
30067
30170
  dropped.push(`${placement.from} (${placement.reason})`);
30068
30171
  continue;
30069
30172
  }
30070
- let record2 = plain7;
30173
+ let record3 = plain7;
30071
30174
  if (placement.narrowed) {
30072
- const { supersedes: _supersedes, restates: _restates, ...withoutPointers } = record2;
30073
- record2 = withoutPointers;
30175
+ const { supersedes: _supersedes, restates: _restates, ...withoutPointers } = record3;
30176
+ record3 = withoutPointers;
30074
30177
  const line4 = `${placement.narrowed.from} \u2192 ${placement.target.kind} (${placement.narrowed.reason})`;
30075
30178
  narrowed.set(line4, (narrowed.get(line4) ?? 0) + 1);
30076
30179
  }
30077
30180
  let batch = byScope.get(placement.target.key);
30078
30181
  if (!batch) byScope.set(placement.target.key, batch = []);
30079
- batch.push(record2);
30182
+ batch.push(record3);
30080
30183
  }
30081
30184
  if (narrowed.size > 0)
30082
30185
  warn(`write narrowed by policy: ${[...narrowed].map(([line4, n2]) => `${n2}\xD7 ${line4}`).join(", ")}`);
@@ -31907,8 +32010,8 @@ ${line4}`
31907
32010
  const list = await call(() => api.residents());
31908
32011
  if (list.status !== 200) throw residentFailure(list);
31909
32012
  const residents = list.data.residents ?? [];
31910
- const record2 = residents.find((rec2) => rec2.resource === repoResourceId(args.slug));
31911
- if (!record2) {
32013
+ const record3 = residents.find((rec2) => rec2.resource === repoResourceId(args.slug));
32014
+ if (!record3) {
31912
32015
  const slugs = residents.map(
31913
32016
  (rec2) => String(rec2.resource ?? "").replace(/^repo:/, "").toLowerCase()
31914
32017
  ).filter(Boolean);
@@ -31928,7 +32031,7 @@ ${line4}`
31928
32031
  ${line4}` : ""}`
31929
32032
  );
31930
32033
  }
31931
- body.commands = { ...obj(record2.commands), ...commands };
32034
+ body.commands = { ...obj(record3.commands), ...commands };
31932
32035
  }
31933
32036
  if (options.ref !== void 0) body.defaultRef = options.ref;
31934
32037
  const r = await call(() => api.reconfigure(body));
@@ -31953,11 +32056,11 @@ ${line4}` : ""}`
31953
32056
 
31954
32057
  // ../../src/core/reviewAbridge.ts
31955
32058
  import { resolve as resolve10 } from "node:path";
31956
- function meatArtifactOf(record2) {
31957
- return record2.events.find((e) => isReadingDiff(e) && e.poweredBy === "meat");
32059
+ function meatArtifactOf(record3) {
32060
+ return record3.events.find((e) => isReadingDiff(e) && e.poweredBy === "meat");
31958
32061
  }
31959
- function gitArtifactOf(record2) {
31960
- return record2.events.find((e) => isReadingDiff(e) && e.poweredBy === "git");
32062
+ function gitArtifactOf(record3) {
32063
+ return record3.events.find((e) => isReadingDiff(e) && e.poweredBy === "git");
31961
32064
  }
31962
32065
  function summarize(a) {
31963
32066
  return {
@@ -31971,16 +32074,16 @@ function summarize(a) {
31971
32074
  };
31972
32075
  }
31973
32076
  async function appendReviewArtifact(store, runId3, artifact, at) {
31974
- const record2 = await store.get(runId3);
31975
- if (!record2) throw new Error("the run's record is gone \u2014 nothing to append to");
31976
- const kept = record2.events.filter((e) => !(isReadingDiff(e) && e.poweredBy === "meat"));
31977
- const replaced = kept.length !== record2.events.length;
31978
- const seq = Math.max(0, ...storedEventSeqs(record2.events), ...record2.events.map((e) => e.seq ?? 0)) + 1;
32077
+ const record3 = await store.get(runId3);
32078
+ if (!record3) throw new Error("the run's record is gone \u2014 nothing to append to");
32079
+ const kept = record3.events.filter((e) => !(isReadingDiff(e) && e.poweredBy === "meat"));
32080
+ const replaced = kept.length !== record3.events.length;
32081
+ const seq = Math.max(0, ...storedEventSeqs(record3.events), ...record3.events.map((e) => e.seq ?? 0)) + 1;
31979
32082
  const event = { type: "review_artifact", artifact: "reading_diff", ...artifact, at, seq };
31980
32083
  const next = fitRecordToBudget({
31981
- ...record2,
32084
+ ...record3,
31982
32085
  events: [...kept, event],
31983
- eventCount: record2.eventCount + (replaced ? 0 : 1),
32086
+ eventCount: record3.eventCount + (replaced ? 0 : 1),
31984
32087
  storedEventCount: kept.length + 1
31985
32088
  });
31986
32089
  if (!next.events.includes(event))
@@ -32069,19 +32172,19 @@ var init_reviewAbridge = __esm({
32069
32172
  async decide(req) {
32070
32173
  const job = this.jobs.get(req.runId);
32071
32174
  if (job) return { state: "running", startedAt: job.startedAt };
32072
- const record2 = await this.deps.store.get(req.runId);
32073
- if (!record2) throw new AbridgeRefusal("not_found", "run not found");
32074
- const existing = meatArtifactOf(record2);
32175
+ const record3 = await this.deps.store.get(req.runId);
32176
+ if (!record3) throw new AbridgeRefusal("not_found", "run not found");
32177
+ const existing = meatArtifactOf(record3);
32075
32178
  if (existing && !req.force) return { state: "done", reused: true, artifact: summarize(existing) };
32076
32179
  const failed = this.failures.get(req.runId);
32077
32180
  if (failed && !req.force) return { state: "failed", ...failed };
32078
- const git2 = gitArtifactOf(record2);
32181
+ const git2 = gitArtifactOf(record3);
32079
32182
  if (!git2)
32080
32183
  throw new AbridgeRefusal("conflict", `run ${req.runId} carries no reading diff \u2014 only PR review runs record one`);
32081
32184
  const startedAt = this.deps.clock();
32082
32185
  const model = req.model ?? this.deps.defaultModel();
32083
32186
  this.failures.delete(req.runId);
32084
- const done = this.produce(record2, git2, model).finally(() => this.jobs.delete(req.runId));
32187
+ const done = this.produce(record3, git2, model).finally(() => this.jobs.delete(req.runId));
32085
32188
  this.jobs.set(req.runId, { startedAt, done });
32086
32189
  return { state: "running", startedAt };
32087
32190
  }
@@ -32091,8 +32194,8 @@ var init_reviewAbridge = __esm({
32091
32194
  if (job) return { state: "running", startedAt: job.startedAt };
32092
32195
  const failed = this.failures.get(runId3);
32093
32196
  if (failed) return { state: "failed", ...failed };
32094
- const record2 = await this.deps.store.get(runId3);
32095
- const existing = record2 ? meatArtifactOf(record2) : void 0;
32197
+ const record3 = await this.deps.store.get(runId3);
32198
+ const existing = record3 ? meatArtifactOf(record3) : void 0;
32096
32199
  return existing ? { state: "done", reused: true, artifact: summarize(existing) } : { state: "absent" };
32097
32200
  }
32098
32201
  /** The running job's final state, or the current state when none runs. */
@@ -32123,10 +32226,10 @@ var init_reviewAbridge = __esm({
32123
32226
  if (!(err2 instanceof AbridgeRefusal)) this.deps.warn(`[reading-diff] ${runId3} abridge failed: ${describe(err2)}`);
32124
32227
  }
32125
32228
  }
32126
- async produce(record2, git2, model) {
32229
+ async produce(record3, git2, model) {
32127
32230
  const at = () => this.deps.clock();
32128
32231
  try {
32129
- const input = await this.input(record2, git2);
32232
+ const input = await this.input(record3, git2);
32130
32233
  const meat = await this.deps.meat({ diff: input.diff, model, timeoutMs: this.deps.timeoutMs() });
32131
32234
  if (!meat.ok) throw new Error(meat.reason);
32132
32235
  const capped4 = capDiff(sanitizeArtifactText(meat.result.diff));
@@ -32142,12 +32245,12 @@ var init_reviewAbridge = __esm({
32142
32245
  inputBytes: input.bytes,
32143
32246
  model
32144
32247
  };
32145
- const stored = await appendReviewArtifact(this.deps.store, record2.id, artifact, at());
32146
- return this.conclude(record2.id, { state: "done", reused: false, artifact: summarize(stored) });
32248
+ const stored = await appendReviewArtifact(this.deps.store, record3.id, artifact, at());
32249
+ return this.conclude(record3.id, { state: "done", reused: false, artifact: summarize(stored) });
32147
32250
  } catch (err2) {
32148
32251
  const failed = { reason: redactSecrets(describe(err2)), at: at() };
32149
- this.failures.set(record2.id, failed);
32150
- return this.conclude(record2.id, { state: "failed", ...failed });
32252
+ this.failures.set(record3.id, failed);
32253
+ return this.conclude(record3.id, { state: "failed", ...failed });
32151
32254
  }
32152
32255
  }
32153
32256
  conclude(runId3, state) {
@@ -32156,8 +32259,8 @@ var init_reviewAbridge = __esm({
32156
32259
  }
32157
32260
  /** The complete diff: GitHub's compare of base...head first; the recorded
32158
32261
  * git diff only when that fails AND it is whole. */
32159
- async input(record2, git2) {
32160
- const meta = record2.events.find((e) => e.type === "run_meta");
32262
+ async input(record3, git2) {
32263
+ const meta = record3.events.find((e) => e.type === "run_meta");
32161
32264
  let compareFailure;
32162
32265
  if (!meta?.repo || !meta.headSha) compareFailure = "the run records no repo and head to compare";
32163
32266
  else {
@@ -32176,7 +32279,7 @@ var init_reviewAbridge = __esm({
32176
32279
  }
32177
32280
  }
32178
32281
  if (git2.truncated) throw new Error(`diff unavailable: ${compareFailure} and the recorded diff is truncated`);
32179
- this.deps.warn(`[reading-diff] ${record2.id} using the recorded diff: ${compareFailure}`);
32282
+ this.deps.warn(`[reading-diff] ${record3.id} using the recorded diff: ${compareFailure}`);
32180
32283
  return { diff: git2.diff, source: "recorded", bytes: Buffer.byteLength(git2.diff) };
32181
32284
  }
32182
32285
  };
@@ -32882,18 +32985,20 @@ function createRunsService(deps) {
32882
32985
  try {
32883
32986
  const row2 = await ledger.planeQueued(id);
32884
32987
  if (!row2) return null;
32988
+ const stampedChannel = row2.request.channelId;
32885
32989
  const channelAt = row2.threadKey.lastIndexOf(":");
32886
- const waiting2 = waitingWords(row2.conditions);
32990
+ const channelId = typeof stampedChannel === "string" ? stampedChannel : channelAt > 0 ? row2.threadKey.slice(0, channelAt) : void 0;
32991
+ const waiting3 = waitingWords(row2.conditions);
32887
32992
  return {
32888
32993
  id: row2.runId,
32889
- ...channelAt > 0 ? { channelId: row2.threadKey.slice(0, channelAt) } : {},
32994
+ ...channelId !== void 0 ? { channelId } : {},
32890
32995
  userId: row2.requester,
32891
32996
  threadKey: row2.threadKey,
32892
32997
  startedAt: row2.queuedAt,
32893
32998
  finished: false,
32894
32999
  eventCount: 0,
32895
- queued: { state: row2.state, position: row2.position, waiting: waiting2 },
32896
- activity: row2.state === "waiting" ? `queued at position ${row2.position} \u2014 waiting on ${waiting2}` : row2.state === "admitted" ? "admitted \u2014 starting" : "withdrawn"
33000
+ queued: { state: row2.state, position: row2.position, waiting: waiting3 },
33001
+ activity: row2.state === "waiting" ? `queued at position ${row2.position} \u2014 waiting on ${waiting3}` : row2.state === "admitted" ? "admitted \u2014 starting" : "withdrawn"
32897
33002
  };
32898
33003
  } catch (err2) {
32899
33004
  warn(`[runs] plane queued read failed for ${id}: ${describe13(err2)}`);
@@ -32978,7 +33083,7 @@ function createRunsService(deps) {
32978
33083
  if (row2) {
32979
33084
  const finishedAt = snap?.finishedAt ?? at;
32980
33085
  const m = row2.meta;
32981
- const record2 = assembleRunRecord({
33086
+ const record3 = assembleRunRecord({
32982
33087
  run: { id, ...summary?.label !== void 0 ? { label: summary.label } : {} },
32983
33088
  snap,
32984
33089
  agent: m.agent ?? "ship",
@@ -33006,7 +33111,7 @@ function createRunsService(deps) {
33006
33111
  }),
33007
33112
  seal
33008
33113
  });
33009
- const done = await ledger.finish(id, row2.ownerGen, record2);
33114
+ const done = await ledger.finish(id, row2.ownerGen, record3);
33010
33115
  if (!done.ok) warn(`[runs] run ledger finish refused for ${id} (${done.reason ?? "unknown"})`);
33011
33116
  }
33012
33117
  } catch (err2) {
@@ -33104,12 +33209,12 @@ function createRunsService(deps) {
33104
33209
  const queued = await queuedRun(id);
33105
33210
  return queued ? { ok: true, value: queued } : notFound;
33106
33211
  }
33107
- const record2 = await storeGet(id);
33108
- if (!record2) {
33212
+ const record3 = await storeGet(id);
33213
+ if (!record3) {
33109
33214
  const queued = await queuedRun(id);
33110
33215
  return queued ? { ok: true, value: queued } : notFound;
33111
33216
  }
33112
- const { events, ...rest } = record2;
33217
+ const { events, ...rest } = record3;
33113
33218
  const view = persistedView(rest, prices);
33114
33219
  view.events = events;
33115
33220
  return { ok: true, value: view };
@@ -34990,11 +35095,11 @@ var init_runLedgerWorker = __esm({
34990
35095
  this.checkIds(runId3, gen);
34991
35096
  return this.writeTurns(runId3, gen, turns, session);
34992
35097
  }
34993
- async step(runId3, gen, record2, turns, session) {
35098
+ async step(runId3, gen, record3, turns, session) {
34994
35099
  this.checkIds(runId3, gen);
34995
35100
  const written = await this.writeTurns(runId3, gen, turns, session);
34996
35101
  if (!written.ok) return written;
34997
- return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 }));
35102
+ return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record3 }));
34998
35103
  }
34999
35104
  checkSessionKey(key) {
35000
35105
  if (!SESSION_KEY_PATTERN.test(key))
@@ -35165,20 +35270,20 @@ var init_runLedgerWorker = __esm({
35165
35270
  this.checkIds(runId3, gen);
35166
35271
  return this.fenceResult(await this.post("/runs/finishing", { storeKey: this.opts.storeKey, runId: runId3, gen }));
35167
35272
  }
35168
- async finish(runId3, gen, record2) {
35273
+ async finish(runId3, gen, record3) {
35169
35274
  this.checkIds(runId3, gen);
35170
- const point = pointOf(record2, this.opts.prices);
35275
+ const point = pointOf(record3, this.opts.prices);
35171
35276
  const r = await this.post("/runs/finish", {
35172
35277
  storeKey: this.opts.storeKey,
35173
35278
  runId: runId3,
35174
35279
  gen,
35175
- record: record2,
35280
+ record: record3,
35176
35281
  ...point !== void 0 ? { point } : {}
35177
35282
  });
35178
35283
  const f = this.fenceResult(r);
35179
35284
  if (!f.ok) return f;
35180
- if (record2.session)
35181
- await this.post("/runs/session/clear-owner", { key: record2.session.key, runId: runId3, gen }).catch(() => {
35285
+ if (record3.session)
35286
+ await this.post("/runs/session/clear-owner", { key: record3.session.key, runId: runId3, gen }).catch(() => {
35182
35287
  });
35183
35288
  return { ok: true, stored: r.data.stored === true };
35184
35289
  }
@@ -35877,6 +35982,19 @@ function runPageLink(id) {
35877
35982
  if (!base) return void 0;
35878
35983
  return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
35879
35984
  }
35985
+ function threadPageLink(threadKey, publicBaseUrl = process.env.PUBLIC_BASE_URL) {
35986
+ if (!publicBaseUrl?.trim()) return void 0;
35987
+ try {
35988
+ const base = new URL(publicBaseUrl.trim());
35989
+ if (!["https:", "http:"].includes(base.protocol) || base.username || base.password) return void 0;
35990
+ base.search = "";
35991
+ base.hash = "";
35992
+ base.pathname = `${base.pathname.replace(/\/+$/, "")}/threads/${encodeURIComponent(threadKey).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
35993
+ return base.href;
35994
+ } catch {
35995
+ return void 0;
35996
+ }
35997
+ }
35880
35998
  function artifactLink(id, key, token2) {
35881
35999
  const page2 = runPageLink(id);
35882
36000
  if (!page2) return void 0;
@@ -36019,7 +36137,7 @@ function cardActivity(e) {
36019
36137
  if (e.type === "tool_call" && e.command !== void 0) return { kind: "command", tool: e.tool, command: e.command };
36020
36138
  return { kind: "line", text: activityLine(e) };
36021
36139
  }
36022
- function quietActivity(activity) {
36140
+ function compactActivity(activity) {
36023
36141
  if (activity?.kind === "command") return { kind: "line", text: `\u2192 ${activity.tool}` };
36024
36142
  return activity;
36025
36143
  }
@@ -36124,7 +36242,9 @@ function cardLines(trace, opts) {
36124
36242
  });
36125
36243
  return { ...shape ? { shape } : {}, ...opts.queued ? { queued: opts.queued } : {} };
36126
36244
  }
36127
- 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;
36128
36248
  if (!io.attach || text.length <= LONG_COMMAND_REPLY_CHARS) return io.reply(text);
36129
36249
  const nl = text.indexOf("\n");
36130
36250
  const lead = nl === -1 ? text : text.slice(0, nl);
@@ -36554,9 +36674,9 @@ function recordPrOf(ctx) {
36554
36674
  }
36555
36675
  function inheritedPr(thread, records, repo) {
36556
36676
  const turn = thread.pr && thread.pr.repo === repo ? { repo, number: thread.pr.number, source: "turn", at: thread.prAt } : void 0;
36557
- const record2 = records?.pr && records.pr.repo === repo ? { repo, number: records.pr.number, source: "record", at: records.pr.at } : void 0;
36558
- if (!turn || !record2) return turn ?? record2;
36559
- return turn.at !== void 0 && record2.at !== void 0 && turn.at > record2.at ? turn : record2;
36677
+ const record3 = records?.pr && records.pr.repo === repo ? { repo, number: records.pr.number, source: "record", at: records.pr.at } : void 0;
36678
+ if (!turn || !record3) return turn ?? record3;
36679
+ return turn.at !== void 0 && record3.at !== void 0 && turn.at > record3.at ? turn : record3;
36560
36680
  }
36561
36681
  async function openPrHeadSha(pr) {
36562
36682
  const head = await prHead(pr).catch(() => void 0);
@@ -37164,7 +37284,7 @@ async function classifyMove(fetchPrCommits, q) {
37164
37284
  async function runReviewPostStep(input) {
37165
37285
  const { agent, repoCtx, verdict, carried, logKey } = input;
37166
37286
  const { reviewHead, observedHead } = input.heads;
37167
- const record2 = (outcome) => {
37287
+ const record3 = (outcome) => {
37168
37288
  if (input.publish && agent.name === "review" && !input.hardStopped) {
37169
37289
  const where = outcome.posted ? void 0 : repoCtx.repo && repoCtx.pr ? `${repoCtx.repo}#${repoCtx.pr}` : void 0;
37170
37290
  input.publish(
@@ -37263,7 +37383,7 @@ ${carriedFooter(carried)}` : rendered;
37263
37383
  console.error(`[review-post] ${logKey} failed for ${where}: ${reason2}`);
37264
37384
  await input.reply(`\u2139\uFE0F Review not posted to ${where}: ${reason2} \u2014 this verdict is Slack-only.`).catch(() => {
37265
37385
  });
37266
- return record2({ posted: false, reason: reason2 });
37386
+ return record3({ posted: false, reason: reason2 });
37267
37387
  }
37268
37388
  console.log(
37269
37389
  `[review-post] ${logKey} \u2192 ${where} (${verdict?.verdict ?? "no verdict"})${carried ? ` carried ${carried.reviewed.slice(0, 7)} \u2192 ${pinned.slice(0, 7)}` : ""}`
@@ -37279,14 +37399,14 @@ ${carriedFooter(carried)}` : rendered;
37279
37399
  await input.reply(moved).catch(() => {
37280
37400
  });
37281
37401
  }
37282
- return record2({
37402
+ return record3({
37283
37403
  posted: true,
37284
37404
  target: { repo: postTarget.repo, number: postTarget.number },
37285
37405
  head: pinned,
37286
37406
  ...verdict !== void 0 ? { verdict: verdict.verdict } : {}
37287
37407
  });
37288
37408
  }
37289
- return record2({ posted: false, reason: skipReason });
37409
+ return record3({ posted: false, reason: skipReason });
37290
37410
  }
37291
37411
  var init_reviewRound = __esm({
37292
37412
  "../../src/core/reviewRound.ts"() {
@@ -37643,11 +37763,11 @@ var init_authorize2 = __esm({
37643
37763
  });
37644
37764
 
37645
37765
  // ../../src/core/runHistoryWriter.ts
37646
- async function putOnce(sink, record2) {
37766
+ async function putOnce(sink, record3) {
37647
37767
  try {
37648
- return await sink.put(record2);
37768
+ return await sink.put(record3);
37649
37769
  } catch (err2) {
37650
- sink.abandoned(record2, describe2(err2));
37770
+ sink.abandoned(record3, describe2(err2));
37651
37771
  throw err2;
37652
37772
  }
37653
37773
  }
@@ -37671,28 +37791,28 @@ function createRunHistoryWriter(opts) {
37671
37791
  opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe2(err2)}`);
37672
37792
  }
37673
37793
  };
37674
- const attemptAll = async (record2, flag2, sink, span) => {
37794
+ const attemptAll = async (record3, flag2, sink, span) => {
37675
37795
  const attempts = RUN_HISTORY_RETRY_DELAYS_MS.length + 1;
37676
37796
  const giveUp = async (why) => {
37677
37797
  failures++;
37678
37798
  try {
37679
- sink.abandoned(record2, why);
37799
+ sink.abandoned(record3, why);
37680
37800
  } catch (err2) {
37681
- opts.warn(`[run-history] abandoned hook failed for ${record2.id}: ${describe2(err2)}`);
37801
+ opts.warn(`[run-history] abandoned hook failed for ${record3.id}: ${describe2(err2)}`);
37682
37802
  }
37683
37803
  if (sink === opts.store || flag2?.superseded) return;
37684
37804
  try {
37685
- await opts.store.put(record2, span ? { span } : void 0);
37686
- if (!flag2) persisted(record2.id);
37805
+ await opts.store.put(record3, span ? { span } : void 0);
37806
+ if (!flag2) persisted(record3.id);
37687
37807
  } catch (err2) {
37688
- opts.warn(`[run-history] ${record2.id} store fallback after give-up failed: ${describe2(err2)}`);
37808
+ opts.warn(`[run-history] ${record3.id} store fallback after give-up failed: ${describe2(err2)}`);
37689
37809
  }
37690
37810
  };
37691
37811
  for (let attempt = 1; ; attempt++) {
37692
37812
  if (flag2?.superseded) return;
37693
37813
  try {
37694
- await sink.put(record2, span ? { span } : void 0);
37695
- if (!flag2) persisted(record2.id);
37814
+ await sink.put(record3, span ? { span } : void 0);
37815
+ if (!flag2) persisted(record3.id);
37696
37816
  return;
37697
37817
  } catch (err2) {
37698
37818
  if (err2 instanceof RouteMissingError) {
@@ -37705,12 +37825,12 @@ function createRunHistoryWriter(opts) {
37705
37825
  return;
37706
37826
  }
37707
37827
  if (err2 instanceof PermanentStoreError) {
37708
- opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe2(err2)}`);
37828
+ opts.warn(`[run-history] ${record3.id} not persisted (permanent, not retried): ${describe2(err2)}`);
37709
37829
  await giveUp(describe2(err2));
37710
37830
  return;
37711
37831
  }
37712
37832
  if (attempt >= attempts) {
37713
- opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe2(err2)}`);
37833
+ opts.warn(`[run-history] ${record3.id} not persisted after ${attempts} attempts: ${describe2(err2)}`);
37714
37834
  await giveUp(`not persisted after ${attempts} attempts: ${describe2(err2)}`);
37715
37835
  return;
37716
37836
  }
@@ -37719,24 +37839,24 @@ function createRunHistoryWriter(opts) {
37719
37839
  }
37720
37840
  };
37721
37841
  return {
37722
- write(record2, writeOpts) {
37842
+ write(record3, writeOpts) {
37723
37843
  let flag2;
37724
37844
  if (writeOpts?.provisional === true) {
37725
- if (finals.has(record2.id)) return;
37726
- flag2 = { id: record2.id, superseded: false };
37845
+ if (finals.has(record3.id)) return;
37846
+ flag2 = { id: record3.id, superseded: false };
37727
37847
  provisionalFlags.add(flag2);
37728
37848
  } else {
37729
- finals.add(record2.id);
37730
- for (const f of provisionalFlags) if (f.id === record2.id) f.superseded = true;
37849
+ finals.add(record3.id);
37850
+ for (const f of provisionalFlags) if (f.id === record3.id) f.superseded = true;
37731
37851
  }
37732
37852
  const sink = writeOpts?.via ?? opts.store;
37733
- const p = attemptAll(record2, flag2, sink, writeOpts?.span).catch((err2) => {
37853
+ const p = attemptAll(record3, flag2, sink, writeOpts?.span).catch((err2) => {
37734
37854
  failures++;
37735
- opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe2(err2)}`);
37855
+ opts.warn(`[run-history] ${record3.id} writer failed unexpectedly: ${describe2(err2)}`);
37736
37856
  try {
37737
- sink.abandoned(record2, `writer failed unexpectedly: ${describe2(err2)}`);
37857
+ sink.abandoned(record3, `writer failed unexpectedly: ${describe2(err2)}`);
37738
37858
  } catch (hookErr) {
37739
- opts.warn(`[run-history] abandoned hook failed for ${record2.id}: ${describe2(hookErr)}`);
37859
+ opts.warn(`[run-history] abandoned hook failed for ${record3.id}: ${describe2(hookErr)}`);
37740
37860
  }
37741
37861
  }).finally(() => {
37742
37862
  if (flag2) provisionalFlags.delete(flag2);
@@ -38099,12 +38219,12 @@ async function closeRestartRow(adopted, restart, why) {
38099
38219
  async function closeResumedRow(adopted, resume, why, status3 = "interrupted", opts) {
38100
38220
  try {
38101
38221
  const finishedAt = (opts?.clock ?? systemClock)();
38102
- const record2 = {
38222
+ const record3 = {
38103
38223
  ...reclaimedRunRecord({ row: resume.row, events: resume.events, status: status3, finishedAt }),
38104
38224
  ...opts?.restarting === true ? { restarting: true, restartUntil: finishedAt + RESTART_CLAIM_GRACE_MS } : {}
38105
38225
  };
38106
- await putOnce(adopted.sink, record2);
38107
- return record2;
38226
+ await putOnce(adopted.sink, record3);
38227
+ return record3;
38108
38228
  } catch (err2) {
38109
38229
  console.warn(
38110
38230
  `[resume] ${resume.row.runId} could not be closed (${why}): ${err2 instanceof Error ? err2.message : String(err2)}`
@@ -38669,6 +38789,79 @@ var init_commandChat = __esm({
38669
38789
  }
38670
38790
  });
38671
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
+
38672
38865
  // ../../src/core/runRegistry/runControl.ts
38673
38866
  var RunControl;
38674
38867
  var init_runControl = __esm({
@@ -39668,6 +39861,7 @@ var init_commandRun = __esm({
39668
39861
  async function answerChatCommand(deps, ctx) {
39669
39862
  const { msg, io, ending, trace } = ctx;
39670
39863
  const root = trace.root;
39864
+ const verbosity2 = deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity);
39671
39865
  if (deps.commands) {
39672
39866
  const chatCmd = parseChatCommand(msg.text, deps.commands);
39673
39867
  if (chatCmd) {
@@ -39687,7 +39881,7 @@ async function answerChatCommand(deps, ctx) {
39687
39881
  confirmations: deps.confirmations
39688
39882
  })
39689
39883
  );
39690
- } : () => root.span("post.reply", () => replyCommandOutput(io, chatCmd, res.text));
39884
+ } : () => root.span("post.reply", () => replyCommandOutput(io, chatCmd, res.text, { verbosity: verbosity2, ok: res.ok }));
39691
39885
  await ending.sealAfterReply(async () => {
39692
39886
  }, replyFn);
39693
39887
  if (res.followUp) postSettledOutcome(res.followUp, io, root);
@@ -39703,6 +39897,7 @@ var init_fastPath = __esm({
39703
39897
  init_commandChat();
39704
39898
  init_reply();
39705
39899
  init_refusal();
39900
+ init_directives();
39706
39901
  init_commandRun();
39707
39902
  init_commandRun();
39708
39903
  }
@@ -39996,79 +40191,6 @@ var init_references = __esm({
39996
40191
  }
39997
40192
  });
39998
40193
 
39999
- // ../../src/directives.ts
40000
- function parseBudgetMinutes(value) {
40001
- if (!/^\d+$/.test(value)) return void 0;
40002
- const minutes = Number(value);
40003
- return minutes >= MIN_BOUNDARY_MINUTES ? minutes : void 0;
40004
- }
40005
- function lastThreadDirectives(history) {
40006
- const out = {};
40007
- for (const h of history) {
40008
- if (h.role !== "user") continue;
40009
- for (const m of h.text.matchAll(DIRECTIVE_RE)) {
40010
- if (m[1] === "model") out.model = m[2];
40011
- else if (m[1] === "effort" && isEffort(m[2])) out.effort = m[2];
40012
- else if (m[1] === "verbosity" && isVerbosity(m[2])) out.verbosity = m[2];
40013
- }
40014
- }
40015
- return out;
40016
- }
40017
- function parseRenewals(value) {
40018
- if (!/^\d+$/.test(value)) return void 0;
40019
- const count = Number(value);
40020
- return count <= GRANT_RENEWALS_MAX ? count : void 0;
40021
- }
40022
- function parseDirectives(input) {
40023
- const out = { text: input };
40024
- let text = input;
40025
- const head = HEAD_AGENT_RE.exec(input);
40026
- if (head !== null && AGENTS[head[1]] !== void 0) {
40027
- out.agent = head[1];
40028
- text = text.replace(head[0], " ");
40029
- }
40030
- for (const m of input.matchAll(DIRECTIVE_RE)) {
40031
- const [token2, key, value] = m;
40032
- if (key === "model") {
40033
- out.model = value;
40034
- } else if (key === "effort") {
40035
- if (!isEffort(value)) continue;
40036
- out.effort = value;
40037
- } else if (key === "budget") {
40038
- const minutes = parseBudgetMinutes(value);
40039
- if (minutes === void 0) continue;
40040
- out.budget = minutes;
40041
- } else if (key === "severity") {
40042
- if (!isAddressSeverity(value)) continue;
40043
- out.severity = value;
40044
- } else if (key === "renewals") {
40045
- const count = parseRenewals(value);
40046
- if (count === void 0) continue;
40047
- out.renewals = count;
40048
- } else {
40049
- if (!isVerbosity(value)) continue;
40050
- out.verbosity = value;
40051
- }
40052
- text = text.replace(token2, " ");
40053
- }
40054
- out.text = text.replace(/\s+/g, " ").trim();
40055
- return out;
40056
- }
40057
- var DIRECTIVE_RE, HEAD_AGENT_RE;
40058
- var init_directives = __esm({
40059
- "../../src/directives.ts"() {
40060
- "use strict";
40061
- init_registry();
40062
- init_validate();
40063
- init_budgets();
40064
- init_effort();
40065
- init_shipPipeline();
40066
- init_verbosity();
40067
- DIRECTIVE_RE = /(?:^|\s)(model|effort|budget|severity|renewals|verbosity)[:=](\S+)/g;
40068
- HEAD_AGENT_RE = /^\s*agent[:=](\S+)/;
40069
- }
40070
- });
40071
-
40072
40194
  // ../../src/execution/githubRepoProbe.ts
40073
40195
  function githubRepoProbe(opts) {
40074
40196
  const fetchImpl = opts.fetch ?? fetch;
@@ -40521,11 +40643,13 @@ async function ownerOf2(runs, unitsOf, threadKey) {
40521
40643
  const units = await unitsOf(instanceId).catch(() => []);
40522
40644
  const unit = units.find((u) => u.threadKey === threadKey && u.ending === void 0);
40523
40645
  if (unit !== void 0) return { kind: "unit", instanceId, unit };
40524
- const ended = units.find(
40646
+ const ended = units.filter(
40525
40647
  (u) => u.threadKey === threadKey && u.ending !== void 0 && u.ending.kind !== "merged" && u.ending.kind !== "already_landed"
40526
40648
  );
40527
40649
  const ship = runs.find((r) => r.instanceId === instanceId && r.finished);
40528
- if (ended !== void 0 && ship !== void 0) return { kind: "pipeline", instanceId, run: ship, unit: ended };
40650
+ if (ended.length === 1 && ship !== void 0) return { kind: "pipeline", instanceId, run: ship, unit: ended[0] };
40651
+ if (ended.length > 1 && ship !== void 0)
40652
+ return { kind: "pipeline_ambiguous", instanceId, run: ship, units: ended };
40529
40653
  }
40530
40654
  const agent = stickyAgentOf(runs);
40531
40655
  if (agent !== void 0) return { kind: "session", agent };
@@ -40736,8 +40860,9 @@ function ownedProjection(p) {
40736
40860
  function ownerNote(owner) {
40737
40861
  const who2 = owner.kind === "live" ? `a live run${owner.runId !== void 0 ? ` (\`${owner.runId}\`)` : ""}` : owner.kind === "unit" ? `the unfinished plan unit ${owner.unit}` : `the ended pipeline for unmerged plan unit ${owner.unit}`;
40738
40862
  const steer = owner.kind === "live" && owner.runId !== void 0 ? ` bind \`steer run ${owner.runId} <words>\` to deliver it,` : "";
40739
- const fold = owner.kind === "pipeline" ? " Any steer decision also folds, so the pipeline's original task is re-issued instead of targeting an ended transcript run." : "";
40740
- return `This thread is owned by ${who2}: the request below is a follow-up for that owner. To act on it,${steer} bind a read command, or ask a question. Any other decision \u2014 a refusal, a preset, a write \u2014 folds the whole message into the owner unchanged and posts no answer.${fold}`;
40863
+ if (owner.kind === "pipeline")
40864
+ return `This thread is owned by ${who2}: the request below is a continuation of that durable task. Read tools may ground the decision, but every action folds into the owner so the current pull request is re-read and the pipeline resumes; an informational command or question is not fulfillment.`;
40865
+ return `This thread is owned by ${who2}: the request below is a follow-up for that owner. To act on it,${steer} bind a read command, or ask a question. Any other decision \u2014 a refusal, a preset, a write \u2014 folds the whole message into the owner unchanged and posts no answer.`;
40741
40866
  }
40742
40867
  function operatorProjection(input) {
40743
40868
  return {
@@ -41122,14 +41247,13 @@ function runnableProposal(proposal, ctx) {
41122
41247
  return (ctx.repositories?.length ?? 0) > 0 || /https?:\/\/github\.com\/[\w.-]+\/[\w.-]+|(?:^|\s)[\w.-]+\/[\w.-]+(?:\s|[:#]|$)/i.test(request2);
41123
41248
  }
41124
41249
  function ownedDecisionRuns(event, owner, commands) {
41125
- if (event.outcome !== "binds") return false;
41250
+ if (owner.kind === "pipeline" || event.outcome !== "binds") return false;
41126
41251
  return (event.binds ?? []).every((bind) => {
41127
41252
  const parsed = commands ? parseChatCommand(bind.line, commands) : null;
41128
41253
  if (parsed?.kind !== "invoke") return false;
41129
41254
  const def = commands.list().find((c) => c.id === parsed.id);
41130
41255
  if (!def) return false;
41131
- if (def.id === "steer.run")
41132
- return owner.kind !== "pipeline" && !(owner.kind === "live" && owner.runId === void 0);
41256
+ if (def.id === "steer.run") return !(owner.kind === "live" && owner.runId === void 0);
41133
41257
  return boundBlastRadius(def, parsed.input) === "read";
41134
41258
  });
41135
41259
  }
@@ -41447,11 +41571,10 @@ function presetRequestOf(line4) {
41447
41571
  async function executeOperatorDecision(deps, ctx) {
41448
41572
  const { event, io, msg } = ctx;
41449
41573
  const surface = io.offer ? "chat" : "typed";
41450
- const verbose = shows(
41451
- deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity),
41452
- "verbose"
41453
- );
41574
+ const verbosity2 = deps.config.verbosityFor(msg.channelId, msg.userId, parseDirectives(msg.text).verbosity);
41575
+ const verbose = shows(verbosity2, "verbose");
41454
41576
  const answered = { kind: "answered" };
41577
+ if (ctx.owner?.kind === "pipeline") return { kind: "fold" };
41455
41578
  if (event.outcome === "question") {
41456
41579
  await io.reply(event.question ?? "");
41457
41580
  await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
@@ -41556,7 +41679,7 @@ async function executeOperatorDecision(deps, ctx) {
41556
41679
  if (verbose) await io.reply(receipt);
41557
41680
  const res = await runChatCommand(deps, msg, io, invocation, ctx.ending, ctx.trace, { operator: executedEvent });
41558
41681
  carried = true;
41559
- 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 });
41560
41683
  }
41561
41684
  if (!carried) await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
41562
41685
  return answered;
@@ -41811,21 +41934,21 @@ function parseSkillMarkdown(raw) {
41811
41934
  if (!fm || typeof fm !== "object" || Array.isArray(fm)) {
41812
41935
  throw new Error("skill frontmatter is not a YAML mapping");
41813
41936
  }
41814
- const record2 = fm;
41815
- const name = record2.name;
41937
+ const record3 = fm;
41938
+ const name = record3.name;
41816
41939
  if (typeof name !== "string" || name.trim() === "") {
41817
41940
  throw new Error("skill frontmatter is missing a non-empty string `name`");
41818
41941
  }
41819
- const description = record2.description;
41942
+ const description = record3.description;
41820
41943
  if (typeof description !== "string" || description.trim() === "") {
41821
41944
  throw new Error(`skill "${name}" frontmatter is missing a non-empty string \`description\``);
41822
41945
  }
41823
- const agents = record2.agents;
41946
+ const agents = record3.agents;
41824
41947
  if (!Array.isArray(agents) || agents.length === 0 || !agents.every((a) => typeof a === "string" && a.trim() !== "")) {
41825
41948
  throw new Error(`skill "${name}" frontmatter needs a non-empty \`agents\` array of strings (e.g. [review])`);
41826
41949
  }
41827
- const source2 = typeof record2.source === "string" ? record2.source.trim() : void 0;
41828
- const upstream = parseUpstream(record2.upstream, name);
41950
+ const source2 = typeof record3.source === "string" ? record3.source.trim() : void 0;
41951
+ const upstream = parseUpstream(record3.upstream, name);
41829
41952
  return {
41830
41953
  name: name.trim(),
41831
41954
  description: description.trim(),
@@ -42235,7 +42358,7 @@ async function openAckCard(deps, ctx) {
42235
42358
  });
42236
42359
  if (route2) shell.note("debug", routeReasonLabel(route2.reason, route2.collapsed));
42237
42360
  const card = coalesceStatus(
42238
- await root.span("dispatch.ack_card", () => io.status(shell.ack())),
42361
+ await root.span("dispatch.ack_card", () => io.status(shell.ack(), { verbosity: resolved.verbosity })),
42239
42362
  deps.statusUpdateMinMs ?? STATUS_UPDATE_MIN_MS
42240
42363
  );
42241
42364
  trace.bindCard({ setupLabel: (label) => shell.setSetupLabel(label) });
@@ -43962,7 +44085,7 @@ function watchedChild(io, on) {
43962
44085
  on.replied(text);
43963
44086
  await io.reply(text);
43964
44087
  },
43965
- status: (initial) => io.status(initial),
44088
+ status: (initial, display) => io.status(initial, display),
43966
44089
  history: () => io.history(),
43967
44090
  runStarted: (started) => {
43968
44091
  io.runStarted?.(started);
@@ -45038,9 +45161,13 @@ function mapStringLeaves(value, fn) {
45038
45161
  }
45039
45162
  return value;
45040
45163
  }
45164
+ function requesterText(text) {
45165
+ return text.trim().replace(/\s+/g, " ").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/[\\`*_{}[\]()#!|~]/g, "\\$&").replace(/@/g, "&#64;");
45166
+ }
45041
45167
  function requestedByLine(by) {
45042
- const steered = by.steeredBy !== void 0 && by.steeredBy.length > 0 ? `; steered by ${by.steeredBy.join(", ")}` : "";
45043
- return `Requested by @${by.login} in ${by.surface}${steered}`;
45168
+ const steered = by.steeredBy !== void 0 && by.steeredBy.length > 0 ? `; steered by ${by.steeredBy.map(requesterText).join(", ")}` : "";
45169
+ const thread = by.threadUrl ? ` \xB7 [Thread](${by.threadUrl})` : "";
45170
+ return `Requested by **${requesterText(by.name) || "Unknown requester"}**${thread}${steered}`;
45044
45171
  }
45045
45172
  function encodeGithubPathSegments(path) {
45046
45173
  return path.split("/").map(encodeURIComponent).join("/");
@@ -45063,6 +45190,7 @@ function renderPrDescriptionMarkdown(desc, ctx) {
45063
45190
  if (!SHA_RE2.test(ctx.headSha))
45064
45191
  throw new Error(`renderPrDescriptionMarkdown: headSha must be a full 40-char lowercase sha, got "${ctx.headSha}"`);
45065
45192
  const out = [];
45193
+ if (ctx.requestedBy) out.push(requestedByLine(ctx.requestedBy), "");
45066
45194
  out.push(desc.tldr, "");
45067
45195
  out.push(`${MAP_LABELS.why} ${desc.why}`, "");
45068
45196
  out.push(MAP_LABELS.whereToLook, "");
@@ -45089,10 +45217,7 @@ function renderPrDescriptionMarkdown(desc, ctx) {
45089
45217
  ...desc.validation.criteria.map((c) => `| ${cell2(c.criterion)} | ${cell2(c.proof)} |`)
45090
45218
  ])
45091
45219
  );
45092
- const agentLines = [
45093
- ...desc.agentNotes ? [desc.agentNotes] : [],
45094
- ...ctx.requestedBy ? [requestedByLine(ctx.requestedBy)] : []
45095
- ];
45220
+ const agentLines = desc.agentNotes ? [desc.agentNotes] : [];
45096
45221
  if (agentLines.length > 0) out.push(...details(FOLD_SUMMARIES.agents, agentLines));
45097
45222
  out.push(GENERATED_FOOTER, "");
45098
45223
  return out.join("\n");
@@ -45100,6 +45225,8 @@ function renderPrDescriptionMarkdown(desc, ctx) {
45100
45225
  function parsePrDescriptionMarkdown(body) {
45101
45226
  const problems = [];
45102
45227
  const lines = body.replace(/\r\n?/g, "\n").split("\n").filter((l) => l.trim() !== GENERATED_FOOTER);
45228
+ if (/^Requested by \*\*(?:\\.|[^*\\])+\*\*(?: · \[Thread\]\([^\s)]+\))?(?:; steered by .*)?$/.test(lines[0] ?? ""))
45229
+ lines.shift();
45103
45230
  const fenced = fenceMask(lines);
45104
45231
  const description = { pointers: [] };
45105
45232
  if (lines.some((l, i) => !fenced[i] && /^##\s+Tour\s*$/i.test(l))) {
@@ -46043,6 +46170,7 @@ async function claimRun(deps, ctx) {
46043
46170
  parentInstanceId: coordinator.parentInstanceId,
46044
46171
  ...unit !== void 0 ? { unit } : {},
46045
46172
  ...coordinator.base !== void 0 ? { base: coordinator.base } : {},
46173
+ ...coordinator.publication !== void 0 ? { publication: coordinator.publication } : {},
46046
46174
  at: clock4()
46047
46175
  });
46048
46176
  }
@@ -46300,12 +46428,12 @@ var init_contract4 = __esm({
46300
46428
  };
46301
46429
  RELAUNCH_CEILING = 2;
46302
46430
  HarnessContainerReplacedError = class extends HarnessInterruptedError {
46303
- constructor(message4, said2, was, now, record2, condition = "word") {
46431
+ constructor(message4, said2, was, now, record3, condition = "word") {
46304
46432
  super(message4, "container replaced under the run", "container_replaced");
46305
46433
  this.said = said2;
46306
46434
  this.was = was;
46307
46435
  this.now = now;
46308
- this.record = record2;
46436
+ this.record = record3;
46309
46437
  this.condition = condition;
46310
46438
  this.name = "HarnessContainerReplacedError";
46311
46439
  if (condition !== "identity" !== (said2 !== void 0))
@@ -46392,11 +46520,11 @@ async function prepareRelaunch(deps, ctx) {
46392
46520
  } else {
46393
46521
  ctx.saveFacts(written);
46394
46522
  }
46395
- const { deadline, ...record2 } = replaced.record;
46523
+ const { deadline, ...record3 } = replaced.record;
46396
46524
  return {
46397
46525
  kind: "relaunch",
46398
46526
  resume: {
46399
- ...record2,
46527
+ ...record3,
46400
46528
  remainingMs: Math.max(0, deadline - ctx.clock()),
46401
46529
  facts: written,
46402
46530
  relaunch: {
@@ -47325,7 +47453,7 @@ function established(facts2) {
47325
47453
  else if (w?.kind === "salvaged")
47326
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.`;
47327
47455
  else if (w?.kind === "left")
47328
- 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.`;
47329
47457
  const description = facts2?.description === "submitted" ? "The PR description was submitted." : facts2?.description === "not_submitted" ? "No PR description was submitted." : "";
47330
47458
  return [tree, description].filter((s) => s !== "").join(" ");
47331
47459
  }
@@ -47559,7 +47687,7 @@ var init_runBearers = __esm({
47559
47687
  * after `record` returned would carry the old hash ahead of the new one.
47560
47688
  * Refuses by name the runs `issue` mints nothing for, and hands `record`
47561
47689
  * nothing then. */
47562
- rotate(runId3, record2) {
47690
+ rotate(runId3, record3) {
47563
47691
  const entry = this.entries.get(runId3);
47564
47692
  if (!entry) return { ok: false, reason: "unknown_run" };
47565
47693
  if (entry.revoked) return { ok: false, reason: "revoked" };
@@ -47567,7 +47695,7 @@ var init_runBearers = __esm({
47567
47695
  const secret2 = randomBytes4(SECRET_BYTES);
47568
47696
  const hash = hashOf(secret2);
47569
47697
  entry.hashes.push(hash);
47570
- record2(hash.toString("hex"));
47698
+ record3(hash.toString("hex"));
47571
47699
  entry.hashes = [hash];
47572
47700
  return { ok: true, token: token(runId3, secret2), expiresAt: entry.grant.expiresAt };
47573
47701
  }
@@ -49370,6 +49498,82 @@ var init_process = __esm({
49370
49498
  }
49371
49499
  });
49372
49500
 
49501
+ // ../../src/core/providerToolSchemas.ts
49502
+ function responsesRejectsPattern(pattern) {
49503
+ return /\(\?(?:[=!]|<[=!])/.test(pattern);
49504
+ }
49505
+ function shapeResponsesSchema(value, tool, degradations) {
49506
+ const schema = record(value);
49507
+ if (!schema) {
49508
+ if (!Array.isArray(value)) return { value, changed: false };
49509
+ let changed2 = false;
49510
+ const items = value.map((item) => {
49511
+ const shaped = shapeResponsesSchema(item, tool, degradations);
49512
+ changed2 ||= shaped.changed;
49513
+ return shaped.value;
49514
+ });
49515
+ return { value: changed2 ? items : value, changed: changed2 };
49516
+ }
49517
+ let changed = false;
49518
+ const out = {};
49519
+ for (const [keyword, item] of Object.entries(schema)) {
49520
+ if (keyword === "pattern" && typeof item === "string" && responsesRejectsPattern(item)) {
49521
+ degradations.push({
49522
+ tool,
49523
+ keyword,
49524
+ why: "the Responses wire does not accept regular-expression lookaround"
49525
+ });
49526
+ changed = true;
49527
+ continue;
49528
+ }
49529
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record(item)) {
49530
+ let mapChanged = false;
49531
+ const mapped = {};
49532
+ for (const [name, child] of Object.entries(record(item))) {
49533
+ const shaped = shapeResponsesSchema(child, tool, degradations);
49534
+ mapChanged ||= shaped.changed;
49535
+ mapped[name] = shaped.value;
49536
+ }
49537
+ out[keyword] = mapChanged ? mapped : item;
49538
+ changed ||= mapChanged;
49539
+ continue;
49540
+ }
49541
+ if (["additionalProperties", "allOf", "anyOf", "if", "items", "not", "oneOf", "then", "else"].includes(keyword) && typeof item === "object" && item !== null) {
49542
+ const shaped = shapeResponsesSchema(item, tool, degradations);
49543
+ out[keyword] = shaped.value;
49544
+ changed ||= shaped.changed;
49545
+ continue;
49546
+ }
49547
+ out[keyword] = item;
49548
+ }
49549
+ return { value: changed ? out : value, changed };
49550
+ }
49551
+ function shapeToolSchemasForWire(shape, body) {
49552
+ if (shape !== "openai-responses" || !Array.isArray(body.tools)) return { body, degradations: [] };
49553
+ const degradations = [];
49554
+ let changed = false;
49555
+ const tools = body.tools.map((item) => {
49556
+ const tool = record(item);
49557
+ if (!tool) return item;
49558
+ const name = typeof tool.name === "string" ? tool.name : "unnamed";
49559
+ const shaped = shapeResponsesSchema(tool.parameters, name, degradations);
49560
+ if (!shaped.changed) return item;
49561
+ changed = true;
49562
+ return { ...tool, parameters: shaped.value };
49563
+ });
49564
+ const unique = degradations.filter(
49565
+ (candidate, index, all) => all.findIndex((other) => other.tool === candidate.tool && other.keyword === candidate.keyword) === index
49566
+ );
49567
+ return { body: changed ? { ...body, tools } : body, degradations: unique };
49568
+ }
49569
+ var record;
49570
+ var init_providerToolSchemas = __esm({
49571
+ "../../src/core/providerToolSchemas.ts"() {
49572
+ "use strict";
49573
+ record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
49574
+ }
49575
+ });
49576
+
49373
49577
  // ../../src/core/harness/piAi.ts
49374
49578
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
49375
49579
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
@@ -49537,6 +49741,7 @@ var init_piAi = __esm({
49537
49741
  "../../src/core/harness/piAi.ts"() {
49538
49742
  "use strict";
49539
49743
  init_provider();
49744
+ init_providerToolSchemas();
49540
49745
  init_secrets2();
49541
49746
  init_clock();
49542
49747
  ANTHROPIC_BASE_URL = "https://api.anthropic.com";
@@ -49624,7 +49829,10 @@ var init_piAi = __esm({
49624
49829
  ...this.fetchImpl ? { fetch: this.fetchImpl } : {}
49625
49830
  };
49626
49831
  try {
49627
- const message4 = await this.api$().stream(model, toPiContext(req, model, this.clock), options).result();
49832
+ const context = toPiContext(req, model, this.clock);
49833
+ const wire = this.api === "openai-completions" ? "openai-chat" : this.api;
49834
+ const shaped = shapeToolSchemasForWire(wire, context);
49835
+ const message4 = await this.api$().stream(model, shaped.body, options).result();
49628
49836
  return fromPiMessage(message4, this.name, req.tools);
49629
49837
  } catch (err2) {
49630
49838
  const failure2 = providerFailureOf(err2);
@@ -49778,10 +49986,28 @@ function judgeBashTimeout(timeout, ctx) {
49778
49986
  return refused4(commandPastLoopEndRefusal(Math.round(timeout), Math.max(0, Math.floor(leftMs / 1e3))));
49779
49987
  }
49780
49988
  function judgePush(tail, ctx) {
49781
- const [remote, refspec] = pushArguments(tail);
49989
+ const [remote, refspec, ...additionalRefspecs] = pushArguments(tail);
49782
49990
  if (remote !== void 0 && remote !== "origin") {
49783
49991
  return refused4(`repo:use \u2014 push to remote \`${remote}\`, not the run's repository (origin)`);
49784
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
+ }
49785
50011
  if (refspec === void 0) return allowed;
49786
50012
  const destination = refspec.replace(/^\+/, "").split(":").pop() ?? refspec;
49787
50013
  if (ctx.branch !== void 0) {
@@ -49901,7 +50127,7 @@ function relayedToolDefinitions(harness) {
49901
50127
  }
49902
50128
  function authorizeToolCall(harness, ask) {
49903
50129
  harness.gateSaw(ask.toolCallId);
49904
- const blocked = harness.toolsBlocked();
50130
+ const blocked2 = harness.toolsBlocked();
49905
50131
  const refuse3 = (reason2) => {
49906
50132
  harness.emit({
49907
50133
  type: "run_note",
@@ -49910,7 +50136,7 @@ function authorizeToolCall(harness, ask) {
49910
50136
  });
49911
50137
  return { allow: false, reason: reason2 };
49912
50138
  };
49913
- if (blocked !== void 0) return refuse3(blocked);
50139
+ if (blocked2 !== void 0) return refuse3(blocked2);
49914
50140
  if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
49915
50141
  const verdict = judgeToolCall(ask.tool, ask.input, harness.rules);
49916
50142
  if (verdict.verdict === "allowed") return { allow: true };
@@ -51263,8 +51489,8 @@ ${stagedLine}` : prompt;
51263
51489
  return;
51264
51490
  }
51265
51491
  const reissued = inputs.filter((item) => item.userId === PLANE_ACTOR_ID && isReissueSteerText(item.text));
51266
- const waiting2 = reissued.length === 0 ? inputs : inputs.filter((item) => !reissued.includes(item));
51267
- if (waiting2.length > 0) run2.inbox?.requeue(waiting2);
51492
+ const waiting3 = reissued.length === 0 ? inputs : inputs.filter((item) => !reissued.includes(item));
51493
+ if (waiting3.length > 0) run2.inbox?.requeue(waiting3);
51268
51494
  for (const item of reissued) {
51269
51495
  emit({
51270
51496
  type: "input",
@@ -51575,13 +51801,13 @@ var init_harness = __esm({
51575
51801
  }
51576
51802
  };
51577
51803
  PiContainerReplacedError = class extends HarnessContainerReplacedError {
51578
- constructor(said2, was, now, record2, condition = "word") {
51804
+ constructor(said2, was, now, record3, condition = "word") {
51579
51805
  super(
51580
51806
  condition === "transport" ? `the container running pi stopped answering (${was ?? "unknown"}; ${replacedBecause(condition, said2)})` : `the container running pi was replaced (${was ?? "unknown"} \u2192 ${now ?? "unknown"}; ${replacedBecause(condition, said2)})`,
51581
51807
  said2,
51582
51808
  was,
51583
51809
  now,
51584
- record2,
51810
+ record3,
51585
51811
  condition
51586
51812
  );
51587
51813
  this.name = "PiContainerReplacedError";
@@ -51790,16 +52016,62 @@ var init_resume = __esm({
51790
52016
  }
51791
52017
  });
51792
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
+
51793
52062
  // ../../src/execution/githubComments.ts
51794
52063
  async function postReviewComment(target, body) {
52064
+ const codePoints = [...body].length;
52065
+ if (codePoints > MAX_REVIEW_POST_CODE_POINTS) {
52066
+ throw new Error(
52067
+ `PR review body is ${codePoints} Unicode code points, over the ${MAX_REVIEW_POST_CODE_POINTS} limit; refusing to clip the structured verdict`
52068
+ );
52069
+ }
51795
52070
  const token2 = await resolveGithubToken();
51796
52071
  if (!token2) {
51797
52072
  throw new Error("no GitHub credential available to post the PR review comment");
51798
52073
  }
51799
- const clipped = body.length > MAX_COMMENT_CHARS ? `${body.slice(0, MAX_COMMENT_CHARS)}
51800
-
51801
- _(review truncated to fit GitHub's comment size limit)_` : body;
51802
- const payload2 = { event: "COMMENT", body: clipped };
52074
+ const payload2 = { event: "COMMENT", body };
51803
52075
  if (target.commitId) payload2.commit_id = target.commitId;
51804
52076
  const res = await fetch(`https://api.github.com/repos/${target.repo}/pulls/${target.number}/reviews`, {
51805
52077
  method: "POST",
@@ -51817,13 +52089,12 @@ _(review truncated to fit GitHub's comment size limit)_` : body;
51817
52089
  throw new Error(`PR comment post failed: HTTP ${res.status} ${redactAndCap(text, 300)}`);
51818
52090
  }
51819
52091
  }
51820
- var MAX_COMMENT_CHARS;
51821
52092
  var init_githubComments = __esm({
51822
52093
  "../../src/execution/githubComments.ts"() {
51823
52094
  "use strict";
51824
52095
  init_githubApp();
51825
52096
  init_redact();
51826
- MAX_COMMENT_CHARS = 65e3;
52097
+ init_reviewVerdict();
51827
52098
  }
51828
52099
  });
51829
52100
 
@@ -51875,13 +52146,13 @@ function parsedPrDescriptionArtifact(facts2, ctx) {
51875
52146
  async function findSubmittedPrDescription(store, key, limit = SUBMITTED_LOOKUP_LIMIT) {
51876
52147
  const rows = await store.list({ limit, visibleTo: { kind: "repos-in", repos: [key.repo] } });
51877
52148
  for (const row2 of rows) {
51878
- const record2 = await store.get(row2.id);
51879
- if (!record2) continue;
51880
- for (let i = record2.events.length - 1; i >= 0; i--) {
51881
- const ev = record2.events[i];
52149
+ const record3 = await store.get(row2.id);
52150
+ if (!record3) continue;
52151
+ for (let i = record3.events.length - 1; i >= 0; i--) {
52152
+ const ev = record3.events[i];
51882
52153
  if (ev.type === "review_artifact" && ev.artifact === "pr_description" && ev.origin === "submitted" && ev.repo === key.repo && ev.pr === key.pr && ev.headSha === key.headSha) {
51883
52154
  const { type: _type, seq: _seq, at: _at, ...artifact } = ev;
51884
- return { artifact: { ...artifact, fromRunId: artifact.fromRunId ?? record2.id }, runId: record2.id };
52155
+ return { artifact: { ...artifact, fromRunId: artifact.fromRunId ?? record3.id }, runId: record3.id };
51885
52156
  }
51886
52157
  }
51887
52158
  }
@@ -52031,7 +52302,26 @@ async function salvageBudgetPush(executor, opts, span) {
52031
52302
  }
52032
52303
  const unpushed = parseCountOutput(await run2("git rev-list --count HEAD --not --remotes")) ?? 0;
52033
52304
  if (!dirty && !endingCheckpoint && unpushed === 0) return { pushed: false, summary: words.nothing };
52034
- 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
+ }
52035
52325
  const head = parseRevParseOutput(await probe("git rev-parse HEAD"));
52036
52326
  return {
52037
52327
  pushed: true,
@@ -52144,7 +52434,7 @@ async function runCodingPrPostStep(input) {
52144
52434
  });
52145
52435
  }
52146
52436
  const ownPr = target.ownPr;
52147
- const requestedBy = input.identity?.requestedBy;
52437
+ const requestedBy = input.requestedBy;
52148
52438
  const onOwnPrBranch = ownPr?.headBranch !== void 0 && branch2 === ownPr.headBranch;
52149
52439
  const pushedPastOwnPr = ownPr !== void 0 && onOwnPrBranch && pushed && headSha !== void 0 && !sameCommit(headSha, ownPr.headSha) ? headSha : void 0;
52150
52440
  if (prDescription && ownPr !== void 0 && ownPr.state !== "open" && pushedPastOwnPr !== void 0 && branch2 !== void 0) {
@@ -52355,16 +52645,17 @@ async function runCodingPrPostStep(input) {
52355
52645
  );
52356
52646
  }
52357
52647
  }
52358
- if (requestedBy !== void 0) {
52359
- const assignable = await input.identity.isAssignable(repo, requestedBy.login).catch(() => void 0);
52648
+ const requestedLogin = input.identity.requestedLogin;
52649
+ if (requestedLogin !== void 0) {
52650
+ const assignable = await input.identity.isAssignable(repo, requestedLogin).catch(() => void 0);
52360
52651
  if (assignable === true)
52361
- await input.identity.addAssignee(repo, opened.number, requestedBy.login).catch(
52652
+ await input.identity.addAssignee(repo, opened.number, requestedLogin).catch(
52362
52653
  (err2) => console.error(
52363
- `[pr-post] ${logKey} assignee add failed for ${requestedBy.login} on ${repo}#${opened.number}: ${err2 instanceof Error ? err2.message : String(err2)}`
52654
+ `[pr-post] ${logKey} assignee add failed for ${requestedLogin} on ${repo}#${opened.number}: ${err2 instanceof Error ? err2.message : String(err2)}`
52364
52655
  )
52365
52656
  );
52366
52657
  else if (assignable === false)
52367
- console.log(`[identity] ${logKey} ${requestedBy.login} is not assignable on ${repo}; skipped`);
52658
+ console.log(`[identity] ${logKey} ${requestedLogin} is not assignable on ${repo}; skipped`);
52368
52659
  }
52369
52660
  }
52370
52661
  console.log(
@@ -52757,6 +53048,12 @@ async function runLoop(deps, ctx) {
52757
53048
  return void 0;
52758
53049
  }
52759
53050
  })();
53051
+ let existingPrPublication;
53052
+ let existingPrPublicationFence;
53053
+ const blockExistingPrPublication = (reason2) => {
53054
+ existingPrPublication = { blocked: reason2 };
53055
+ if (existingPrPublicationFence !== void 0) existingPrPublicationFence.authority = existingPrPublication;
53056
+ };
52760
53057
  const identitySeam = deps.identityRewrite;
52761
53058
  const startStateRead = identitySeam !== void 0 && isCodingPrRun && repoCtx.repo !== void 0 ? (async () => {
52762
53059
  const repo = repoCtx.repo;
@@ -52793,14 +53090,14 @@ async function runLoop(deps, ctx) {
52793
53090
  );
52794
53091
  return pace ? ` \xB7 ${pace}` : "";
52795
53092
  };
53093
+ const showsActivity = shows(resolved.verbosity, "verbose");
52796
53094
  const chatty = shows(resolved.verbosity, "debug");
52797
53095
  const showBookkeeping = shows(resolved.verbosity, "verbose");
52798
53096
  const currentFrame = () => shell.live({
52799
- // the pace mark rides every level (the stall signal is a fact, not chatter); the quiet suffix is debug's
52800
- suffix: chatty ? `${quietSuffix(clock4() - lastActivityAt, inFlightTool)}${cardPace()}` : cardPace(),
53097
+ suffix: showsActivity ? chatty ? `${quietSuffix(clock4() - lastActivityAt, inFlightTool)}${cardPace()}` : cardPace() : "",
52801
53098
  notice: shutdownNotice(),
52802
53099
  detail: [checklist],
52803
- activity: chatty ? lastActivity : quietActivity(lastActivity)
53100
+ activity: showsActivity ? chatty ? lastActivity : compactActivity(lastActivity) : void 0
52804
53101
  });
52805
53102
  const checklistAsLeft = () => checklist;
52806
53103
  const checklistCheckedOff = () => checklist?.replace(/^(\s*)[○✱](?=\s)/gm, "$1\u2713");
@@ -53035,8 +53332,18 @@ async function runLoop(deps, ctx) {
53035
53332
  });
53036
53333
  const salvaged = "skipped" in target ? { pushed: false, summary: target.skipped } : await root.span(
53037
53334
  cue === "budget" ? "run.budget_salvage" : "run.work_salvage",
53038
- (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
+ )
53039
53344
  );
53345
+ if ("publicationBlocked" in salvaged && salvaged.publicationBlocked !== void 0)
53346
+ blockExistingPrPublication(salvaged.publicationBlocked);
53040
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);
53041
53348
  if (cleanCompletion) return;
53042
53349
  workSalvageAttempted = true;
@@ -53051,7 +53358,10 @@ async function runLoop(deps, ctx) {
53051
53358
  salvagedTo = { branch: target.branch, head: salvaged.head };
53052
53359
  } else if (salvaged.pushed && !("skipped" in target)) salvagedTo = { branch: target.branch };
53053
53360
  if (salvaged.pushed) await observeWorkspaceNow();
53054
- 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))
53055
53365
  shell.note("quiet", salvaged.summary);
53056
53366
  };
53057
53367
  let relaunchEndedRun = false;
@@ -53077,7 +53387,9 @@ async function runLoop(deps, ctx) {
53077
53387
  return true;
53078
53388
  };
53079
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);
53080
53391
  const releaseWorkspace = (span) => {
53392
+ if (keepBlockedPublicationWorkspace()) return Promise.resolve();
53081
53393
  const events2 = recordEvents();
53082
53394
  const pushed = pushedBranchesOf(events2);
53083
53395
  return round2.release({
@@ -53175,9 +53487,9 @@ async function runLoop(deps, ctx) {
53175
53487
  throw new Error(
53176
53488
  `the ${agent.name} preset's run left ${facts2.harness} harness facts on its row, but this process has no harness roster to end that process with`
53177
53489
  );
53178
- const judge4 = harnessOf(deps.harness);
53490
+ const judge5 = harnessOf(deps.harness);
53179
53491
  const container = deps.harness.containerFor?.(executor, profile.machine) ?? harnessContainerFor(executor, profile.machine);
53180
- const found = await judge4.find(facts2, container).catch((err2) => {
53492
+ const found = await judge5.find(facts2, container).catch((err2) => {
53181
53493
  if (!isContainerGone(err2)) throw err2;
53182
53494
  return "gone";
53183
53495
  });
@@ -53202,11 +53514,11 @@ async function runLoop(deps, ctx) {
53202
53514
  onEvent({
53203
53515
  type: "run_note",
53204
53516
  kind: "resumed",
53205
- summary: `the run's row carries ${facts2.harness} harness facts (pid ${facts2.pid}) that the ${judge4.name} harness does not own: that process was neither judged nor ended here`
53517
+ summary: `the run's row carries ${facts2.harness} harness facts (pid ${facts2.pid}) that the ${judge5.name} harness does not own: that process was neither judged nor ended here`
53206
53518
  });
53207
53519
  break;
53208
53520
  default:
53209
- await judge4.end(facts2, container);
53521
+ await judge5.end(facts2, container);
53210
53522
  }
53211
53523
  } else noteUnknownWord("the run finished on the answer it already had");
53212
53524
  windDownEnding = windDownEndingUnder(finish2.answer, loopEnding);
@@ -53230,7 +53542,38 @@ async function runLoop(deps, ctx) {
53230
53542
  if (!providerCfg)
53231
53543
  throw new Error(`the ${harness.name} harness found no provider named ${providerName} in the config`);
53232
53544
  const prBase = repoCtx.baseRef ?? await coordinatorBase;
53233
- 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;
53234
53577
  const protectedBranches = [
53235
53578
  ...new Set(
53236
53579
  (prBase !== void 0 ? [prBase] : [binding?.ref, repoCtx.ref]).filter(
@@ -53290,8 +53633,18 @@ async function runLoop(deps, ctx) {
53290
53633
  const branch2 = ownBranch;
53291
53634
  const salvaged = await root.span(
53292
53635
  "run.compaction_salvage",
53293
- (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
+ )
53294
53645
  );
53646
+ if (salvaged.publicationBlocked !== void 0)
53647
+ blockExistingPrPublication(salvaged.publicationBlocked);
53295
53648
  onEvent({
53296
53649
  type: "run_note",
53297
53650
  kind: "compaction_salvage",
@@ -53304,7 +53657,8 @@ async function runLoop(deps, ctx) {
53304
53657
  rules: {
53305
53658
  checkout: binding?.workspace ?? "/workspace",
53306
53659
  ...ownBranch !== void 0 ? { branch: ownBranch } : {},
53307
- protectedBranches
53660
+ protectedBranches,
53661
+ ...existingPrPublicationFence !== void 0 ? { publication: existingPrPublicationFence } : {}
53308
53662
  },
53309
53663
  ...round2.selection.backend ? { backend: round2.selection.backend } : {},
53310
53664
  span: root,
@@ -53520,6 +53874,12 @@ async function runLoop(deps, ctx) {
53520
53874
  if (descriptionTurnRan) await preserveCodingChildWork("completion");
53521
53875
  await endHarness();
53522
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;
53523
53883
  const endingFacts = () => {
53524
53884
  if (profile.machine === "none") return { workspace: { kind: "none" } };
53525
53885
  if (!isCodingPrRun || tailSkipped()) return { workspace: { kind: "unread" } };
@@ -53538,11 +53898,16 @@ async function runLoop(deps, ctx) {
53538
53898
  kind: "left",
53539
53899
  uncommitted: observedUncommitted,
53540
53900
  unpushed: observedUnpushed,
53541
- 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"
53542
53902
  };
53543
53903
  return { workspace, description: prDescription !== void 0 ? "submitted" : "not_submitted" };
53544
53904
  };
53545
- 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) {
53546
53911
  events.publish({
53547
53912
  type: "run_note",
53548
53913
  kind: "work_left_behind",
@@ -53558,8 +53923,10 @@ async function runLoop(deps, ctx) {
53558
53923
  at: clock4()
53559
53924
  });
53560
53925
  }
53561
- if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted) {
53562
- const requestedLogin = identitySeam !== void 0 ? await identitySeam.requestedLogin(msg.userId).catch(() => void 0) : void 0;
53926
+ if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted && !(existingPrPublication !== void 0 && "blocked" in existingPrPublication)) {
53927
+ const instance = coordinator && deps.coordinatorInstances ? await deps.coordinatorInstances.get(coordinator.parentInstanceId).catch(() => void 0) : void 0;
53928
+ const requester = instance?.userId === msg.userId ? instance : msg;
53929
+ const requestedLogin = identitySeam !== void 0 ? await identitySeam.requestedLogin(requester.userId).catch(() => void 0) : void 0;
53563
53930
  prNote = await root.span(
53564
53931
  "run.pr_post_step",
53565
53932
  () => runCodingPrPostStep({
@@ -53572,6 +53939,10 @@ async function runLoop(deps, ctx) {
53572
53939
  remoteRepo: observedRemoteRepo
53573
53940
  },
53574
53941
  description: prDescription,
53942
+ requestedBy: {
53943
+ name: requester.userName?.trim() || requestedLogin || requester.userId,
53944
+ threadUrl: threadPageLink(requester.threadKey)
53945
+ },
53575
53946
  target: prTarget,
53576
53947
  openPullRequest: deps.openPullRequest ?? openPullRequest,
53577
53948
  findOpenPr: deps.findOpenPrByHead ?? findOpenPrByHead,
@@ -53584,7 +53955,7 @@ async function runLoop(deps, ctx) {
53584
53955
  pullRequestHead: identitySeam.pullRequestHead,
53585
53956
  isAssignable: identitySeam.isAssignable,
53586
53957
  addAssignee: identitySeam.addAssignee,
53587
- ...requestedLogin !== void 0 ? { requestedBy: { login: requestedLogin, surface: msg.channelId } } : {}
53958
+ ...requestedLogin !== void 0 ? { requestedLogin } : {}
53588
53959
  }
53589
53960
  } : {},
53590
53961
  descriptionTurnRan,
@@ -53823,6 +54194,7 @@ var init_runLoop = __esm({
53823
54194
  "use strict";
53824
54195
  init_actor();
53825
54196
  init_predicate();
54197
+ init_contract2();
53826
54198
  init_decisionRecordReservation();
53827
54199
  init_profile();
53828
54200
  init_provider();
@@ -53838,6 +54210,7 @@ var init_runLoop = __esm({
53838
54210
  init_resume();
53839
54211
  init_githubPulls();
53840
54212
  init_repoContext();
54213
+ init_existingPrPublication();
53841
54214
  init_prDescription();
53842
54215
  init_handoff();
53843
54216
  init_reviewVerdict();
@@ -53992,7 +54365,7 @@ async function shipPreflight(input) {
53992
54365
  `\u{1F6AB} ${where}'s head branch lives on a fork, not on \`${repo}\` \u2014 ship cannot drive it.`
53993
54366
  );
53994
54367
  }
53995
- const branch2 = facts2.headRef ?? repoCtx.ref;
54368
+ const branch2 = facts2.headRef;
53996
54369
  if (!branch2) {
53997
54370
  return refuse2(
53998
54371
  "ship_preflight_head_unknown",
@@ -54001,10 +54374,34 @@ async function shipPreflight(input) {
54001
54374
  `\u{1F6AB} Could not determine ${where}'s head branch, so ship cannot bind the thread's worktree to it \u2014 refusing fail-closed.`
54002
54375
  );
54003
54376
  }
54004
- 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;
54005
54395
  const ownBaseExists = ownBase !== void 0 && input.refExists ? await input.refExists(repo, ownBase).catch(() => void 0) : void 0;
54006
- const baseFallback = ownBase !== void 0 && ownBaseExists === false ? { requested: ownBase } : void 0;
54007
- 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);
54008
54405
  const autoMerge = facts2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: facts2.autoMergeEnabled } : {};
54009
54406
  if (task) {
54010
54407
  return {
@@ -54013,8 +54410,7 @@ async function shipPreflight(input) {
54013
54410
  repo,
54014
54411
  branch: branch2,
54015
54412
  base,
54016
- adopt: { pr: repoCtx.pr, ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {} },
54017
- ...baseFallback !== void 0 ? { baseFallback } : {},
54413
+ adopt: { pr: repoCtx.pr, headSha, ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {} },
54018
54414
  ...autoMerge
54019
54415
  }
54020
54416
  };
@@ -54027,10 +54423,9 @@ async function shipPreflight(input) {
54027
54423
  base,
54028
54424
  resume: {
54029
54425
  pr: repoCtx.pr,
54030
- headSha: facts2.headSha ?? repoCtx.headSha,
54426
+ headSha,
54031
54427
  ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {}
54032
54428
  },
54033
- ...baseFallback !== void 0 ? { baseFallback } : {},
54034
54429
  ...autoMerge
54035
54430
  }
54036
54431
  };
@@ -54221,18 +54616,30 @@ async function rowsFor(deps, p, selected, instanceId, carried) {
54221
54616
  p.graph.units.filter((u) => selected.includes(u.id)).map(async (u) => {
54222
54617
  const previous = carried?.get(u.id);
54223
54618
  const taskKey = p.recordTasks?.[u.id];
54224
- const record2 = taskKey !== void 0 ? await reserve(p.identity.repo, taskKey, previous?.record) : previous?.record;
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;
54225
54631
  return {
54226
54632
  instanceId,
54227
54633
  unit: u.id,
54228
54634
  slug: u.slug,
54229
54635
  title: u.title,
54230
- branch: p.entryBranch ?? u.branch,
54636
+ branch: branch2,
54231
54637
  dependsOn: u.dependsOn,
54232
54638
  rounds: [],
54233
54639
  ...p.resume !== void 0 ? { resume: p.resume } : {},
54640
+ ...publication !== void 0 ? { publication } : {},
54234
54641
  ...previous?.lastPush !== void 0 ? { lastPush: previous.lastPush } : {},
54235
- ...record2 !== void 0 ? { record: record2 } : {}
54642
+ ...record3 !== void 0 ? { record: record3 } : {}
54236
54643
  };
54237
54644
  })
54238
54645
  );
@@ -54648,7 +55055,11 @@ async function runShipBranch(deps, msg, io, ctx) {
54648
55055
  return { hostedLive: false };
54649
55056
  }
54650
55057
  const entry = pre.entry;
54651
- const caps = { ...resolveShipCaps(deps.config.config.ship), maxMinutes: profile.minutes };
55058
+ const configuredCaps = resolveShipCaps(deps.config.config.ship);
55059
+ const caps = {
55060
+ maxRounds: Math.min(configuredCaps.maxRounds, ctx.reissueCaps?.maxRounds ?? configuredCaps.maxRounds),
55061
+ maxMinutes: Math.min(profile.minutes, ctx.reissueCaps?.maxMinutes ?? profile.minutes)
55062
+ };
54652
55063
  const held2 = fit(caps);
54653
55064
  if (!held2.ok) {
54654
55065
  const reason2 = `budget ${caps.maxMinutes} min cannot hold the ship loop (${caps.maxRounds} review rounds need ${held2.need} min)`;
@@ -54792,6 +55203,7 @@ async function runShipBranch(deps, msg, io, ctx) {
54792
55203
  ...msg.authenticatedAs !== void 0 ? { authenticatedAs: msg.authenticatedAs } : {},
54793
55204
  ...msg.postedBy !== void 0 ? { postedBy: msg.postedBy } : {},
54794
55205
  ...entry.resume !== void 0 ? { pr: entry.resume.pr } : {},
55206
+ verbosity: ctx.verbosity,
54795
55207
  profile
54796
55208
  },
54797
55209
  card: card.handle ?? null,
@@ -55135,7 +55547,13 @@ function carriedCoordinatorTag(row2, events) {
55135
55547
  if (typeof parentInstanceId !== "string" || typeof idempotencyKey !== "string") return void 0;
55136
55548
  const tag = events.find((e) => e.type === "coordinator_tag");
55137
55549
  const base = tag?.type === "coordinator_tag" ? tag.base : void 0;
55138
- 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
+ };
55139
55557
  }
55140
55558
  function lostWorkspaceNote(why, restarts) {
55141
55559
  const lost = `resumed after a restart: the run's workspace could not be re-attached (${why})`;
@@ -55359,41 +55777,41 @@ function prName(pr) {
55359
55777
  const m = GITHUB_PULL_URL.exec(pr.url);
55360
55778
  return m ? `${m[1]}#${pr.number} (${pr.url})` : `pull request ${pr.number} at ${pr.url}`;
55361
55779
  }
55362
- function runArtifacts(record2) {
55780
+ function runArtifacts(record3) {
55363
55781
  const lines = [];
55364
- if (record2.pr) {
55365
- const head = record2.pr.head ? `, head branch ${record2.pr.head}` : "";
55366
- lines.push(`- pull request opened or edited: ${prName(record2.pr)}${head}`);
55782
+ if (record3.pr) {
55783
+ const head = record3.pr.head ? `, head branch ${record3.pr.head}` : "";
55784
+ lines.push(`- pull request opened or edited: ${prName(record3.pr)}${head}`);
55367
55785
  }
55368
- if (record2.verdict) {
55369
- lines.push(`- verdict: ${record2.verdict.verdict} \u2014 ${record2.verdict.summary}`);
55370
- const findings = record2.verdict.findings ?? [];
55786
+ if (record3.verdict) {
55787
+ lines.push(`- verdict: ${record3.verdict.verdict} \u2014 ${record3.verdict.summary}`);
55788
+ const findings = record3.verdict.findings ?? [];
55371
55789
  lines.push(findings.length ? `- findings:
55372
55790
  ${indent(findings.map(formatFinding))}` : "- findings: none recorded");
55373
55791
  }
55374
- if (record2.dispositions) {
55792
+ if (record3.dispositions) {
55375
55793
  lines.push(
55376
- record2.dispositions.length ? `- dispositions:
55377
- ${indent(record2.dispositions.map(formatDisposition))}` : "- dispositions: none recorded"
55794
+ record3.dispositions.length ? `- dispositions:
55795
+ ${indent(record3.dispositions.map(formatDisposition))}` : "- dispositions: none recorded"
55378
55796
  );
55379
55797
  }
55380
- if (record2.handoff) {
55381
- const entries = handoffLines(record2.handoff);
55798
+ if (record3.handoff) {
55799
+ const entries = handoffLines(record3.handoff);
55382
55800
  lines.push(
55383
55801
  entries.length ? `- handoff:
55384
55802
  ${indent(entries)}` : "- handoff: nothing to hand off (three empty lists)"
55385
55803
  );
55386
55804
  }
55387
- const tldr = record2.pr ? tldrOf(record2.events) : void 0;
55805
+ const tldr = record3.pr ? tldrOf(record3.events) : void 0;
55388
55806
  if (tldr !== void 0) lines.push(`- description TL;DR: ${tldr}`);
55389
- if (record2.reviewPost) {
55390
- const post = record2.reviewPost;
55807
+ if (record3.reviewPost) {
55808
+ const post = record3.reviewPost;
55391
55809
  lines.push(
55392
55810
  post.posted ? `- review post: posted to ${post.target.repo}#${post.target.number} at ${post.head}${post.verdict ? ` (${post.verdict})` : ""}` : `- review post: not posted \u2014 ${post.reason}`
55393
55811
  );
55394
55812
  }
55395
55813
  if (lines.length === 0) return void 0;
55396
- return [`### run ${record2.id} (${record2.agent ?? "agent unknown"})`, ...lines].join("\n");
55814
+ return [`### run ${record3.id} (${record3.agent ?? "agent unknown"})`, ...lines].join("\n");
55397
55815
  }
55398
55816
  function threadArtifactsBlock(records, opts) {
55399
55817
  const maxRuns = opts.maxRuns ?? THREAD_ARTIFACTS_MAX_RUNS;
@@ -55654,6 +56072,23 @@ async function answerUnitOwnedThread(deps, ctx) {
55654
56072
  function activeRunCount() {
55655
56073
  return activeRuns;
55656
56074
  }
56075
+ function continuationBudgetOf(instance, unit) {
56076
+ if (unit.ending?.kind === "wall_clock_cap" || unit.ending?.kind === "review_pending")
56077
+ return { kind: "exhausted", budget: "wall-clock" };
56078
+ if (unit.ending?.kind === "round_cap") return { kind: "exhausted", budget: "review-round" };
56079
+ const caps = instance.caps;
56080
+ if (caps === void 0 || unit.startedAt === void 0 || unit.ending === void 0 || caps.maxMinutes <= 0 || caps.maxRounds <= 0 || unit.ending.at < unit.startedAt)
56081
+ return { kind: "unavailable" };
56082
+ const remainingMinutes = caps.maxMinutes - (unit.ending.at - unit.startedAt) / MINUTE_MS;
56083
+ if (remainingMinutes <= 0) return { kind: "exhausted", budget: "wall-clock" };
56084
+ const reviewRounds = unit.rounds.reduce(
56085
+ (highest, round2) => round2.agent === "review" ? Math.max(highest, round2.index) : highest,
56086
+ 0
56087
+ );
56088
+ const remainingRounds = caps.maxRounds - reviewRounds;
56089
+ if (remainingRounds <= 0) return { kind: "exhausted", budget: "review-round" };
56090
+ return { kind: "remaining", caps: { maxRounds: remainingRounds, maxMinutes: remainingMinutes } };
56091
+ }
55657
56092
  async function dispatch(deps, msg, io, opts = {}) {
55658
56093
  const ended = { status: "completed" };
55659
56094
  const resume = opts.resume;
@@ -55748,22 +56183,46 @@ async function dispatch(deps, msg, io, opts = {}) {
55748
56183
  try {
55749
56184
  const configuredOperator = !resume && !restart && !opts.parent && !opts.coordinator ? operatorModeOf(deps.config.config) : "off";
55750
56185
  const typedDecision = parseDirectives(msg.text).agent !== void 0 || deps.commands !== void 0 && parseChatCommand(msg.text, deps.commands) !== null;
55751
- const operatorMode = configuredOperator === "on" && typedDecision ? "off" : configuredOperator;
56186
+ let operatorMode = configuredOperator === "on" && typedDecision ? "off" : configuredOperator;
55752
56187
  let operatorEvent;
55753
56188
  let operatorPreset;
55754
56189
  let operatorRequest;
55755
56190
  let operatorModel;
55756
56191
  let operatorRepo;
55757
56192
  let reissuePlanId;
56193
+ let reissueCaps;
55758
56194
  let operatorThread;
55759
56195
  let pageOwner;
55760
56196
  if (operatorMode !== "off") {
55761
56197
  const runsService2 = deps.runs ?? createRunsService({ registry, store: deps.runStore });
55762
56198
  operatorThread = opts.thread ?? await readThread(runsService2, msg.threadKey);
56199
+ if (operatorThread !== void 0 && deps.coordinatorInstances !== void 0) {
56200
+ const unitReads = /* @__PURE__ */ new Map();
56201
+ const unitsOf = (id) => {
56202
+ let read = unitReads.get(id);
56203
+ if (read === void 0) {
56204
+ read = deps.coordinatorInstances.listUnits(id);
56205
+ unitReads.set(id, read);
56206
+ }
56207
+ return read;
56208
+ };
56209
+ pageOwner = await ownerOf2(operatorThread, unitsOf, msg.threadKey);
56210
+ if (operatorMode === "on" && parseDirectives(msg.text).agent === void 0) {
56211
+ const continuationOwner = pageOwner.kind === "pipeline" || pageOwner.kind === "pipeline_ambiguous" ? pageOwner : await ownerOf2(
56212
+ operatorThread.filter((run3) => run3.finished),
56213
+ unitsOf,
56214
+ msg.threadKey
56215
+ );
56216
+ if (continuationOwner.kind === "pipeline" || continuationOwner.kind === "pipeline_ambiguous") {
56217
+ pageOwner = continuationOwner;
56218
+ operatorMode = "off";
56219
+ }
56220
+ }
56221
+ }
56222
+ }
56223
+ if (operatorMode !== "off") {
55763
56224
  const slot = admission.get(msg.threadKey);
55764
56225
  const liveElsewhere = deps.threadsElsewhere.get(msg.threadKey) !== void 0;
55765
- if (slot === void 0 && !liveElsewhere && operatorThread !== void 0 && deps.coordinatorInstances !== void 0)
55766
- pageOwner = await ownerOf2(operatorThread, (id) => deps.coordinatorInstances.listUnits(id), msg.threadKey);
55767
56226
  const pendingQuestion = operatorMode === "on" ? pendingQuestionOf(operatorThread) : void 0;
55768
56227
  const joinedAnswer = pendingQuestion !== void 0 && !(pendingQuestion.proposal !== void 0 && isYesAnswer(msg.text)) ? joinedAnswerRequest(pendingQuestion, msg.text) : void 0;
55769
56228
  const doorMsg = joinedAnswer !== void 0 ? { ...msg, text: joinedAnswer } : msg;
@@ -55775,7 +56234,7 @@ async function dispatch(deps, msg, io, opts = {}) {
55775
56234
  // other, and the fold runs on to the seed refusal below, which
55776
56235
  // names the unit threads to reply in.
55777
56236
  pageOwner.run.instanceId !== void 0 ? { kind: "live" } : { kind: "live", runId: pageOwner.run.id }
55778
- ) : pageOwner?.kind === "unit" ? { kind: "unit", unit: pageOwner.unit.unit } : pageOwner?.kind === "pipeline" ? { kind: "pipeline", unit: pageOwner.unit.unit } : void 0;
56237
+ ) : pageOwner?.kind === "unit" ? { kind: "unit", unit: pageOwner.unit.unit } : pageOwner?.kind === "pipeline" ? { kind: "pipeline", unit: pageOwner.unit.unit } : pageOwner?.kind === "pipeline_ambiguous" ? { kind: "pipeline", unit: pageOwner.units.map((unit) => unit.unit).join(", ") } : void 0;
55779
56238
  operatorEvent = await root.span(
55780
56239
  "dispatch.operator",
55781
56240
  () => operatorStage(deps, {
@@ -55879,20 +56338,120 @@ async function dispatch(deps, msg, io, opts = {}) {
55879
56338
  await recordPendingOperator();
55880
56339
  return ended;
55881
56340
  }
56341
+ if (owner.kind === "pipeline_ambiguous" && directives.agent === void 0) {
56342
+ await io.reply(
56343
+ `This thread matches multiple ended plan units (${owner.units.map((unit) => unit.unit).join(", ")}), so continuation is ambiguous. Nothing started.`
56344
+ );
56345
+ await recordPendingOperator();
56346
+ return ended;
56347
+ }
55882
56348
  if (owner.kind === "pipeline" && directives.agent === void 0) {
56349
+ const instance = await deps.coordinatorInstances.get(owner.instanceId).catch(() => null);
56350
+ if (instance === null) {
56351
+ await io.reply(
56352
+ `The ended pipeline's record \`${owner.instanceId}\` is unavailable, so continuation did not start.`
56353
+ );
56354
+ await recordPendingOperator();
56355
+ return ended;
56356
+ }
56357
+ const actor = chatActorOf(deps.config, msg);
56358
+ if (authorizeSteerOwner({
56359
+ caller: { ids: actorIdsOf(actor), grants: effectiveGrants(actor) },
56360
+ target: { runId: owner.run.id, requesterId: instance.userId }
56361
+ }).kind === "refused") {
56362
+ await io.reply(STEER_OWNER_REFUSED);
56363
+ await recordPendingOperator();
56364
+ return ended;
56365
+ }
56366
+ if (instance.threadKey !== msg.threadKey || instance.plan === void 0 || instance.plan.path !== void 0) {
56367
+ await io.reply(
56368
+ `The ended pipeline's durable generated-task identity does not match this thread, so continuation did not start.`
56369
+ );
56370
+ await recordPendingOperator();
56371
+ return ended;
56372
+ }
56373
+ if (instance.repo !== owner.run.repo || instance.branch !== owner.unit.branch) {
56374
+ await io.reply(
56375
+ `The ended pipeline's repository or branch identity no longer matches unit ${owner.unit.unit}, so continuation did not start.`
56376
+ );
56377
+ await recordPendingOperator();
56378
+ return ended;
56379
+ }
56380
+ const continuationBudget = continuationBudgetOf(instance, owner.unit);
56381
+ if (continuationBudget.kind === "unavailable") {
56382
+ await io.reply(
56383
+ `Unit ${owner.unit.unit} does not retain a complete durable wall-clock and review-round budget, so continuation did not start. Nothing else ran.`
56384
+ );
56385
+ await recordPendingOperator();
56386
+ return ended;
56387
+ }
56388
+ if (continuationBudget.kind === "exhausted") {
56389
+ await io.reply(
56390
+ `Unit ${owner.unit.unit} exhausted the ended pipeline's ${continuationBudget.budget} budget, so continuation did not start. Nothing else ran.`
56391
+ );
56392
+ await recordPendingOperator();
56393
+ return ended;
56394
+ }
55883
56395
  const original = await shipRequestOf(runsService, owner.run.id);
55884
56396
  if (original === void 0) {
55885
56397
  await io.reply(
55886
- "This ship pipeline ended, but its original task could not be read. What was the pipeline's original task?"
56398
+ `The ended pipeline's original task is unavailable from run \`${owner.run.id}\`, so continuation did not start.`
55887
56399
  );
55888
56400
  await recordPendingOperator();
55889
56401
  return ended;
55890
56402
  }
55891
- const instance = await deps.coordinatorInstances.get(owner.instanceId).catch(() => null);
55892
- if (instance?.plan?.path === void 0) reissuePlanId = instance?.plan?.id;
55893
- directives.text = original;
56403
+ const recordedPr = owner.unit.pr;
56404
+ if (recordedPr === void 0) {
56405
+ await io.reply(
56406
+ `Unit ${owner.unit.unit} does not retain a durable pull request identity, so continuation did not start. Nothing else ran.`
56407
+ );
56408
+ await recordPendingOperator();
56409
+ return ended;
56410
+ }
56411
+ const expectedHead = owner.unit.lastPush;
56412
+ if (expectedHead === void 0) {
56413
+ await io.reply(
56414
+ `Unit ${owner.unit.unit} does not retain the pull request's durable expected head, so continuation did not start. Nothing else ran.`
56415
+ );
56416
+ await recordPendingOperator();
56417
+ return ended;
56418
+ }
56419
+ const target = { repo: instance.repo, number: recordedPr.number };
56420
+ const facts2 = await (deps.fetchPrFacts ?? fetchPullRequestFacts)(target).catch(() => void 0);
56421
+ if (facts2 === void 0) {
56422
+ await io.reply(
56423
+ `GitHub did not return current facts for ${instance.repo}#${recordedPr.number}, so continuation did not start.`
56424
+ );
56425
+ await recordPendingOperator();
56426
+ return ended;
56427
+ }
56428
+ if (facts2.state !== "open") {
56429
+ await io.reply(
56430
+ `${instance.repo}#${recordedPr.number} is closed, so this ended pipeline has no open pull request to continue. Nothing started.`
56431
+ );
56432
+ await recordPendingOperator();
56433
+ return ended;
56434
+ }
56435
+ const verifiedHead = facts2.verifiedHead;
56436
+ if (!facts2.sameRepoHead || facts2.headBranchExists !== true || facts2.headRef !== owner.unit.branch || verifiedHead === void 0 || verifiedHead.repo.toLowerCase() !== instance.repo.toLowerCase() || verifiedHead.ref !== owner.unit.branch || facts2.headSha !== verifiedHead.sha) {
56437
+ await io.reply(
56438
+ `${instance.repo}#${recordedPr.number} no longer has the pipeline's verifiable \`${owner.unit.branch}\` head, so continuation did not start.`
56439
+ );
56440
+ await recordPendingOperator();
56441
+ return ended;
56442
+ }
56443
+ if (verifiedHead.sha !== expectedHead) {
56444
+ await io.reply(
56445
+ `${instance.repo}#${recordedPr.number} moved from the pipeline's expected head \`${expectedHead}\` to \`${verifiedHead.sha}\`, so continuation did not start. Nothing else ran.`
56446
+ );
56447
+ await recordPendingOperator();
56448
+ return ended;
56449
+ }
56450
+ reissuePlanId = instance.plan.id;
56451
+ reissueCaps = continuationBudget.caps;
56452
+ const durableRequest = parseDirectives(original);
56453
+ Object.assign(directives, durableRequest, { agent: void 0, budget: reissueCaps.maxMinutes });
55894
56454
  operatorPreset = "ship";
55895
- operatorRequest = original;
55896
56455
  settled = resolveCurrent();
55897
56456
  ({ sticky, resolved, agentSource } = settled);
55898
56457
  agentSource = "sticky";
@@ -56118,6 +56677,7 @@ async function dispatch(deps, msg, io, opts = {}) {
56118
56677
  card,
56119
56678
  directives,
56120
56679
  ...reissuePlanId !== void 0 ? { reissuePlanId } : {},
56680
+ ...reissueCaps !== void 0 ? { reissueCaps: { ...reissueCaps, maxMinutes: Math.min(reissueCaps.maxMinutes, profile.minutes) } } : {},
56121
56681
  sticky,
56122
56682
  history,
56123
56683
  repoCtx,
@@ -57046,6 +57606,7 @@ var init_dispatcher = __esm({
57046
57606
  init_route();
57047
57607
  init_operator();
57048
57608
  init_authorize2();
57609
+ init_authorize();
57049
57610
  init_messages();
57050
57611
  init_staging();
57051
57612
  init_runRecord();
@@ -57064,6 +57625,7 @@ var init_dispatcher = __esm({
57064
57625
  init_settle();
57065
57626
  init_reattach2();
57066
57627
  init_factory();
57628
+ init_githubPulls();
57067
57629
  init_sandboxErrors();
57068
57630
  init_lineage();
57069
57631
  init_seed();
@@ -57424,8 +57986,8 @@ function createLedgerWriteThrough(opts) {
57424
57986
  settleLanding(this.runId, entry, outcome);
57425
57987
  return value;
57426
57988
  },
57427
- abandoned: (record2, why) => {
57428
- if (record2.id !== this.runId) return;
57989
+ abandoned: (record3, why) => {
57990
+ if (record3.id !== this.runId) return;
57429
57991
  const entry = landing.get(this.runId);
57430
57992
  if (entry !== void 0) settleLanding(this.runId, entry, { kind: "failed", why });
57431
57993
  }
@@ -57435,18 +57997,18 @@ function createLedgerWriteThrough(opts) {
57435
57997
  async land(plain7) {
57436
57998
  await this.close();
57437
57999
  const session = this.sessionOnRecord();
57438
- const record2 = session ? { ...plain7, session } : plain7;
58000
+ const record3 = session ? { ...plain7, session } : plain7;
57439
58001
  let result;
57440
58002
  try {
57441
- result = await ledger.finish(this.runId, gen, record2);
58003
+ result = await ledger.finish(this.runId, gen, record3);
57442
58004
  } catch (err2) {
57443
58005
  if (!(err2 instanceof RouteMissingError)) throw err2;
57444
58006
  routeMissing();
57445
- return { value: await fallback.put(record2), outcome: { kind: "refused" } };
58007
+ return { value: await fallback.put(record3), outcome: { kind: "refused" } };
57446
58008
  }
57447
58009
  if (result.ok) return { value: result, outcome: { kind: "landed" } };
57448
58010
  warn(`[ledger] ${this.threadKey} finish refused (${result.reason}) \u2014 record written to the store directly`);
57449
- return { value: await fallback.put(record2), outcome: { kind: "refused" } };
58011
+ return { value: await fallback.put(record3), outcome: { kind: "refused" } };
57450
58012
  }
57451
58013
  tracked() {
57452
58014
  return !this.detached;
@@ -57516,7 +58078,7 @@ function createLedgerWriteThrough(opts) {
57516
58078
  }));
57517
58079
  if (report2.compaction)
57518
58080
  turns.push({ idx: this.rowIndex(report2.firstIdx + report2.turns.length), compaction: report2.compaction });
57519
- const record2 = {
58081
+ const record3 = {
57520
58082
  step: ++this.stepNo,
57521
58083
  seq: this.lastSeq,
57522
58084
  turnIndex: report2.firstIdx + turns.length,
@@ -57527,10 +58089,10 @@ function createLedgerWriteThrough(opts) {
57527
58089
  turn: report2.turn,
57528
58090
  iteration: report2.iteration
57529
58091
  };
57530
- this.factRound = record2.step;
57531
- const kept = new Set(record2.inFlight.map((c) => c.callId));
58092
+ this.factRound = record3.step;
58093
+ const kept = new Set(record3.inFlight.map((c) => c.callId));
57532
58094
  for (const id of this.factCallStarts.keys()) if (!kept.has(id)) this.factCallStarts.delete(id);
57533
- const call2 = record2.inFlight[0];
58095
+ const call2 = record3.inFlight[0];
57534
58096
  this.factInFlight = call2 ? {
57535
58097
  callId: call2.callId,
57536
58098
  tool: call2.tool,
@@ -57542,13 +58104,13 @@ function createLedgerWriteThrough(opts) {
57542
58104
  } : void 0;
57543
58105
  for (let attempt = 1; ; attempt++) {
57544
58106
  try {
57545
- const result = await ledger.step(this.runId, gen, record2, turns, this.sessionRow?.key);
57546
- if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
57547
- else this.turnsWritten = record2.turnIndex;
58107
+ const result = await ledger.step(this.runId, gen, record3, turns, this.sessionRow?.key);
58108
+ if (!result.ok) this.detach(`step ${record3.step} refused (${result.reason})`);
58109
+ else this.turnsWritten = record3.turnIndex;
57548
58110
  return;
57549
58111
  } catch (err2) {
57550
58112
  if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
57551
- this.detach(`step ${record2.step} failed: ${describe9(err2)}`);
58113
+ this.detach(`step ${record3.step} failed: ${describe9(err2)}`);
57552
58114
  return;
57553
58115
  }
57554
58116
  await sleep2(RETRY_MS[0]);
@@ -59376,8 +59938,8 @@ async function launchOpenCode(deps, spec, bearer) {
59376
59938
  let consumed = 0;
59377
59939
  for (const line4 of text.split("\n").slice(0, -1)) {
59378
59940
  consumed += Buffer.byteLength(line4, "utf8") + 1;
59379
- const record2 = parseFeedRecord(line4);
59380
- if (record2?.feed === "tailer" && record2.note === "connected") {
59941
+ const record3 = parseFeedRecord(line4);
59942
+ if (record3?.feed === "tailer" && record3.note === "connected") {
59381
59943
  feedOffset = consumed;
59382
59944
  break;
59383
59945
  }
@@ -59457,9 +60019,9 @@ var init_process2 = __esm({
59457
60019
  });
59458
60020
 
59459
60021
  // ../../src/core/harness/opencode/bridge.ts
59460
- function executionStartedFor(record2, sessionID) {
59461
- if (record2.feed !== "event" || record2.event.type !== "session.execution.started") return false;
59462
- const data = record2.event.data;
60022
+ function executionStartedFor(record3, sessionID) {
60023
+ if (record3.feed !== "event" || record3.event.type !== "session.execution.started") return false;
60024
+ const data = record3.event.data;
59463
60025
  return data?.sessionID === sessionID;
59464
60026
  }
59465
60027
  function openCodeToolNameWord(name) {
@@ -60131,18 +60693,18 @@ async function driveOpenCode(deps, run2, conn, kind = "loop") {
60131
60693
  const after = transport.consumedOffset;
60132
60694
  const reattachCatchUp = conn.reattach !== void 0 && after <= conn.reattach.catchUpTo;
60133
60695
  lastRecord = next.value;
60134
- const record2 = parseFeedRecord(next.value);
60135
- if (!record2) continue;
60136
- if (!reattachCatchUp && executionStartedFor(record2, conn.sessionID)) {
60696
+ const record3 = parseFeedRecord(next.value);
60697
+ if (!record3) continue;
60698
+ if (!reattachCatchUp && executionStartedFor(record3, conn.sessionID)) {
60137
60699
  executionOwned = true;
60138
60700
  awaiting = void 0;
60139
60701
  }
60140
60702
  bridge.observing = reattachCatchUp ? "catching-up" : executionOwned ? "own" : "earlier";
60141
- const obs = bridge.observe(record2);
60703
+ const obs = bridge.observe(record3);
60142
60704
  if (!reattachCatchUp && obs.boundary === true) conn.boundaries?.reached();
60143
60705
  if (!reattachCatchUp && obs.inboxDelivered !== void 0) conn.inboxFate?.deliver(obs.inboxDelivered);
60144
60706
  if (!reattachCatchUp && obs.inboxDropped === true) conn.inboxFate?.interruptAll();
60145
- if (record2.feed === "messages" && conn.saveOffset !== void 0) {
60707
+ if (record3.feed === "messages" && conn.saveOffset !== void 0) {
60146
60708
  const save = conn.saveOffset;
60147
60709
  void bridge.flush().then(() => save(after));
60148
60710
  }
@@ -60229,14 +60791,14 @@ async function driveOpenCode(deps, run2, conn, kind = "loop") {
60229
60791
  );
60230
60792
  }
60231
60793
  if (replacedBy !== void 0) {
60232
- const record2 = bridge.record(recordBase, deadline);
60794
+ const record3 = bridge.record(recordBase, deadline);
60233
60795
  bridge.closeOpenSpans((open2) => openCodeReplacedCallNote(open2.tool));
60234
60796
  const replaced = replacedBy.condition === "word" ? new OpenCodeContainerReplacedError(
60235
60797
  replacedBy.said.message,
60236
60798
  conn.containerWord,
60237
60799
  await conn.container.identity().catch(() => void 0),
60238
- record2
60239
- ) : new OpenCodeContainerReplacedError(void 0, replacedBy.was, replacedBy.now, record2, "identity");
60800
+ record3
60801
+ ) : new OpenCodeContainerReplacedError(void 0, replacedBy.was, replacedBy.now, record3, "identity");
60240
60802
  note("sandbox_restarted", replaced.message);
60241
60803
  throw replaced;
60242
60804
  }
@@ -60376,13 +60938,13 @@ var init_bridge3 = __esm({
60376
60938
  diagnostics;
60377
60939
  };
60378
60940
  OpenCodeContainerReplacedError = class extends HarnessContainerReplacedError {
60379
- constructor(said2, was, now, record2, condition = "word") {
60941
+ constructor(said2, was, now, record3, condition = "word") {
60380
60942
  super(
60381
60943
  `the container running OpenCode was replaced (${was ?? "unknown"} \u2192 ${now ?? "unknown"}; ${replacedBecause(condition, said2)})`,
60382
60944
  said2,
60383
60945
  was,
60384
60946
  now,
60385
- record2,
60947
+ record3,
60386
60948
  condition
60387
60949
  );
60388
60950
  this.name = "OpenCodeContainerReplacedError";
@@ -60831,25 +61393,25 @@ var init_bridge3 = __esm({
60831
61393
  emit(event) {
60832
61394
  this.deps.emit(event.at === void 0 ? { ...event, at: this.deps.clock() } : event);
60833
61395
  }
60834
- observe(record2) {
60835
- switch (record2.feed) {
61396
+ observe(record3) {
61397
+ switch (record3.feed) {
60836
61398
  case "event":
60837
- return this.onEvent(record2.event);
61399
+ return this.onEvent(record3.event);
60838
61400
  case "permissions":
60839
61401
  if (this.observing === "earlier") return { replies: [], settled: false };
60840
- return this.onPermissionsRefill(record2.data);
61402
+ return this.onPermissionsRefill(record3.data);
60841
61403
  case "messages": {
60842
- const out = this.onMessagesRefill(record2.data);
60843
- if (this.settleOwedTo !== void 0 && record2.reason === this.settleOwedTo) {
61404
+ const out = this.onMessagesRefill(record3.data);
61405
+ if (this.settleOwedTo !== void 0 && record3.reason === this.settleOwedTo) {
60844
61406
  this.settleOwedTo = void 0;
60845
61407
  out.settled = true;
60846
61408
  }
60847
61409
  return out;
60848
61410
  }
60849
61411
  case "tailer":
60850
- if (record2.note === "stream closed" || record2.note.includes("failed"))
60851
- this.deps.onProgress?.(`opencode feed: ${redactAndCap(record2.note, 120)}`);
60852
- if (this.settleOwedTo !== void 0 && (record2.note === "message refill failed" || record2.note === "refill failed") && record2.reason === this.settleOwedTo) {
61412
+ if (record3.note === "stream closed" || record3.note.includes("failed"))
61413
+ this.deps.onProgress?.(`opencode feed: ${redactAndCap(record3.note, 120)}`);
61414
+ if (this.settleOwedTo !== void 0 && (record3.note === "message refill failed" || record3.note === "refill failed") && record3.reason === this.settleOwedTo) {
60853
61415
  this.settleOwedTo = void 0;
60854
61416
  return { replies: [], settled: true };
60855
61417
  }
@@ -61809,7 +62371,7 @@ ${stagedLine}` : prompt;
61809
62371
  return "lost";
61810
62372
  return "running";
61811
62373
  };
61812
- const resolve13 = async (steerSeq, knownAtSteer, seen, waiting2) => {
62374
+ const resolve13 = async (steerSeq, knownAtSteer, seen, waiting3) => {
61813
62375
  let left = false;
61814
62376
  for (; ; ) {
61815
62377
  const verdict = await learnRow(steerSeq, knownAtSteer);
@@ -61824,7 +62386,7 @@ ${stagedLine}` : prompt;
61824
62386
  "the follow-up's steer",
61825
62387
  "the loop left the feed with no row of the steer and the store still showing its execution under way, so the store cannot say whether the server took it"
61826
62388
  );
61827
- waiting2();
62389
+ waiting3();
61828
62390
  if (await conn.boundaries.wait(seen) === "left") left = true;
61829
62391
  seen = conn.boundaries.count;
61830
62392
  }
@@ -62136,6 +62698,14 @@ var init_requester = __esm({
62136
62698
 
62137
62699
  // ../../src/channels/http.ts
62138
62700
  import { timingSafeEqual as timingSafeEqual4 } from "node:crypto";
62701
+ function ingressComponentError(name, value) {
62702
+ if (value.length === 0) return `\`${name}\` must be a non-empty string`;
62703
+ if (value.length > INGRESS_COMPONENT_MAX_LENGTH) {
62704
+ return `\`${name}\` must be at most ${INGRESS_COMPONENT_MAX_LENGTH} characters`;
62705
+ }
62706
+ if (/[:#]/.test(value)) return `\`${name}\` must not contain ':' or '#'`;
62707
+ return void 0;
62708
+ }
62139
62709
  function safeEqual2(a, b) {
62140
62710
  const ab = Buffer.from(a, "utf8");
62141
62711
  const bb = Buffer.from(b, "utf8");
@@ -62179,11 +62749,15 @@ function parseBody3(raw) {
62179
62749
  if (typeof obj2.text !== "string" || obj2.text.trim() === "") {
62180
62750
  return { error: "`text` is required and must be a non-empty string" };
62181
62751
  }
62182
- if (obj2.channel !== void 0 && typeof obj2.channel !== "string") {
62183
- return { error: "`channel` must be a string" };
62752
+ if (obj2.channel !== void 0) {
62753
+ if (typeof obj2.channel !== "string") return { error: "`channel` must be a string" };
62754
+ const error = ingressComponentError("channel", obj2.channel);
62755
+ if (error) return { error };
62184
62756
  }
62185
- if (obj2.thread !== void 0 && typeof obj2.thread !== "string") {
62186
- return { error: "`thread` must be a string" };
62757
+ if (obj2.thread !== void 0) {
62758
+ if (typeof obj2.thread !== "string") return { error: "`thread` must be a string" };
62759
+ const error = ingressComponentError("thread", obj2.thread);
62760
+ if (error) return { error };
62187
62761
  }
62188
62762
  if (obj2.async !== void 0 && typeof obj2.async !== "boolean") {
62189
62763
  return { error: "`async` must be a boolean" };
@@ -62236,6 +62810,10 @@ async function handleAuthorized(identity2, body, deps, options) {
62236
62810
  if ("error" in parsed) {
62237
62811
  return { status: 400, body: { error: parsed.error } };
62238
62812
  }
62813
+ if (identity2.channel !== void 0) {
62814
+ const error = ingressComponentError("channel", identity2.channel);
62815
+ if (error) return { status: 400, body: { error } };
62816
+ }
62239
62817
  const receivedAt = systemClock();
62240
62818
  const trace = startRequestRoot(deps, { channel: "http", receivedAt });
62241
62819
  const msg = {
@@ -62316,7 +62894,7 @@ function parseIngressTokens(secrets) {
62316
62894
  if (!parsed.ok) console.error(`[ingress] SWITCHBOARD_INGRESS_TOKENS is ${parsed.reason} \u2014 ingress disabled`);
62317
62895
  return { tokens: parsed.tokens };
62318
62896
  }
62319
- var PLATFORM, DEFAULT_CHANNEL, DEFAULT_THREAD, MAX_BODY_BYTES2, HttpIO, DISPATCH_ACTION;
62897
+ var PLATFORM, DEFAULT_CHANNEL, DEFAULT_THREAD, INGRESS_COMPONENT_MAX_LENGTH, MAX_BODY_BYTES2, HttpIO, DISPATCH_ACTION;
62320
62898
  var init_http = __esm({
62321
62899
  "../../src/channels/http.ts"() {
62322
62900
  "use strict";
@@ -62329,6 +62907,7 @@ var init_http = __esm({
62329
62907
  PLATFORM = "http";
62330
62908
  DEFAULT_CHANNEL = "default";
62331
62909
  DEFAULT_THREAD = "default";
62910
+ INGRESS_COMPONENT_MAX_LENGTH = 128;
62332
62911
  MAX_BODY_BYTES2 = 1e6;
62333
62912
  HttpIO = class {
62334
62913
  constructor(priorTurns = []) {
@@ -62525,7 +63104,7 @@ function openAiStopReason(raw) {
62525
63104
  }
62526
63105
  }
62527
63106
  function meterAnthropicMessage(json4) {
62528
- const msg = record(json4);
63107
+ const msg = record2(json4);
62529
63108
  if (!msg) return {};
62530
63109
  const usage = usageFromAnthropic(msg.usage);
62531
63110
  return {
@@ -62534,11 +63113,11 @@ function meterAnthropicMessage(json4) {
62534
63113
  };
62535
63114
  }
62536
63115
  function meterOpenAiCompletion(json4) {
62537
- const body = record(json4);
63116
+ const body = record2(json4);
62538
63117
  if (!body) return {};
62539
63118
  const usage = usageFromOpenAI(body.usage);
62540
63119
  const reported = reportedCostOf(body.usage);
62541
- const choice = Array.isArray(body.choices) ? record(body.choices[0]) : void 0;
63120
+ const choice = Array.isArray(body.choices) ? record2(body.choices[0]) : void 0;
62542
63121
  return {
62543
63122
  ...usage ? { usage } : {},
62544
63123
  ...reported ? { reported } : {},
@@ -62548,15 +63127,15 @@ function meterOpenAiCompletion(json4) {
62548
63127
  function responsesStopReason(response) {
62549
63128
  const output = Array.isArray(response.output) ? response.output : [];
62550
63129
  if (response.status === "completed") {
62551
- return output.some((item) => record(item)?.type === "function_call") ? "tool_use" : "end_turn";
63130
+ return output.some((item) => record2(item)?.type === "function_call") ? "tool_use" : "end_turn";
62552
63131
  }
62553
63132
  if (response.status === "incomplete") {
62554
- return record(response.incomplete_details)?.reason === "max_output_tokens" ? "max_tokens" : "other";
63133
+ return record2(response.incomplete_details)?.reason === "max_output_tokens" ? "max_tokens" : "other";
62555
63134
  }
62556
63135
  return "other";
62557
63136
  }
62558
63137
  function meterResponses(json4) {
62559
- const body = record(json4);
63138
+ const body = record2(json4);
62560
63139
  if (!body) return {};
62561
63140
  const usage = usageFromResponses(body.usage);
62562
63141
  const reported = reportedCostOf(body.usage);
@@ -62578,87 +63157,20 @@ function shapeTools(shape, body, marks) {
62578
63157
  const allowed2 = new Set(marks.turn.tools);
62579
63158
  const table = Array.isArray(body.tools) ? body.tools : [];
62580
63159
  const kept = table.filter((t) => {
62581
- const name = toolNameOf(shape, record(t));
63160
+ const name = toolNameOf(shape, record2(t));
62582
63161
  return typeof name === "string" && allowed2.has(name);
62583
63162
  });
62584
63163
  const { tool_choice: _choice, ...rest } = body;
62585
63164
  const shaped = { ...rest, tools: kept };
62586
63165
  return { body: shaped, toolChoice: toolChoiceWord(shape, void 0, kept.length) };
62587
63166
  }
62588
- function responsesRejectsPattern(pattern) {
62589
- return /\(\?(?:[=!]|<[=!])/.test(pattern);
62590
- }
62591
- function shapeResponsesSchema(value, tool, degradations) {
62592
- const schema = record(value);
62593
- if (!schema) {
62594
- if (!Array.isArray(value)) return { value, changed: false };
62595
- let changed2 = false;
62596
- const items = value.map((item) => {
62597
- const shaped = shapeResponsesSchema(item, tool, degradations);
62598
- changed2 ||= shaped.changed;
62599
- return shaped.value;
62600
- });
62601
- return { value: changed2 ? items : value, changed: changed2 };
62602
- }
62603
- let changed = false;
62604
- const out = {};
62605
- for (const [keyword, item] of Object.entries(schema)) {
62606
- if (keyword === "pattern" && typeof item === "string" && responsesRejectsPattern(item)) {
62607
- degradations.push({
62608
- tool,
62609
- keyword,
62610
- why: "the Responses wire does not accept regular-expression lookaround"
62611
- });
62612
- changed = true;
62613
- continue;
62614
- }
62615
- if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record(item)) {
62616
- let mapChanged = false;
62617
- const mapped = {};
62618
- for (const [name, child] of Object.entries(record(item))) {
62619
- const shaped = shapeResponsesSchema(child, tool, degradations);
62620
- mapChanged ||= shaped.changed;
62621
- mapped[name] = shaped.value;
62622
- }
62623
- out[keyword] = mapChanged ? mapped : item;
62624
- changed ||= mapChanged;
62625
- continue;
62626
- }
62627
- if (["additionalProperties", "allOf", "anyOf", "if", "items", "not", "oneOf", "then", "else"].includes(keyword) && typeof item === "object" && item !== null) {
62628
- const shaped = shapeResponsesSchema(item, tool, degradations);
62629
- out[keyword] = shaped.value;
62630
- changed ||= shaped.changed;
62631
- continue;
62632
- }
62633
- out[keyword] = item;
62634
- }
62635
- return { value: changed ? out : value, changed };
62636
- }
62637
- function shapeToolSchemasForWire(shape, body) {
62638
- if (shape !== "openai-responses" || !Array.isArray(body.tools)) return { body, degradations: [] };
62639
- const degradations = [];
62640
- let changed = false;
62641
- const tools = body.tools.map((item) => {
62642
- const tool = record(item);
62643
- if (!tool) return item;
62644
- const name = typeof tool.name === "string" ? tool.name : "unnamed";
62645
- const shaped = shapeResponsesSchema(tool.parameters, name, degradations);
62646
- if (!shaped.changed) return item;
62647
- changed = true;
62648
- return { ...tool, parameters: shaped.value };
62649
- });
62650
- const unique = degradations.filter(
62651
- (candidate, index, all) => all.findIndex((other) => other.tool === candidate.tool && other.keyword === candidate.keyword) === index
62652
- );
62653
- return { body: changed ? { ...body, tools } : body, degradations: unique };
62654
- }
62655
63167
  function toolNameOf(shape, tool) {
62656
63168
  if (tool === void 0) return void 0;
62657
- return shape === "openai-chat" ? record(tool.function)?.name : tool.name;
63169
+ return shape === "openai-chat" ? record2(tool.function)?.name : tool.name;
62658
63170
  }
62659
63171
  function toolSchemaOf(shape, tool) {
62660
63172
  if (shape === "anthropic-messages") return tool.input_schema;
62661
- if (shape === "openai-chat") return record(tool.function)?.parameters;
63173
+ if (shape === "openai-chat") return record2(tool.function)?.parameters;
62662
63174
  return tool.parameters;
62663
63175
  }
62664
63176
  function schemaKeywords(value, into = /* @__PURE__ */ new Set()) {
@@ -62666,12 +63178,12 @@ function schemaKeywords(value, into = /* @__PURE__ */ new Set()) {
62666
63178
  for (const item of value) schemaKeywords(item, into);
62667
63179
  return into;
62668
63180
  }
62669
- const schema = record(value);
63181
+ const schema = record2(value);
62670
63182
  if (!schema) return into;
62671
63183
  for (const [keyword, item] of Object.entries(schema)) {
62672
63184
  into.add(keyword);
62673
- if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record(item)) {
62674
- for (const child of Object.values(record(item))) schemaKeywords(child, into);
63185
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record2(item)) {
63186
+ for (const child of Object.values(record2(item))) schemaKeywords(child, into);
62675
63187
  } else {
62676
63188
  schemaKeywords(item, into);
62677
63189
  }
@@ -62682,7 +63194,7 @@ function providerErrorProse(body) {
62682
63194
  const strings2 = (value, into = []) => {
62683
63195
  if (typeof value === "string") into.push(value);
62684
63196
  else if (Array.isArray(value)) for (const item of value) strings2(item, into);
62685
- else if (record(value)) for (const item of Object.values(record(value))) strings2(item, into);
63197
+ else if (record2(value)) for (const item of Object.values(record2(value))) strings2(item, into);
62686
63198
  return into;
62687
63199
  };
62688
63200
  try {
@@ -62700,7 +63212,7 @@ function providerSchemaRejectionOf2(shape, body, errorBody) {
62700
63212
  prose2
62701
63213
  ))
62702
63214
  return void 0;
62703
- const candidates2 = (Array.isArray(body.tools) ? body.tools : []).map((item) => record(item)).filter((item) => item !== void 0).map((tool) => ({ name: toolNameOf(shape, tool), schema: toolSchemaOf(shape, tool) })).filter(
63215
+ const candidates2 = (Array.isArray(body.tools) ? body.tools : []).map((item) => record2(item)).filter((item) => item !== void 0).map((tool) => ({ name: toolNameOf(shape, tool), schema: toolSchemaOf(shape, tool) })).filter(
62704
63216
  (tool) => typeof tool.name === "string" && quoted(prose2, tool.name)
62705
63217
  );
62706
63218
  if (candidates2.length !== 1) return void 0;
@@ -62723,7 +63235,7 @@ function toolsOffered(shape, body) {
62723
63235
  const table = Array.isArray(body.tools) ? body.tools : [];
62724
63236
  const names = [];
62725
63237
  for (const t of table) {
62726
- const name = toolNameOf(shape, record(t));
63238
+ const name = toolNameOf(shape, record2(t));
62727
63239
  if (typeof name === "string") names.push(name);
62728
63240
  }
62729
63241
  names.sort();
@@ -62736,7 +63248,7 @@ function toolsOffered(shape, body) {
62736
63248
  function toolChoiceWord(shape, choice, tools) {
62737
63249
  if (choice === void 0 || choice === null) return tools > 0 ? "auto" : "none";
62738
63250
  if (shape === "anthropic-messages") {
62739
- const type = record(choice)?.type;
63251
+ const type = record2(choice)?.type;
62740
63252
  if (type === "none" || type === "any" || type === "tool") return type;
62741
63253
  if (type === "auto") return tools > 0 ? "auto" : "none";
62742
63254
  return tools > 0 ? "auto" : "none";
@@ -62744,7 +63256,7 @@ function toolChoiceWord(shape, choice, tools) {
62744
63256
  if (choice === "none") return "none";
62745
63257
  if (choice === "required") return "any";
62746
63258
  if (choice === "auto") return tools > 0 ? "auto" : "none";
62747
- return record(choice) ? "tool" : tools > 0 ? "auto" : "none";
63259
+ return record2(choice) ? "tool" : tools > 0 ? "auto" : "none";
62748
63260
  }
62749
63261
  function turnAttrs(grant2, meter, ttftMs, offered, price) {
62750
63262
  const u = meter.usage;
@@ -62853,7 +63365,7 @@ async function handleAdmitted(door, req, deps) {
62853
63365
  } catch {
62854
63366
  return refusalResponse(shape, 400, "invalid_body", "the body is not JSON");
62855
63367
  }
62856
- const body = record(parsed);
63368
+ const body = record2(parsed);
62857
63369
  if (!body) return refusalResponse(shape, 400, "invalid_body", "the body must be a JSON object");
62858
63370
  const upstream = upstreamFor(
62859
63371
  shape,
@@ -63124,7 +63636,7 @@ function createModelProxyHandler(deps) {
63124
63636
  })();
63125
63637
  };
63126
63638
  }
63127
- var ANTHROPIC_MESSAGES_PATH, OPENAI_CHAT_COMPLETIONS_PATH, OPENAI_RESPONSES_PATH, PROXY_PATHS, RESPONSES_MIN_OUTPUT_TOKENS, MAX_PROXY_BODY_BYTES, DEFAULT_ANTHROPIC_BASE_URL, DEFAULT_ANTHROPIC_VERSION, FORWARDED_REQUEST_HEADERS, FORWARDED_RESPONSE_HEADERS, record, SseMeter, MODEL_STREAM_HEARTBEAT;
63639
+ var ANTHROPIC_MESSAGES_PATH, OPENAI_CHAT_COMPLETIONS_PATH, OPENAI_RESPONSES_PATH, PROXY_PATHS, RESPONSES_MIN_OUTPUT_TOKENS, MAX_PROXY_BODY_BYTES, DEFAULT_ANTHROPIC_BASE_URL, DEFAULT_ANTHROPIC_VERSION, FORWARDED_REQUEST_HEADERS, FORWARDED_RESPONSE_HEADERS, record2, SseMeter, MODEL_STREAM_HEARTBEAT;
63128
63640
  var init_modelProxy = __esm({
63129
63641
  "../../src/channels/modelProxy.ts"() {
63130
63642
  "use strict";
@@ -63132,6 +63644,7 @@ var init_modelProxy = __esm({
63132
63644
  init_providerFailureAuth();
63133
63645
  init_usage();
63134
63646
  init_modelPricing();
63647
+ init_providerToolSchemas();
63135
63648
  init_provider();
63136
63649
  init_http();
63137
63650
  ANTHROPIC_MESSAGES_PATH = "/v1/messages";
@@ -63148,7 +63661,7 @@ var init_modelProxy = __esm({
63148
63661
  DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
63149
63662
  FORWARDED_REQUEST_HEADERS = ["accept", "anthropic-version", "anthropic-beta"];
63150
63663
  FORWARDED_RESPONSE_HEADERS = ["content-type", "request-id", "x-request-id"];
63151
- record = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
63664
+ record2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
63152
63665
  SseMeter = class {
63153
63666
  constructor(shape) {
63154
63667
  this.shape = shape;
@@ -63192,7 +63705,7 @@ var init_modelProxy = __esm({
63192
63705
  };
63193
63706
  }
63194
63707
  apply(json4) {
63195
- const event = record(json4);
63708
+ const event = record2(json4);
63196
63709
  if (!event) return;
63197
63710
  if (this.shape === "openai-responses") {
63198
63711
  if (event.type !== "response.completed" && event.type !== "response.incomplete" && event.type !== "response.failed")
@@ -63205,12 +63718,12 @@ var init_modelProxy = __esm({
63205
63718
  }
63206
63719
  if (this.shape === "anthropic-messages") {
63207
63720
  if (event.type === "message_start") {
63208
- const usage2 = usageFromAnthropic(record(event.message)?.usage);
63721
+ const usage2 = usageFromAnthropic(record2(event.message)?.usage);
63209
63722
  if (usage2) this.usage = { ...this.usage, ...usage2 };
63210
63723
  } else if (event.type === "message_delta") {
63211
- const stop = record(event.delta)?.stop_reason;
63724
+ const stop = record2(event.delta)?.stop_reason;
63212
63725
  if (typeof stop === "string") this.stopReason = anthropicStopReason(stop);
63213
- const usage2 = record(event.usage);
63726
+ const usage2 = record2(event.usage);
63214
63727
  if (usage2) {
63215
63728
  if (typeof usage2.output_tokens === "number") this.usage.outputTokens = usage2.output_tokens;
63216
63729
  if (typeof usage2.input_tokens === "number") this.usage.inputTokens = usage2.input_tokens;
@@ -63223,7 +63736,7 @@ var init_modelProxy = __esm({
63223
63736
  }
63224
63737
  return;
63225
63738
  }
63226
- const choice = Array.isArray(event.choices) ? record(event.choices[0]) : void 0;
63739
+ const choice = Array.isArray(event.choices) ? record2(event.choices[0]) : void 0;
63227
63740
  if (typeof choice?.finish_reason === "string") this.stopReason = openAiStopReason(choice.finish_reason);
63228
63741
  const usage = usageFromOpenAI(event.usage);
63229
63742
  if (usage) this.usage = { ...this.usage, ...usage };
@@ -63245,7 +63758,7 @@ function bearerOf(headers) {
63245
63758
  const m = value ? /^Bearer\s+(\S+)$/i.exec(value.trim()) : null;
63246
63759
  return m ? m[1] : void 0;
63247
63760
  }
63248
- function judge3(deps, presented) {
63761
+ function judge4(deps, presented) {
63249
63762
  const verdict = deps.bearers.verify(presented);
63250
63763
  if (!verdict.ok) {
63251
63764
  const status3 = verdict.reason === "unknown_run" ? 404 : verdict.reason === "malformed" ? 401 : verdict.reason === "unknown_bearer" ? 401 : 403;
@@ -63267,12 +63780,12 @@ function held(path, reason2, runId3) {
63267
63780
  async function admitHarnessRequest(deps, headers, path) {
63268
63781
  const presented = bearerOf(headers);
63269
63782
  if (!presented) return { ok: false, response: { status: 401, body: { error: "missing_bearer" } } };
63270
- let verdict = judge3(deps, presented);
63783
+ let verdict = judge4(deps, presented);
63271
63784
  if (verdict.ok) return verdict;
63272
63785
  if (!RESUME_CAN_OPEN.has(verdict.reason)) return refused6(verdict);
63273
63786
  if (!deps.takeover.settled) {
63274
63787
  await deps.takeover.whenSettled(DOOR_HOLD_MS);
63275
- verdict = judge3(deps, presented);
63788
+ verdict = judge4(deps, presented);
63276
63789
  if (verdict.ok) return verdict;
63277
63790
  if (!RESUME_CAN_OPEN.has(verdict.reason)) return refused6(verdict);
63278
63791
  if (!deps.takeover.settled) return held(path, "reclaim_pending", verdict.runId);
@@ -64608,13 +65121,13 @@ async function catchUpMissedMentions(opts) {
64608
65121
  const orphanCutoffMs = now - (opts.orphanWindowMs ?? ORPHAN_CARD_WINDOW_MS);
64609
65122
  const threadCutoffMs = sweep ? Math.min(cutoffMs, orphanCutoffMs) : cutoffMs;
64610
65123
  const { client, botUserId } = opts;
64611
- const record2 = opts.record ?? recordCatchUpOutcome;
65124
+ const record3 = opts.record ?? recordCatchUpOutcome;
64612
65125
  let channels;
64613
65126
  try {
64614
65127
  channels = await listChannels(client);
64615
65128
  } catch (err2) {
64616
65129
  log(`[catch-up] cannot list channels: ${errMsg(err2)}`);
64617
- record2({ at: now, channels: 0, missed: 0, skippedChannels: 0, error: errMsg(err2) });
65130
+ record3({ at: now, channels: 0, missed: 0, skippedChannels: 0, error: errMsg(err2) });
64618
65131
  return { channels: 0, missed: 0, orphans: 0, skippedChannels: 0 };
64619
65132
  }
64620
65133
  const scans = await mapLimit(channels, CATCH_UP_CHANNEL_CONCURRENCY, async (channel) => {
@@ -64683,7 +65196,7 @@ async function catchUpMissedMentions(opts) {
64683
65196
  log(
64684
65197
  `[catch-up] scanned ${channels.length} channel(s): ${missed} missed message(s), ${orphans} orphaned card(s), ${skippedChannels} skipped`
64685
65198
  );
64686
- record2({ at: now, channels: channels.length, missed, skippedChannels });
65199
+ record3({ at: now, channels: channels.length, missed, skippedChannels });
64687
65200
  return { channels: channels.length, missed, orphans, skippedChannels };
64688
65201
  }
64689
65202
  async function listChannels(client) {
@@ -65403,7 +65916,8 @@ async function handle(deps, { client, statusClient }, ev, intake) {
65403
65916
  span,
65404
65917
  {
65405
65918
  staging: deps.artifacts !== void 0,
65406
- 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)
65407
65921
  },
65408
65922
  relayAppsOf(deps),
65409
65923
  intake
@@ -65485,10 +65999,25 @@ async function receiveSlackMessage(client, ev, span, policy, relayApps, intake)
65485
65999
  threadRuns = gated.thread;
65486
66000
  }
65487
66001
  }
65488
- client.reactions.add({ channel: ev.channel, timestamp: ev.ts, name: ACK_EMOJI }).catch((err2) => {
65489
- if (!err2.message.includes("already_reacted")) console.error(`[ack] ${err2.message}`);
65490
- });
65491
- 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) => {
65492
66021
  console.error(`[catch-up] ${ev.channel}:${ev.ts} delay note failed: ${err2.message}`);
65493
66022
  }) : Promise.resolve();
65494
66023
  const [imagePass, documentPass] = await Promise.all([
@@ -65511,19 +66040,6 @@ async function receiveSlackMessage(client, ev, span, policy, relayApps, intake)
65511
66040
  });
65512
66041
  const unsupported = imagePass.skippedFiles.filter((f) => documentPass.skippedFiles.includes(f));
65513
66042
  const { staged, skipped } = stagedFiles(unsupported, { ...policy, messageId: ev.ts });
65514
- const requester = await resolveSlackRequester(
65515
- client,
65516
- {
65517
- channel: ev.channel,
65518
- ts: ev.ts,
65519
- threadTs: ev.threadTs,
65520
- ...ev.user !== void 0 ? { user: ev.user } : {},
65521
- text: ev.rawText ?? ev.text,
65522
- ...ev.poster !== void 0 ? { poster: ev.poster } : {}
65523
- },
65524
- relayApps
65525
- );
65526
- span.setAttrs({ requester: requester.resolvedBy });
65527
66043
  const [channelName, userName, team] = await Promise.all([
65528
66044
  resolveChannelName(client, ev.channel),
65529
66045
  requester.slackUserId !== void 0 ? resolveUserName(client, requester.slackUserId) : Promise.resolve(requester.userName),
@@ -65843,6 +66359,8 @@ var init_slack = __esm({
65843
66359
  init_intake();
65844
66360
  init_thread();
65845
66361
  init_operator();
66362
+ init_directives();
66363
+ init_verbosity();
65846
66364
  init_confirmations();
65847
66365
  init_threadTurns();
65848
66366
  init_statusBudget();
@@ -66028,14 +66546,15 @@ ${file.text}`));
66028
66546
  io
66029
66547
  };
66030
66548
  }
66031
- async status(initial) {
66549
+ async status(initial, display = {}) {
66032
66550
  const statusClient = this.opts.statusClient ?? this.client;
66033
66551
  const budget = this.opts.statusBudget ?? processStatusBudget;
66034
- 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({
66035
66554
  channel_id: this.ev.channel,
66036
66555
  thread_ts: this.ev.threadTs,
66037
- status: LOADING_PHRASES[0],
66038
- loading_messages: LOADING_PHRASES
66556
+ status: status3,
66557
+ ...status3 === "" ? {} : { loading_messages: LOADING_PHRASES }
66039
66558
  }).catch((err2) => console.error(`[shimmer] ${err2.message}`));
66040
66559
  let ts;
66041
66560
  if (this.opts.existingCard) {
@@ -66060,11 +66579,17 @@ ${file.text}`));
66060
66579
  liveCards.add(liveCardKey(this.ev.channel, ts));
66061
66580
  const card = `${this.ev.channel}:${ts}`;
66062
66581
  const shimmerKey = `${this.ev.channel}:${this.ev.threadTs}`;
66063
- shimmerOwners.set(shimmerKey, card);
66064
- await setShimmer();
66065
- const shimmerTimer = setInterval(() => {
66066
- if (shimmerOwners.get(shimmerKey) === card) void setShimmer();
66067
- }, 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
+ }
66068
66593
  budget.open(card);
66069
66594
  const edit = (frame2) => statusClient.chat.update({ channel: this.ev.channel, ts, ...render2(frame2) });
66070
66595
  let dropped = 0;
@@ -66093,7 +66618,7 @@ ${file.text}`));
66093
66618
  });
66094
66619
  },
66095
66620
  done: async (frame2) => {
66096
- clearInterval(shimmerTimer);
66621
+ if (shimmerTimer !== void 0) clearInterval(shimmerTimer);
66097
66622
  liveCards.delete(liveCardKey(this.ev.channel, ts));
66098
66623
  budget.close(card);
66099
66624
  const waitMs = budget.takeTerminal(this.ev.channel);
@@ -66105,7 +66630,7 @@ ${file.text}`));
66105
66630
  if (dropped > 0) console.log(`[slack] card ${card}: ${dropped} progress frames dropped by the status budget`);
66106
66631
  if (shimmerOwners.get(shimmerKey) !== card) return;
66107
66632
  shimmerOwners.delete(shimmerKey);
66108
- await statusClient.assistant.threads.setStatus({ channel_id: this.ev.channel, thread_ts: this.ev.threadTs, status: "" }).catch(() => {
66633
+ await setShimmer("").catch(() => {
66109
66634
  });
66110
66635
  }
66111
66636
  };
@@ -66571,11 +67096,19 @@ ${JSON.stringify(result.value)}` }] });
66571
67096
  if (typeof args.text !== "string" || args.text.trim() === "") {
66572
67097
  return err(id, INVALID_PARAMS, "`text` is required and must be a non-empty string");
66573
67098
  }
66574
- if (args.channel !== void 0 && typeof args.channel !== "string") {
66575
- return err(id, INVALID_PARAMS, "`channel` must be a string");
67099
+ if (args.channel !== void 0) {
67100
+ if (typeof args.channel !== "string") return err(id, INVALID_PARAMS, "`channel` must be a string");
67101
+ const error = ingressComponentError("channel", args.channel);
67102
+ if (error) return err(id, INVALID_PARAMS, error);
67103
+ }
67104
+ if (args.thread !== void 0) {
67105
+ if (typeof args.thread !== "string") return err(id, INVALID_PARAMS, "`thread` must be a string");
67106
+ const error = ingressComponentError("thread", args.thread);
67107
+ if (error) return err(id, INVALID_PARAMS, error);
66576
67108
  }
66577
- if (args.thread !== void 0 && typeof args.thread !== "string") {
66578
- return err(id, INVALID_PARAMS, "`thread` must be a string");
67109
+ if (identity2.channel !== void 0) {
67110
+ const error = ingressComponentError("channel", identity2.channel);
67111
+ if (error) return err(id, INVALID_PARAMS, error);
66579
67112
  }
66580
67113
  const receivedAt = systemClock();
66581
67114
  const trace = startRequestRoot(deps, { channel: "mcp", receivedAt });
@@ -68211,16 +68744,16 @@ function residentStateTone(state) {
68211
68744
  }
68212
68745
  function residentsFleetTone(records) {
68213
68746
  if (records.length === 0) return "grey";
68214
- const tones = new Set(records.map((record2) => residentStateTone(residentLive(rec(record2)).state)));
68747
+ const tones = new Set(records.map((record3) => residentStateTone(residentLive(rec(record3)).state)));
68215
68748
  if (tones.has("red")) return "red";
68216
68749
  if (tones.has("amber")) return "amber";
68217
68750
  return tones.size === 1 && tones.has("green") ? "green" : "grey";
68218
68751
  }
68219
- function residentSlug(record2) {
68220
- return str8(record2.resource).replace(/^repo:/, "");
68752
+ function residentSlug(record3) {
68753
+ return str8(record3.resource).replace(/^repo:/, "");
68221
68754
  }
68222
- function residentLive(record2) {
68223
- const live = rec(record2.live);
68755
+ function residentLive(record3) {
68756
+ const live = rec(record3.live);
68224
68757
  if (typeof live.error === "string") return { ...live, state: "unreachable", reason: live.error };
68225
68758
  return { ...live, state: str8(live.state) || "unknown", reason: str8(live.reason) };
68226
68759
  }
@@ -68520,12 +69053,12 @@ function createResidentsViewHandler(deps) {
68520
69053
  finish2(200);
68521
69054
  return;
68522
69055
  }
68523
- const record2 = listing.residents.find((x) => residentSlug(x) === route2.slug);
68524
- if (!record2) {
69056
+ const record3 = listing.residents.find((x) => residentSlug(x) === route2.slug);
69057
+ if (!record3) {
68525
69058
  plainEnd(404, `${route2.slug} is not onboarded as a resident`);
68526
69059
  return;
68527
69060
  }
68528
- page2(req, res, 200, ctx.actor, route2.slug, { page: "resident", slug: route2.slug, record: record2 });
69061
+ page2(req, res, 200, ctx.actor, route2.slug, { page: "resident", slug: route2.slug, record: record3 });
68529
69062
  finish2(200);
68530
69063
  });
68531
69064
  return true;
@@ -70667,9 +71200,9 @@ function startReclaimSweep(opts) {
70667
71200
  return { stop: () => stop(timer) };
70668
71201
  }
70669
71202
  async function closeReclaimed(ledger, gen, input) {
70670
- const record2 = reclaimedRunRecord(input);
70671
- const result = await ledger.finish(input.row.runId, gen, record2);
70672
- return result.ok ? { ok: true, record: record2 } : { ok: false, reason: result.reason ?? "refused" };
71203
+ const record3 = reclaimedRunRecord(input);
71204
+ const result = await ledger.finish(input.row.runId, gen, record3);
71205
+ return result.ok ? { ok: true, record: record3 } : { ok: false, reason: result.reason ?? "refused" };
70673
71206
  }
70674
71207
  async function completenessVerdict(ledger, row2, lastStep) {
70675
71208
  if (!lastStep) return { resumable: false, why: "no step record: killed before its conversation was stored" };
@@ -70870,7 +71403,12 @@ async function launchResumes(deps, resumable, opts) {
70870
71403
  startedAt: row2.startedAt,
70871
71404
  now: deps.clock ?? Date.now
70872
71405
  });
70873
- 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 });
70874
71412
  } catch (err2) {
70875
71413
  warn(
70876
71414
  `[resume] ${row2.runId} ${row2.threadKey}: restart card could not be written: ${err2 instanceof Error ? err2.message : String(err2)}`
@@ -70949,6 +71487,7 @@ var PI_READ_ONLY_TOOLS;
70949
71487
  var init_resumeLaunch = __esm({
70950
71488
  "../../src/core/resumeLaunch.ts"() {
70951
71489
  "use strict";
71490
+ init_directives();
70952
71491
  init_inboxMessage();
70953
71492
  init_resume();
70954
71493
  init_runRegistry();
@@ -71925,7 +72464,7 @@ function watched(io, on) {
71925
72464
  on.replied(text);
71926
72465
  await io.reply(text);
71927
72466
  },
71928
- status: (initial) => io.status(initial),
72467
+ status: (initial, display) => io.status(initial, display),
71929
72468
  history: () => io.history(),
71930
72469
  runStarted: (started) => {
71931
72470
  io.runStarted?.(started);
@@ -71999,6 +72538,33 @@ The thread since the last step:
71999
72538
  ${foldThreadEvents(folded)}` };
72000
72539
  }
72001
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
+ }
72002
72568
  const msg = {
72003
72569
  channelId: instance.channelId,
72004
72570
  userId: instance.userId,
@@ -72040,10 +72606,16 @@ ${foldThreadEvents(folded)}` };
72040
72606
  lastReply = text;
72041
72607
  }
72042
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;
72043
72614
  const tag = {
72044
72615
  parentInstanceId: instance.id,
72045
72616
  idempotencyKey: key,
72046
- ...instance.base !== void 0 ? { base: instance.base } : {}
72617
+ ...instance.base !== void 0 ? { base: instance.base } : {},
72618
+ ...publication !== void 0 ? { publication } : {}
72047
72619
  };
72048
72620
  let dispatching;
72049
72621
  try {
@@ -72219,11 +72791,11 @@ async function reviewPostedAtPatiently(deps, pr, verdict, head) {
72219
72791
  }
72220
72792
  return answer;
72221
72793
  }
72222
- function reviewPostedByRecord(record2, pr) {
72223
- const post = record2.reviewPost;
72224
- if (post === void 0 || record2.reviewHead === void 0 || record2.verdict === void 0) return void 0;
72794
+ function reviewPostedByRecord(record3, pr) {
72795
+ const post = record3.reviewPost;
72796
+ if (post === void 0 || record3.reviewHead === void 0 || record3.verdict === void 0) return void 0;
72225
72797
  if (!post.posted) return { reviewPosted: false, reviewPostReason: post.reason };
72226
- const same = post.target.repo === pr.repo && post.target.number === pr.number && sameCommit(post.head.toLowerCase(), record2.reviewHead) && post.verdict === record2.verdict.verdict;
72798
+ const same = post.target.repo === pr.repo && post.target.number === pr.number && sameCommit(post.head.toLowerCase(), record3.reviewHead) && post.verdict === record3.verdict.verdict;
72227
72799
  return same ? { reviewPosted: true } : void 0;
72228
72800
  }
72229
72801
  function headBranchStateUnknown(facts2) {
@@ -72240,6 +72812,7 @@ function pullRequestState(number, fallbackUrl, facts2) {
72240
72812
  state: "merged",
72241
72813
  prNumber: number,
72242
72814
  url,
72815
+ ...facts2.headSha !== void 0 ? { headSha: facts2.headSha } : {},
72243
72816
  sha: facts2.mergeCommitSha,
72244
72817
  mergedAt: facts2.mergedAt,
72245
72818
  ...facts2.mergedBy !== void 0 ? { mergedBy: facts2.mergedBy } : {}
@@ -72310,15 +72883,16 @@ async function readRecord(body, deps) {
72310
72883
  at
72311
72884
  });
72312
72885
  const full = await deps.runs.getRun(body.runId, { include: "messages" });
72313
- const record2 = full.ok ? full.value : view;
72314
- const finalReply = finalReplyOf2(record2.events);
72315
- const reviewAskedAt = reviewAskedAtOf(record2.events);
72316
- const opened = prOpenedOf(record2.events);
72886
+ const record3 = full.ok ? full.value : view;
72887
+ const finalReply = finalReplyOf2(record3.events);
72888
+ const reviewAskedAt = reviewAskedAtOf(record3.events);
72889
+ const description = record3.events?.some((event) => event.type === "pr_description") === true;
72890
+ const opened = prOpenedOf(record3.events);
72317
72891
  const pr = opened !== void 0 ? { number: opened.number, url: opened.url, created: opened.created } : void 0;
72318
72892
  let coordinatorUnit;
72319
72893
  if (instanceRow !== null && typeof body.unit === "string") {
72320
72894
  coordinatorUnit = (await deps.instances.listUnits(instanceRow.id)).find((u) => u.unit === body.unit);
72321
- const pushedPrRef = opened?.head !== void 0 && record2.pushed?.some((pushed) => pushed.ref === opened.head) ? opened.head : void 0;
72895
+ const pushedPrRef = opened?.head !== void 0 && record3.pushed?.some((pushed) => pushed.ref === opened.head) ? opened.head : void 0;
72322
72896
  if (coordinatorUnit !== void 0 && pushedPrRef !== void 0 && coordinatorUnit.branch !== pushedPrRef) {
72323
72897
  coordinatorUnit = { ...coordinatorUnit, branch: pushedPrRef };
72324
72898
  await deps.instances.putUnits([coordinatorUnit]);
@@ -72345,13 +72919,13 @@ async function readRecord(body, deps) {
72345
72919
  });
72346
72920
  }
72347
72921
  let posted;
72348
- if (record2.verdict !== void 0 && record2.reviewHead !== void 0 && typeof body.unit === "string") {
72922
+ if (record3.verdict !== void 0 && record3.reviewHead !== void 0 && typeof body.unit === "string") {
72349
72923
  const instance = instanceRow;
72350
72924
  if (instance && coordinatorUnit?.pr !== void 0) {
72351
72925
  const unitPr = { repo: instance.repo, number: coordinatorUnit.pr.number };
72352
- posted = reviewPostedByRecord(record2, unitPr);
72926
+ posted = reviewPostedByRecord(record3, unitPr);
72353
72927
  if (posted === void 0) {
72354
- const seen = await reviewPostedAtPatiently(deps, unitPr, record2.verdict.verdict, record2.reviewHead);
72928
+ const seen = await reviewPostedAtPatiently(deps, unitPr, record3.verdict.verdict, record3.reviewHead);
72355
72929
  if (seen !== void 0) posted = { reviewPosted: seen };
72356
72930
  }
72357
72931
  }
@@ -72363,27 +72937,31 @@ async function readRecord(body, deps) {
72363
72937
  run: {
72364
72938
  ...coordinatorRunView(view, id.value, finalReply),
72365
72939
  ...pr !== void 0 ? { pr } : {},
72366
- ...record2.verdict !== void 0 ? { verdict: record2.verdict } : {},
72367
- ...record2.reviewHead !== void 0 ? { reviewHead: record2.reviewHead } : {},
72940
+ ...record3.verdict !== void 0 ? { verdict: record3.verdict } : {},
72941
+ ...record3.reviewHead !== void 0 ? { reviewHead: record3.reviewHead } : {},
72368
72942
  ...reviewAskedAt !== void 0 ? { reviewAskedAt } : {},
72369
72943
  ...posted !== void 0 ? { reviewPosted: posted.reviewPosted } : {},
72370
72944
  ...posted?.reviewPostReason !== void 0 ? { reviewPostReason: posted.reviewPostReason } : {},
72371
- ...record2.dispositions !== void 0 ? { dispositions: record2.dispositions } : {},
72372
- ...record2.handoff !== void 0 ? { handoff: true } : {},
72945
+ ...record3.dispositions !== void 0 ? { dispositions: record3.dispositions } : {},
72946
+ // Findings readiness requires the typed description output. Its event is
72947
+ // durable on the child record, so the runner can verify it without
72948
+ // trusting final-reply prose.
72949
+ ...description ? { description: true } : {},
72950
+ ...record3.handoff !== void 0 ? { handoff: true } : {},
72373
72951
  // The renewal's facts (decision 0046): progress is read off these.
72374
- ...record2.pushed !== void 0 ? { pushed: record2.pushed } : {},
72375
- ...record2.lease !== void 0 ? { leaseStartedAt: record2.lease.startedAt } : {},
72952
+ ...record3.pushed !== void 0 ? { pushed: record3.pushed } : {},
72953
+ ...record3.lease !== void 0 ? { leaseStartedAt: record3.lease.startedAt } : {},
72376
72954
  // What the child cost, as the runs service prices it (costs.md item 4c):
72377
72955
  // null when unknown — no usage on the record, or a model without a price
72378
72956
  // — so a capped grant never renews on an understated total.
72379
- costUsd: record2.cost?.usd ?? null,
72380
- ...record2.handoff !== void 0 ? { handoffLists: record2.handoff } : {},
72957
+ costUsd: record3.cost?.usd ?? null,
72958
+ ...record3.handoff !== void 0 ? { handoffLists: record3.handoff } : {},
72381
72959
  // The failure by name (run-history item 57): a `provider_transient` lets
72382
72960
  // the machine re-run a round-0 child that pushed nothing (issue 1932).
72383
- ...record2.failure !== void 0 ? { failure: record2.failure } : {},
72961
+ ...record3.failure !== void 0 ? { failure: record3.failure } : {},
72384
72962
  // What ended an interrupted child (issue 1876), off its record's own
72385
72963
  // events: the unit's ending names the actual cause in the user's nouns.
72386
- ...view.status === "interrupted" ? interruptionOf(record2.events) : {}
72964
+ ...view.status === "interrupted" ? interruptionOf(record3.events) : {}
72387
72965
  },
72388
72966
  at
72389
72967
  });
@@ -72462,6 +73040,13 @@ async function recoverPushedBranch(deps, instance, row2, branch2, runId3) {
72462
73040
  if (subject2 !== void 0 && checkPrTitle(subject2, prTitleVocabulary_default).ok) title = subject2;
72463
73041
  }
72464
73042
  title ??= recoveredFallbackTitle(row2, branch2, instance.plan?.id ?? parsePlanBranch(branch2)?.planId);
73043
+ const header4 = requestedByLine({
73044
+ name: instance.userName?.trim() || instance.userId,
73045
+ threadUrl: threadPageLink(instance.threadKey, deps.runPageBase?.replace(/\/runs\/?$/, "") ?? "")
73046
+ });
73047
+ prBody = `${header4}
73048
+
73049
+ ${prBody}`;
72465
73050
  if (deps.rewriteIdentities !== void 0) {
72466
73051
  const rewritten = await deps.rewriteIdentities({
72467
73052
  repo: instance.repo,
@@ -72521,9 +73106,19 @@ async function prCheck(body, deps) {
72521
73106
  const unit = await unitRowOf(deps, instance, body.unit);
72522
73107
  if (!unit.ok) return unit.response;
72523
73108
  const branch2 = unit.row?.branch ?? instance.branch;
72524
- const remember = async (pr) => {
72525
- if (unit.row && (unit.row.pr?.number !== pr.number || unit.row.pr.url !== pr.url))
72526
- 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 } : {} }]);
72527
73122
  };
72528
73123
  try {
72529
73124
  if (follow !== void 0) {
@@ -72532,8 +73127,12 @@ async function prCheck(body, deps) {
72532
73127
  const fallbackUrl = unit.row?.pr?.url ?? `https://github.com/${instance.repo}/pull/${follow}`;
72533
73128
  const state2 = pullRequestState(follow, fallbackUrl, facts2);
72534
73129
  if (state2 === void 0) throw new Error(headBranchStateError(instance.repo, follow));
72535
- await remember({ number: follow, url: String(state2.url) });
73130
+ await remember({ number: follow, url: String(state2.url) }, facts2);
72536
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
+ });
72537
73136
  const prRef = { repo: instance.repo, number: follow };
72538
73137
  const headSha = facts2.headSha;
72539
73138
  const approved = entry && headSha !== void 0 ? await reviewPostedAt(deps, prRef, "approve", headSha) : void 0;
@@ -72564,13 +73163,13 @@ async function prCheck(body, deps) {
72564
73163
  }
72565
73164
  const open2 = await deps.findOpenPrByHead(instance.repo, branch2);
72566
73165
  if (open2) {
72567
- await remember({ number: open2.number, url: open2.htmlUrl });
72568
73166
  deps.runnerOwnership?.claim(instance.repo, open2.number, {
72569
73167
  instanceId: instance.id,
72570
73168
  unit: unit.row?.unit ?? body.unit
72571
73169
  });
72572
73170
  const liveFacts = await deps.fetchPrFacts({ repo: instance.repo, number: open2.number });
72573
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);
72574
73173
  const current = pullRequestState(open2.number, open2.htmlUrl, liveFacts);
72575
73174
  if (current === void 0) throw new Error(headBranchStateError(instance.repo, open2.number));
72576
73175
  if (current.state !== "open") return json3(200, { ok: true, ...current, at });
@@ -72879,12 +73478,13 @@ async function drawCard(deps, instance, units, close) {
72879
73478
  source: instance.addressSeveritySource ?? "org"
72880
73479
  } : void 0
72881
73480
  );
73481
+ const display = { verbosity: instance.verbosity ?? DEFAULT_VERBOSITY };
72882
73482
  if (!close) {
72883
- await io.status(shell.live({ detail }));
73483
+ await io.status(shell.live({ detail }), display);
72884
73484
  return;
72885
73485
  }
72886
73486
  shell.freeze(clock4());
72887
- const handle2 = await io.status(shell.live({ detail }));
73487
+ const handle2 = await io.status(shell.live({ detail }), display);
72888
73488
  await handle2.done(shell.close({ kind: "done", icon: close.icon, detail: detail.join("\n") }));
72889
73489
  }
72890
73490
  function parseGate(raw) {
@@ -73711,7 +74311,7 @@ ${planSummary(units)}`).catch(() => {
73711
74311
  const summary = deps.registry.getById(host.runId);
73712
74312
  const visibility = await deps.channelVisibilityOf(instance.channelId);
73713
74313
  const finishedAt = snap?.finishedAt ?? at;
73714
- const record2 = assembleRunRecord({
74314
+ const record3 = assembleRunRecord({
73715
74315
  run: { id: host.runId, ...summary?.label !== void 0 ? { label: summary.label } : {} },
73716
74316
  snap,
73717
74317
  agent: "ship",
@@ -73741,7 +74341,7 @@ ${planSummary(units)}`).catch(() => {
73741
74341
  seal
73742
74342
  });
73743
74343
  const handle2 = deps.ledgerRuns().find((run2) => run2.runId === host.runId);
73744
- deps.runHistoryWriter.write(record2, handle2 !== void 0 ? { via: handle2.sink } : void 0);
74344
+ deps.runHistoryWriter.write(record3, handle2 !== void 0 ? { via: handle2.sink } : void 0);
73745
74345
  }
73746
74346
  (deps.log ?? console.log)(`[coordinator] ${instance.id}: finished ${body.outcome} \u2014 record ${runId3}`);
73747
74347
  return json3(200, { ok: true, runId: runId3, at });
@@ -73867,6 +74467,8 @@ var init_adminCoordinator = __esm({
73867
74467
  "use strict";
73868
74468
  init_budgets();
73869
74469
  init_verbosity();
74470
+ init_prDescription();
74471
+ init_reply();
73870
74472
  init_registry();
73871
74473
  init_authorize();
73872
74474
  init_actor();