@coreplane/switchboard 1.261.0 → 1.261.1

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() ?? "";
@@ -6553,15 +6579,15 @@ function capEvent(event, maxBytes) {
6553
6579
  }
6554
6580
  return { event: capped4, bytes: size };
6555
6581
  }
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 };
6582
+ function fitRecordToBudget(record3, maxBytes = MAX_RECORD_BYTES) {
6583
+ const cappedAll = record3.events.map((e) => capEvent(e, MAX_EVENT_BYTES));
6584
+ const measure = (evs) => utf8ByteLength(JSON.stringify({ ...record3, events: evs, storedEventCount: evs.length, truncated: true }));
6585
+ const whole = { ...record3, events: cappedAll.map((c) => c.event), storedEventCount: cappedAll.length };
6560
6586
  if (utf8ByteLength(JSON.stringify(whole)) <= maxBytes) return whole;
6561
6587
  const baseline = measure([]);
6562
6588
  const capped4 = dropSpanPairsFromTheMiddle(cappedAll, maxBytes - baseline);
6563
6589
  const events = capped4.map((c) => c.event);
6564
- if (measure(events) <= maxBytes) return { ...record2, events, storedEventCount: events.length, truncated: true };
6590
+ if (measure(events) <= maxBytes) return { ...record3, events, storedEventCount: events.length, truncated: true };
6565
6591
  const sizes = capped4.map((c) => c.bytes + 1);
6566
6592
  let remaining = maxBytes - measure([]);
6567
6593
  let head = 0;
@@ -6578,7 +6604,7 @@ function fitRecordToBudget(record2, maxBytes = MAX_RECORD_BYTES) {
6578
6604
  while (kept.length > 0 && measure(kept) > maxBytes) {
6579
6605
  kept.splice(Math.floor(kept.length / 2), 1);
6580
6606
  }
6581
- return { ...record2, events: kept, storedEventCount: kept.length, truncated: true };
6607
+ return { ...record3, events: kept, storedEventCount: kept.length, truncated: true };
6582
6608
  }
6583
6609
  function headLength(events) {
6584
6610
  let n2 = 0;
@@ -6696,9 +6722,9 @@ function dominantFriction(byCategory) {
6696
6722
  }
6697
6723
  return best;
6698
6724
  }
6699
- function pointOf(record2, prices) {
6700
- if (record2.provisional === true) return void 0;
6701
- const usage = record2.usage ?? emptyUsage();
6725
+ function pointOf(record3, prices) {
6726
+ if (record3.provisional === true) return void 0;
6727
+ const usage = record3.usage ?? emptyUsage();
6702
6728
  const priced = llmUsdOfUsage(usage, prices);
6703
6729
  let inputTokens = 0;
6704
6730
  let outputTokens = 0;
@@ -6710,27 +6736,27 @@ function pointOf(record2, prices) {
6710
6736
  cacheReadTokens += m.cacheReadTokens;
6711
6737
  cacheWriteTokens += m.cacheWriteTokens;
6712
6738
  }
6713
- const shape = record2.diagnosis.shape;
6739
+ const shape = record3.diagnosis.shape;
6714
6740
  const blobs = {
6715
6741
  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
6742
+ agent: record3.agent ?? "",
6743
+ preset: record3.profile?.preset ?? "",
6744
+ model: record3.model ?? "",
6745
+ status: record3.status,
6746
+ "failure kind": record3.failure?.kind ?? "",
6747
+ "dominant friction": dominantFriction(record3.diagnosis.byCategory),
6748
+ channel: record3.channelId,
6749
+ repository: record3.repo ?? "",
6750
+ "machine class": record3.profile?.machine ?? "",
6751
+ "route class": record3.route !== void 0 ? "routed" : "chosen",
6752
+ reply: record3.replyOk === true ? "ok" : record3.replyOk === false ? "failed" : "none",
6753
+ lineage: record3.parentRunId !== void 0 ? "child" : "root",
6754
+ requester: record3.userId,
6755
+ identity: record3.profile?.identity ?? "",
6756
+ "run id": record3.id
6731
6757
  };
6732
6758
  const doubles = {
6733
- wall: record2.finishedAt - (record2.receivedAt ?? record2.startedAt),
6759
+ wall: record3.finishedAt - (record3.receivedAt ?? record3.startedAt),
6734
6760
  "getting ready": shape?.gettingReadyMs ?? 0,
6735
6761
  thinking: shape?.thinkingMs ?? 0,
6736
6762
  tools: shape?.toolsMs ?? 0,
@@ -6744,15 +6770,15 @@ function pointOf(record2, prices) {
6744
6770
  "cache read tokens": cacheReadTokens,
6745
6771
  "cache write tokens": cacheWriteTokens,
6746
6772
  dollars: priced.usd,
6747
- steps: record2.stepCount ?? 0,
6748
- "tool calls": isFinite_2(record2.diagnosis.toolCalls) ? record2.diagnosis.toolCalls : 0,
6749
- events: record2.eventCount,
6773
+ steps: record3.stepCount ?? 0,
6774
+ "tool calls": isFinite_2(record3.diagnosis.toolCalls) ? record3.diagnosis.toolCalls : 0,
6775
+ events: record3.eventCount,
6750
6776
  "unpriced tokens": priced.unpricedTokens,
6751
- minutes: record2.profile?.minutes ?? 0,
6752
- "finished at": record2.finishedAt
6777
+ minutes: record3.profile?.minutes ?? 0,
6778
+ "finished at": record3.finishedAt
6753
6779
  };
6754
6780
  return {
6755
- indexes: [capBlob(record2.agent ?? UNKNOWN_AGENT)],
6781
+ indexes: [capBlob(record3.agent ?? UNKNOWN_AGENT)],
6756
6782
  blobs: POINT_COLUMNS.blobs.map((n2) => capBlob(blobs[n2])),
6757
6783
  doubles: POINT_COLUMNS.doubles.map((n2) => doubles[n2])
6758
6784
  };
@@ -10390,12 +10416,8 @@ function containerApplicationName(script, className) {
10390
10416
  return `${script}-${className.toLowerCase()}`;
10391
10417
  }
10392
10418
  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
- };
10419
+ const containerApp = containerApplicationName(script, gate.containerClass);
10420
+ return gate.kind === "bot" ? { kind: "bot", healthUrl, containerApp } : { kind: "sandbox", healthUrl, bearerEnv: gate.bearerEnv, containerApp };
10399
10421
  }
10400
10422
  function workersFor(profile) {
10401
10423
  return WORKER_SPECS.flatMap(({ preflight, liveGate, ...spec }) => {
@@ -10410,8 +10432,8 @@ function workersFor(profile) {
10410
10432
  script,
10411
10433
  baseUrl,
10412
10434
  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 } : {},
10435
+ // The bot preflight reads the same URL as its application + health live gate.
10436
+ ...preflight ? { preflight: liveGate?.kind === "bot" ? { ...preflight, healthUrl } : preflight } : {},
10415
10437
  ...liveGate ? { liveGate: bindLiveGate(liveGate, script, healthUrl) } : {}
10416
10438
  }
10417
10439
  ];
@@ -10522,7 +10544,7 @@ function formatPlan(plan3) {
10522
10544
  plan3.steps.forEach((s, i) => {
10523
10545
  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
10546
  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)` : "";
10547
+ 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
10548
  const cap = s.capabilities.length > 0 ? ` \u2014 credential must pass ${s.capabilities.map((c) => `\`${c.command.join(" ")}\` (${c.needs})`).join(" and ")}` : "";
10527
10549
  lines.push(
10528
10550
  ` ${i + 1}. ${s.name} (${s.script}) \u2014 ${s.dir}: ${s.command.join(" ")}${pf}${env}${cap}${live}
@@ -10593,7 +10615,7 @@ function classifyDeployOutput(exitCode, output) {
10593
10615
  const firstError = lines.find((l) => isErrorish(l) && !/^npm ERR!/i.test(l)) ?? lines.find(isErrorish);
10594
10616
  return { kind: "failed", reason: firstError ?? `exit ${exitCode}` };
10595
10617
  }
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;
10618
+ 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
10619
  var init_plan = __esm({
10598
10620
  "../../src/deploy/plan.ts"() {
10599
10621
  "use strict";
@@ -10605,6 +10627,7 @@ var init_plan = __esm({
10605
10627
  init_profile2();
10606
10628
  UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
10607
10629
  DEPLOY_FORCE_ENV = "SWITCHBOARD_DEPLOY_FORCE";
10630
+ BOT_CONTAINER_CLASS = "SwitchboardServer";
10608
10631
  SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
10609
10632
  SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
10610
10633
  R2_CAPABILITY = {
@@ -10665,10 +10688,10 @@ var init_plan = __esm({
10665
10688
  ]
10666
10689
  },
10667
10690
  preflight: { forceEnv: DEPLOY_FORCE_ENV, baseUrlEnv: "SWITCHBOARD_BASE_URL" },
10668
- liveGate: { kind: "health" },
10691
+ liveGate: { kind: "bot", containerClass: BOT_CONTAINER_CLASS },
10669
10692
  // The preflight reads the container application and the deploy pushes the image: both need Containers.
10670
10693
  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"
10694
+ 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
10695
  },
10673
10696
  {
10674
10697
  name: "resident",
@@ -11788,11 +11811,11 @@ var init_runStoreWorker = __esm({
11788
11811
  fetchImpl;
11789
11812
  abandoned() {
11790
11813
  }
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);
11814
+ async put(record3, trace) {
11815
+ if (!RUN_ID_PATTERN.test(record3.id))
11816
+ throw new PermanentStoreError(`run store: refusing to put malformed id ${JSON.stringify(record3.id)}`);
11817
+ const body = { storeKey: this.opts.storeKey, record: record3 };
11818
+ const point = pointOf(record3, this.opts.prices);
11796
11819
  if (point !== void 0) body.point = point;
11797
11820
  if (this.opts.policy) {
11798
11821
  body.policy = this.opts.policy;
@@ -11962,6 +11985,66 @@ var init_supersede = __esm({
11962
11985
  }
11963
11986
  });
11964
11987
 
11988
+ // ../../src/deploy/botLiveGate.ts
11989
+ function judge(input) {
11990
+ if ("error" in input.app)
11991
+ return waiting(`container application ${input.containerApp} unreadable: ${input.app.error}`);
11992
+ const app = input.app.value;
11993
+ let rollout;
11994
+ if (input.target) {
11995
+ if (input.target.image !== null && app.image !== input.target.image)
11996
+ return waiting(
11997
+ `container application ${input.containerApp} still targets ${app.image ?? "an unknown image"}; expected ${input.target.image}`
11998
+ );
11999
+ if ("error" in input.before)
12000
+ return waiting(
12001
+ `container application ${input.containerApp} pre-deploy version unreadable (${input.before.error}); cannot prove the application version advanced`
12002
+ );
12003
+ if (app.version <= input.before.value.version)
12004
+ return waiting(
12005
+ `container application ${input.containerApp} is still at pre-deploy version ${input.before.value.version}; expected a newer application version`
12006
+ );
12007
+ rollout = ` at version ${app.version} (up from ${input.before.value.version})`;
12008
+ } else {
12009
+ rollout = ` at version ${app.version} (Worker-only deploy; no application change printed)`;
12010
+ }
12011
+ if ("error" in input.health) return waiting(`health: GET /healthz failed: ${input.health.error}`);
12012
+ if (input.health.status !== 200)
12013
+ return waiting(
12014
+ `health: GET /healthz \u2192 HTTP ${input.health.status}; expected the deployed container's exact build identity`
12015
+ );
12016
+ if (!input.health.body || input.health.body.ok !== true)
12017
+ return waiting("health: /healthz is not ready or did not answer with JSON");
12018
+ const commit = servedCommit(input.health.body);
12019
+ if (!commit) return waiting("health: /healthz carries no build identity");
12020
+ if (commit !== input.expectedCommit)
12021
+ return waiting(`health: serving commit ${commit}, expected exact ${input.expectedCommit}`);
12022
+ if (input.health.body.draining === true)
12023
+ return waiting("health: the exact deployed commit answers but its container is draining");
12024
+ const target = input.target?.image ?? app.image ?? "an unchanged image";
12025
+ return {
12026
+ kind: "live",
12027
+ summary: `container application ${input.containerApp} targets ${target}${rollout}; /healthz serves exact ${commit}`
12028
+ };
12029
+ }
12030
+ function decideBotLive(input, deadlineMs = LIVE_GATE_DEADLINE_MS) {
12031
+ const decision = judge(input);
12032
+ if (decision.kind !== "waiting" || input.elapsedMs < deadlineMs) return decision;
12033
+ return {
12034
+ kind: "failed",
12035
+ reason: `${decision.reason} \u2014 still not live after ${Math.round(input.elapsedMs / MINUTE_MS)} min (deadline ${deadlineMs / MINUTE_MS} min)`
12036
+ };
12037
+ }
12038
+ var waiting;
12039
+ var init_botLiveGate = __esm({
12040
+ "../../src/deploy/botLiveGate.ts"() {
12041
+ "use strict";
12042
+ init_budgets();
12043
+ init_liveGate();
12044
+ waiting = (reason2) => ({ kind: "waiting", reason: reason2 });
12045
+ }
12046
+ });
12047
+
11965
12048
  // ../../src/execution/sandboxErrors.ts
11966
12049
  function fleetBusyRunEndedLine(run2, thread, err2) {
11967
12050
  if (typeof err2 !== "object" || err2 === null) return null;
@@ -12048,51 +12131,51 @@ function rolloutAdvanced(app, before, target) {
12048
12131
  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
12132
  };
12050
12133
  }
12051
- function judge(input) {
12134
+ function judge2(input) {
12052
12135
  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}`);
12136
+ if (!worker.ok) return worker.fatal ? { kind: "failed", reason: worker.reason } : waiting2(worker.reason);
12137
+ if (input.app === null) return waiting2("rollout: not read yet");
12138
+ if ("error" in input.app) return waiting2(`rollout: ${input.app.error}`);
12056
12139
  const app = input.app.value;
12057
12140
  const appVersion = app.version;
12058
12141
  let advancedFrom = null;
12059
12142
  if (input.target) {
12060
12143
  const advanced = rolloutAdvanced(app, input.before, input.target);
12061
- if (!advanced.ok) return waiting(advanced.reason);
12144
+ if (!advanced.ok) return waiting2(advanced.reason);
12062
12145
  advancedFrom = advanced.from;
12063
12146
  }
12064
- if (input.instances === null) return waiting("rollout: instances not read yet");
12065
- if ("error" in input.instances) return waiting(`rollout: ${input.instances.error}`);
12147
+ if (input.instances === null) return waiting2("rollout: instances not read yet");
12148
+ if ("error" in input.instances) return waiting2(`rollout: ${input.instances.error}`);
12066
12149
  const running = input.instances.value.filter((i) => i.state === "running");
12067
12150
  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
12151
  const stale = running.filter((i) => i.version !== appVersion);
12069
12152
  if (stale.length > 0) {
12070
12153
  const versions = [...new Set(stale.map((i) => i.version === null ? "?" : String(i.version)))].join("/");
12071
- return waiting(
12154
+ return waiting2(
12072
12155
  `rollout in progress \u2014 ${stale.length} of ${running.length} running instance(s) still on version ${versions}, app version ${appVersion}`
12073
12156
  );
12074
12157
  }
12075
- if (input.probe === null) return waiting("probe: not sent yet");
12076
- if ("error" in input.probe) return waiting(`probe: ${input.probe.error}`);
12158
+ if (input.probe === null) return waiting2("probe: not sent yet");
12159
+ if ("error" in input.probe) return waiting2(`probe: ${input.probe.error}`);
12077
12160
  const body = input.probe.body;
12078
12161
  if (body.reason === FLEET_BUSY_REASON)
12079
- return waiting("probe: fleet busy \u2014 no free instance for the probe thread (max_instances reached)");
12162
+ return waiting2("probe: fleet busy \u2014 no free instance for the probe thread (max_instances reached)");
12080
12163
  if (typeof body.error === "string" && CONTAINER_STARTING.test(body.error))
12081
- return waiting("probe: container starting");
12164
+ return waiting2("probe: container starting");
12082
12165
  if ("error" in body) {
12083
12166
  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`);
12167
+ return waiting2(`probe: ${failed} \u2014 the probe's container may still run the previous image`);
12085
12168
  }
12086
12169
  if (body.exitCode !== 0) {
12087
12170
  const stderr = typeof body.stderr === "string" ? body.stderr.trim() : "";
12088
- return waiting(`probe: \`${PROBE_COMMAND}\` exited ${String(body.exitCode)}${stderr ? `: ${stderr}` : ""}`);
12171
+ return waiting2(`probe: \`${PROBE_COMMAND}\` exited ${String(body.exitCode)}${stderr ? `: ${stderr}` : ""}`);
12089
12172
  }
12090
12173
  const stdout = typeof body.stdout === "string" ? body.stdout.trim() : "";
12091
- if (stdout !== "ok") return waiting(`probe: \`${PROBE_COMMAND}\` printed ${JSON.stringify(stdout)}`);
12174
+ if (stdout !== "ok") return waiting2(`probe: \`${PROBE_COMMAND}\` printed ${JSON.stringify(stdout)}`);
12092
12175
  const mine = input.instances.value.find((i) => i.name === input.probeThreadKey);
12093
- if (!mine) return waiting(`probe instance ${input.probeThreadKey} not listed yet`);
12176
+ if (!mine) return waiting2(`probe instance ${input.probeThreadKey} not listed yet`);
12094
12177
  if (mine.version !== appVersion)
12095
- return waiting(
12178
+ return waiting2(
12096
12179
  `probe instance ${input.probeThreadKey} is on version ${mine.version === null ? "?" : mine.version}, app version ${appVersion} \u2014 the probe landed on a previous image`
12097
12180
  );
12098
12181
  return {
@@ -12101,7 +12184,7 @@ function judge(input) {
12101
12184
  };
12102
12185
  }
12103
12186
  function decideSandboxLive(input, deadlineMs = LIVE_GATE_DEADLINE_MS) {
12104
- const d = judge(input);
12187
+ const d = judge2(input);
12105
12188
  if (d.kind !== "waiting" || input.elapsedMs < deadlineMs) return d;
12106
12189
  return {
12107
12190
  kind: "failed",
@@ -12175,7 +12258,7 @@ function parseExecStream(text) {
12175
12258
  return { error: `/exec answered no JSON document: ${JSON.stringify(text.trim().slice(0, 120))}` };
12176
12259
  }
12177
12260
  }
12178
- var PROBE_COMMAND, PROBE_TIMEOUT_MS, CONTAINER_STARTING, waiting, isRecord2, ANSI_SEQUENCE2, SECTION_GUTTER;
12261
+ var PROBE_COMMAND, PROBE_TIMEOUT_MS, CONTAINER_STARTING, waiting2, isRecord2, ANSI_SEQUENCE2, SECTION_GUTTER;
12179
12262
  var init_sandboxLiveGate = __esm({
12180
12263
  "../../src/deploy/sandboxLiveGate.ts"() {
12181
12264
  "use strict";
@@ -12184,7 +12267,7 @@ var init_sandboxLiveGate = __esm({
12184
12267
  PROBE_COMMAND = "echo ok";
12185
12268
  PROBE_TIMEOUT_MS = 6e4;
12186
12269
  CONTAINER_STARTING = /Container is starting/i;
12187
- waiting = (reason2) => ({ kind: "waiting", reason: reason2 });
12270
+ waiting2 = (reason2) => ({ kind: "waiting", reason: reason2 });
12188
12271
  isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
12189
12272
  ANSI_SEQUENCE2 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
12190
12273
  SECTION_GUTTER = /^[\s│├╭╰|]+/;
@@ -12510,26 +12593,6 @@ async function fetchHealthz(url) {
12510
12593
  const r = await readHealthz(url);
12511
12594
  return "body" in r ? r.body : void 0;
12512
12595
  }
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
12596
  async function wranglerJson(dir, args) {
12534
12597
  const r = await run("npx", ["wrangler", ...args], { cwd: workerDir(dir), unset: UNSET_ENV });
12535
12598
  if (r.code !== 0)
@@ -12550,6 +12613,29 @@ async function resolveContainerAppId(dir, containerApp) {
12550
12613
  containerAppIds.set(containerApp, id);
12551
12614
  return { value: id };
12552
12615
  }
12616
+ async function waitUntilBotLive(step, gate, expectedCommit, rollout, io, deps = defaultSandboxGateDeps) {
12617
+ const started = deps.now();
12618
+ for (; ; ) {
12619
+ const elapsed2 = deps.now() - started;
12620
+ const app = await deps.readAppState(step.dir, gate.containerApp);
12621
+ const health = await deps.readHealth(gate.healthUrl);
12622
+ const decision = decideBotLive({
12623
+ containerApp: gate.containerApp,
12624
+ health,
12625
+ app,
12626
+ before: rollout.before,
12627
+ target: rollout.target,
12628
+ expectedCommit,
12629
+ elapsedMs: elapsed2
12630
+ });
12631
+ if (decision.kind === "live") return { live: true, detail: decision.summary, waitedMs: deps.now() - started };
12632
+ if (decision.kind === "failed") return { live: false, reason: decision.reason };
12633
+ io.log(
12634
+ `[deploy:all] ${step.name}: deployed, not live yet \u2014 ${decision.reason} (${Math.floor(elapsed2 / 6e4)}m ${Math.floor(elapsed2 % 6e4 / 1e3)}s)`
12635
+ );
12636
+ await deps.sleep(LIVE_GATE_POLL_MS);
12637
+ }
12638
+ }
12553
12639
  async function waitUntilSandboxLive(step, gate, expectedCommit, rollout, io, deps = defaultSandboxGateDeps) {
12554
12640
  const bearer = deps.env[gate.bearerEnv];
12555
12641
  if (!bearer)
@@ -12590,7 +12676,7 @@ async function waitUntilSandboxLive(step, gate, expectedCommit, rollout, io, dep
12590
12676
  async function readAppBeforeUpload(step, gate, io, deps) {
12591
12677
  const before = await deps.readAppState(step.dir, gate.containerApp);
12592
12678
  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`
12679
+ "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
12680
  );
12595
12681
  return before;
12596
12682
  }
@@ -12666,8 +12752,7 @@ async function deployStepLoop(step, plan3, expectedCommit, io, deps, exec, root,
12666
12752
  for (; ; ) {
12667
12753
  io.log(`
12668
12754
  [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;
12755
+ const application = step.liveGate ? { before: await readAppBeforeUpload(step, step.liveGate, io, deps) } : void 0;
12671
12756
  const r = await exec(step, io);
12672
12757
  const outcome = classifyDeployOutput(r.code, r.output);
12673
12758
  if (outcome.kind === "deployed") {
@@ -12680,15 +12765,20 @@ async function deployStepLoop(step, plan3, expectedCommit, io, deps, exec, root,
12680
12765
  );
12681
12766
  const liveGate = step.liveGate;
12682
12767
  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);
12768
+ if (!application) throw new Error(`${step.name}: live gate has no application state`);
12769
+ const target = rolloutTargetFromDeployOutput(r.output);
12770
+ const from = "value" in application.before ? `version ${application.before.value.version}` : "its pre-deploy version";
12771
+ io.log(
12772
+ 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`
12773
+ );
12774
+ const g = liveGate.kind === "sandbox" ? await waitUntilSandboxLive(
12775
+ step,
12776
+ liveGate,
12777
+ expectedCommit,
12778
+ { before: application.before, target },
12779
+ io,
12780
+ deps
12781
+ ) : await waitUntilBotLive(step, liveGate, expectedCommit, { before: application.before, target }, io, deps);
12692
12782
  wait2.setAttrs(g.live ? { outcome: "live", waitedMs: g.waitedMs } : { outcome: "not_live" });
12693
12783
  return g;
12694
12784
  });
@@ -13105,6 +13195,7 @@ var init_run = __esm({
13105
13195
  init_planeDeploy();
13106
13196
  init_wranglerTemplate();
13107
13197
  init_supersede();
13198
+ init_botLiveGate();
13108
13199
  init_sandboxLiveGate();
13109
13200
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
13110
13201
  workerDir = (dir) => workPath(OPERATOR_ROOT, dir);
@@ -17364,7 +17455,7 @@ async function branchHeadSubject(repo, branch2) {
17364
17455
  if (typeof message4 !== "string" || message4.trim() === "") return void 0;
17365
17456
  return message4.split("\n", 1)[0].trim();
17366
17457
  }
17367
- async function headRefTipSha(repo, branch2, headers) {
17458
+ async function readHeadRef(repo, branch2, headers) {
17368
17459
  let res;
17369
17460
  try {
17370
17461
  res = await fetch(`https://api.github.com/repos/${repo}/git/ref/heads/${encodeGithubRef(branch2)}`, {
@@ -17372,13 +17463,17 @@ async function headRefTipSha(repo, branch2, headers) {
17372
17463
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
17373
17464
  });
17374
17465
  } catch {
17375
- return void 0;
17466
+ return { kind: "unverified" };
17376
17467
  }
17377
- if (!res.ok) return void 0;
17468
+ if (res.status === 404) return { kind: "missing" };
17469
+ if (!res.ok) return { kind: "unverified" };
17378
17470
  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;
17471
+ const sha = data?.object?.sha;
17472
+ return data?.object?.type === "commit" && typeof sha === "string" && /^[0-9a-f]{40}$/.test(sha) ? { kind: "verified", sha } : { kind: "unverified" };
17473
+ }
17474
+ async function headRefTipSha(repo, branch2, headers) {
17475
+ const read = await readHeadRef(repo, branch2, headers);
17476
+ return read.kind === "verified" ? read.sha : void 0;
17382
17477
  }
17383
17478
  function preferRefTip(where, prSha, refTip, branch2) {
17384
17479
  if (refTip === void 0) return prSha;
@@ -17428,8 +17523,11 @@ async function fetchPullRequestFacts(pr) {
17428
17523
  const prSha = typeof data.head?.sha === "string" && /^[0-9a-f]{40}$/.test(data.head.sha) ? data.head.sha : void 0;
17429
17524
  const sameRepoHead = headRepo === pr.repo.toLowerCase();
17430
17525
  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;
17526
+ const headRead = sameRepoHead && headRef !== void 0 ? await readHeadRef(pr.repo, headRef, headers) : void 0;
17527
+ const headBranchExists = headRead?.kind === "verified" ? true : headRead?.kind === "missing" ? false : void 0;
17528
+ const refTip = headRead?.kind === "verified" ? headRead.sha : void 0;
17529
+ const sha = sameRepoHead && headRef !== void 0 ? preferRefTip(`${pr.repo}#${pr.number}`, prSha, refTip, headRef) : prSha;
17530
+ const verifiedHead = sameRepoHead && headRef !== void 0 && prSha !== void 0 && refTip === prSha ? { repo: pr.repo, ref: headRef, sha: refTip } : void 0;
17433
17531
  let closedBy = typeof data.closed_by?.login === "string" ? data.closed_by.login : void 0;
17434
17532
  if (data.state === "closed" && !(typeof data.merged_at === "string" && data.merged_at) && closedBy === void 0) {
17435
17533
  try {
@@ -17454,6 +17552,7 @@ async function fetchPullRequestFacts(pr) {
17454
17552
  } : {},
17455
17553
  ...headRef !== void 0 ? { headRef } : {},
17456
17554
  ...sha ? { headSha: sha } : {},
17555
+ ...verifiedHead !== void 0 ? { verifiedHead } : {},
17457
17556
  ...headBranchExists !== void 0 ? { headBranchExists } : {},
17458
17557
  sameRepoHead,
17459
17558
  ...typeof data.base?.ref === "string" && data.base.ref ? { baseRef: data.base.ref } : {},
@@ -21081,8 +21180,8 @@ function pageEvents(events, opts) {
21081
21180
  if (page2.length < rest.length && page2.length > 0) out.nextAfterSeq = page2[page2.length - 1].seq;
21082
21181
  return out;
21083
21182
  }
21084
- function toListItem(record2, bytes) {
21085
- const { events: _events, ...rest } = record2;
21183
+ function toListItem(record3, bytes) {
21184
+ const { events: _events, ...rest } = record3;
21086
21185
  return { ...rest, bytes };
21087
21186
  }
21088
21187
  function retentionPolicyOf(cfg) {
@@ -21233,19 +21332,19 @@ var init_runStore = __esm({
21233
21332
  }
21234
21333
  abandoned() {
21235
21334
  }
21236
- async put(record2) {
21237
- if (!isValidRunId(record2.id))
21335
+ async put(record3) {
21336
+ if (!isValidRunId(record3.id))
21238
21337
  return { ok: true, retained: this.readIndex().length, stored: false, rewritten: false };
21239
21338
  this.ensureDir();
21240
- const content = JSON.stringify(record2);
21339
+ const content = JSON.stringify(record3);
21241
21340
  const bytes = utf8ByteLength(content);
21242
21341
  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 };
21342
+ const prev = index.find((r) => r.id === record3.id);
21343
+ const rewritten = prev !== void 0 && !sameStoredVersion(prev, { ...record3, bytes });
21344
+ this.writeAtomic(this.recordPath(record3.id), content);
21345
+ const item = toListItem(record3, bytes);
21346
+ const kept = this.compact([...index.filter((r) => r.id !== record3.id), item]);
21347
+ return { ok: true, retained: kept.length, stored: kept.some((r) => r.id === record3.id), rewritten };
21249
21348
  }
21250
21349
  /** Retention is decided from the index alone (no per-file stat); the one
21251
21350
  * file that matters is then read directly — a torn or missing file lands
@@ -21272,16 +21371,16 @@ var init_runStore = __esm({
21272
21371
  return selectListItems(this.retainedIntact(), opts);
21273
21372
  }
21274
21373
  async events(id, opts) {
21275
- const record2 = await this.get(id);
21276
- return record2 ? pageEvents(record2.events, opts) : null;
21374
+ const record3 = await this.get(id);
21375
+ return record3 ? pageEvents(record3.events, opts) : null;
21277
21376
  }
21278
21377
  async usage(query) {
21279
21378
  const items = this.retainedIntact();
21280
21379
  const records = [];
21281
21380
  for (const item of items) {
21282
21381
  if (item.finishedAt < query.sinceMs || item.finishedAt >= query.untilMs) continue;
21283
- const record2 = await this.get(item.id);
21284
- if (record2) records.push(record2);
21382
+ const record3 = await this.get(item.id);
21383
+ if (record3) records.push(record3);
21285
21384
  }
21286
21385
  const parent = (id) => {
21287
21386
  const p = items.find((i) => i.id === id);
@@ -21999,24 +22098,24 @@ function predicateWith(rules, actor, action, resourceType, kind) {
21999
22098
  function predicateFor(actor, action, resourceType, kind) {
22000
22099
  return predicateWith(POLICY, actor, action, resourceType, kind);
22001
22100
  }
22002
- function matchesPredicate(predicate, record2) {
22101
+ function matchesPredicate(predicate, record3) {
22003
22102
  switch (predicate.kind) {
22004
22103
  case "none":
22005
22104
  return false;
22006
22105
  case "all":
22007
22106
  return true;
22008
22107
  case "channels-in":
22009
- return record2.channelId !== void 0 && holds(predicate.channelIds, record2.channelId);
22108
+ return record3.channelId !== void 0 && holds(predicate.channelIds, record3.channelId);
22010
22109
  case "user-is":
22011
- return record2.userId !== void 0 && record2.userId === predicate.userId;
22110
+ return record3.userId !== void 0 && record3.userId === predicate.userId;
22012
22111
  case "repos-in":
22013
- return record2.repo !== void 0 && holds(predicate.repos, record2.repo);
22112
+ return record3.repo !== void 0 && holds(predicate.repos, record3.repo);
22014
22113
  case "visibility-in":
22015
- return predicate.visibilities.has(record2.channelVisibility ?? "unknown");
22114
+ return predicate.visibilities.has(record3.channelVisibility ?? "unknown");
22016
22115
  case "or":
22017
- return predicate.of.some((p) => matchesPredicate(p, record2));
22116
+ return predicate.of.some((p) => matchesPredicate(p, record3));
22018
22117
  case "and":
22019
- return predicate.of.length > 0 && predicate.of.every((p) => matchesPredicate(p, record2));
22118
+ return predicate.of.length > 0 && predicate.of.every((p) => matchesPredicate(p, record3));
22020
22119
  }
22021
22120
  return false;
22022
22121
  }
@@ -25602,9 +25701,9 @@ function endingCauseWords(cause) {
25602
25701
  function isReissueSteerText(text) {
25603
25702
  return /^the model provider .+ is answering again — re-issue the held turn and continue$/.test(text.trim());
25604
25703
  }
25605
- function waitingWords(waiting2) {
25606
- if (waiting2.length === 0) return "its turn";
25607
- return waiting2.map(
25704
+ function waitingWords(waiting3) {
25705
+ if (waiting3.length === 0) return "its turn";
25706
+ return waiting3.map(
25608
25707
  (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
25708
  ).join(", then ");
25610
25709
  }
@@ -28876,23 +28975,23 @@ var init_turnEffort = __esm({
28876
28975
  function tokenize2(text) {
28877
28976
  return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
28878
28977
  }
28879
- function keywordMatch(record2, query) {
28978
+ function keywordMatch(record3, query) {
28880
28979
  const queryTokens = typeof query === "string" ? tokenize2(query) : query;
28881
28980
  if (queryTokens.length === 0) return 0;
28882
- const recordTokens = /* @__PURE__ */ new Set([...tokenize2(record2.keywords.join(" ")), ...tokenize2(record2.text)]);
28981
+ const recordTokens = /* @__PURE__ */ new Set([...tokenize2(record3.keywords.join(" ")), ...tokenize2(record3.text)]);
28883
28982
  let hits = 0;
28884
28983
  for (const token2 of queryTokens) {
28885
28984
  if (recordTokens.has(token2)) hits++;
28886
28985
  }
28887
28986
  return hits / queryTokens.length;
28888
28987
  }
28889
- function recencyScore(record2, now, tau = RECENCY_TAU_MS) {
28890
- const ts = record2.lastUsedAt ?? record2.createdAt;
28988
+ function recencyScore(record3, now, tau = RECENCY_TAU_MS) {
28989
+ const ts = record3.lastUsedAt ?? record3.createdAt;
28891
28990
  const age = Math.max(0, now - ts);
28892
28991
  return Math.exp(-age / tau);
28893
28992
  }
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);
28993
+ function scoreRecord(record3, query, now, weights = DEFAULT_WEIGHTS, tau = RECENCY_TAU_MS) {
28994
+ return weights.keyword * keywordMatch(record3, query) + weights.recency * recencyScore(record3, now, tau);
28896
28995
  }
28897
28996
  function estimateTokens(text) {
28898
28997
  return Math.ceil(text.length / 4);
@@ -28900,11 +28999,11 @@ function estimateTokens(text) {
28900
28999
  function applyBudget(records, budget) {
28901
29000
  const out = [];
28902
29001
  let tokens = 0;
28903
- for (const record2 of records) {
29002
+ for (const record3 of records) {
28904
29003
  if (out.length >= budget.maxRecords) break;
28905
- const cost = estimateTokens(renderMemoryBullet(record2));
29004
+ const cost = estimateTokens(renderMemoryBullet(record3));
28906
29005
  if (out.length > 0 && tokens + cost > budget.maxTokens) break;
28907
- out.push(record2);
29006
+ out.push(record3);
28908
29007
  tokens += cost;
28909
29008
  }
28910
29009
  return out;
@@ -28915,8 +29014,8 @@ function memoryBlockPrefix(resource) {
28915
29014
  function sanitizeMemoryField(s) {
28916
29015
  return s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, " ").replace(/\s+/g, " ").trim().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
28917
29016
  }
28918
- function renderMemoryBullet(record2) {
28919
- return `- ${sanitizeMemoryField(record2.text)} (source: ${sanitizeMemoryField(record2.sourceThreadKey)})`;
29017
+ function renderMemoryBullet(record3) {
29018
+ return `- ${sanitizeMemoryField(record3.text)} (source: ${sanitizeMemoryField(record3.sourceThreadKey)})`;
28920
29019
  }
28921
29020
  function renderMemoryBlock(resource, records) {
28922
29021
  const bullets = records.map(renderMemoryBullet);
@@ -29502,7 +29601,7 @@ function parseConfirmation(v) {
29502
29601
  function isRefusal(v) {
29503
29602
  return typeof v === "string" && CONFIRMATION_REFUSALS.includes(v);
29504
29603
  }
29505
- function judge2(rows, id, actorIds, now, kind) {
29604
+ function judge3(rows, id, actorIds, now, kind) {
29506
29605
  const row2 = rows.get(id);
29507
29606
  if (!row2) return { ok: false, refused: "used" };
29508
29607
  if (kind === "consume" && row2.expiresAt <= now) {
@@ -29572,13 +29671,13 @@ var init_confirmations = __esm({
29572
29671
  }
29573
29672
  async consume(id, actorIds) {
29574
29673
  const rows = this.read();
29575
- const out = judge2(rows, id, actorIds, this.clock(), "consume");
29674
+ const out = judge3(rows, id, actorIds, this.clock(), "consume");
29576
29675
  this.write(rows);
29577
29676
  return out;
29578
29677
  }
29579
29678
  async cancel(id, actorIds) {
29580
29679
  const rows = this.read();
29581
- const out = judge2(rows, id, actorIds, this.clock(), "cancel");
29680
+ const out = judge3(rows, id, actorIds, this.clock(), "cancel");
29582
29681
  this.write(rows);
29583
29682
  return out;
29584
29683
  }
@@ -29586,7 +29685,7 @@ var init_confirmations = __esm({
29586
29685
  const rows = this.read();
29587
29686
  const id = threadRowId(rows, threadKey);
29588
29687
  if (id === void 0) return { ok: false, refused: "used" };
29589
- const out = judge2(rows, id, actorIds, this.clock(), "cancel");
29688
+ const out = judge3(rows, id, actorIds, this.clock(), "cancel");
29590
29689
  this.write(rows);
29591
29690
  return out;
29592
29691
  }
@@ -30067,16 +30166,16 @@ async function reflect(deps) {
30067
30166
  dropped.push(`${placement.from} (${placement.reason})`);
30068
30167
  continue;
30069
30168
  }
30070
- let record2 = plain7;
30169
+ let record3 = plain7;
30071
30170
  if (placement.narrowed) {
30072
- const { supersedes: _supersedes, restates: _restates, ...withoutPointers } = record2;
30073
- record2 = withoutPointers;
30171
+ const { supersedes: _supersedes, restates: _restates, ...withoutPointers } = record3;
30172
+ record3 = withoutPointers;
30074
30173
  const line4 = `${placement.narrowed.from} \u2192 ${placement.target.kind} (${placement.narrowed.reason})`;
30075
30174
  narrowed.set(line4, (narrowed.get(line4) ?? 0) + 1);
30076
30175
  }
30077
30176
  let batch = byScope.get(placement.target.key);
30078
30177
  if (!batch) byScope.set(placement.target.key, batch = []);
30079
- batch.push(record2);
30178
+ batch.push(record3);
30080
30179
  }
30081
30180
  if (narrowed.size > 0)
30082
30181
  warn(`write narrowed by policy: ${[...narrowed].map(([line4, n2]) => `${n2}\xD7 ${line4}`).join(", ")}`);
@@ -31907,8 +32006,8 @@ ${line4}`
31907
32006
  const list = await call(() => api.residents());
31908
32007
  if (list.status !== 200) throw residentFailure(list);
31909
32008
  const residents = list.data.residents ?? [];
31910
- const record2 = residents.find((rec2) => rec2.resource === repoResourceId(args.slug));
31911
- if (!record2) {
32009
+ const record3 = residents.find((rec2) => rec2.resource === repoResourceId(args.slug));
32010
+ if (!record3) {
31912
32011
  const slugs = residents.map(
31913
32012
  (rec2) => String(rec2.resource ?? "").replace(/^repo:/, "").toLowerCase()
31914
32013
  ).filter(Boolean);
@@ -31928,7 +32027,7 @@ ${line4}`
31928
32027
  ${line4}` : ""}`
31929
32028
  );
31930
32029
  }
31931
- body.commands = { ...obj(record2.commands), ...commands };
32030
+ body.commands = { ...obj(record3.commands), ...commands };
31932
32031
  }
31933
32032
  if (options.ref !== void 0) body.defaultRef = options.ref;
31934
32033
  const r = await call(() => api.reconfigure(body));
@@ -31953,11 +32052,11 @@ ${line4}` : ""}`
31953
32052
 
31954
32053
  // ../../src/core/reviewAbridge.ts
31955
32054
  import { resolve as resolve10 } from "node:path";
31956
- function meatArtifactOf(record2) {
31957
- return record2.events.find((e) => isReadingDiff(e) && e.poweredBy === "meat");
32055
+ function meatArtifactOf(record3) {
32056
+ return record3.events.find((e) => isReadingDiff(e) && e.poweredBy === "meat");
31958
32057
  }
31959
- function gitArtifactOf(record2) {
31960
- return record2.events.find((e) => isReadingDiff(e) && e.poweredBy === "git");
32058
+ function gitArtifactOf(record3) {
32059
+ return record3.events.find((e) => isReadingDiff(e) && e.poweredBy === "git");
31961
32060
  }
31962
32061
  function summarize(a) {
31963
32062
  return {
@@ -31971,16 +32070,16 @@ function summarize(a) {
31971
32070
  };
31972
32071
  }
31973
32072
  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;
32073
+ const record3 = await store.get(runId3);
32074
+ if (!record3) throw new Error("the run's record is gone \u2014 nothing to append to");
32075
+ const kept = record3.events.filter((e) => !(isReadingDiff(e) && e.poweredBy === "meat"));
32076
+ const replaced = kept.length !== record3.events.length;
32077
+ const seq = Math.max(0, ...storedEventSeqs(record3.events), ...record3.events.map((e) => e.seq ?? 0)) + 1;
31979
32078
  const event = { type: "review_artifact", artifact: "reading_diff", ...artifact, at, seq };
31980
32079
  const next = fitRecordToBudget({
31981
- ...record2,
32080
+ ...record3,
31982
32081
  events: [...kept, event],
31983
- eventCount: record2.eventCount + (replaced ? 0 : 1),
32082
+ eventCount: record3.eventCount + (replaced ? 0 : 1),
31984
32083
  storedEventCount: kept.length + 1
31985
32084
  });
31986
32085
  if (!next.events.includes(event))
@@ -32069,19 +32168,19 @@ var init_reviewAbridge = __esm({
32069
32168
  async decide(req) {
32070
32169
  const job = this.jobs.get(req.runId);
32071
32170
  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);
32171
+ const record3 = await this.deps.store.get(req.runId);
32172
+ if (!record3) throw new AbridgeRefusal("not_found", "run not found");
32173
+ const existing = meatArtifactOf(record3);
32075
32174
  if (existing && !req.force) return { state: "done", reused: true, artifact: summarize(existing) };
32076
32175
  const failed = this.failures.get(req.runId);
32077
32176
  if (failed && !req.force) return { state: "failed", ...failed };
32078
- const git2 = gitArtifactOf(record2);
32177
+ const git2 = gitArtifactOf(record3);
32079
32178
  if (!git2)
32080
32179
  throw new AbridgeRefusal("conflict", `run ${req.runId} carries no reading diff \u2014 only PR review runs record one`);
32081
32180
  const startedAt = this.deps.clock();
32082
32181
  const model = req.model ?? this.deps.defaultModel();
32083
32182
  this.failures.delete(req.runId);
32084
- const done = this.produce(record2, git2, model).finally(() => this.jobs.delete(req.runId));
32183
+ const done = this.produce(record3, git2, model).finally(() => this.jobs.delete(req.runId));
32085
32184
  this.jobs.set(req.runId, { startedAt, done });
32086
32185
  return { state: "running", startedAt };
32087
32186
  }
@@ -32091,8 +32190,8 @@ var init_reviewAbridge = __esm({
32091
32190
  if (job) return { state: "running", startedAt: job.startedAt };
32092
32191
  const failed = this.failures.get(runId3);
32093
32192
  if (failed) return { state: "failed", ...failed };
32094
- const record2 = await this.deps.store.get(runId3);
32095
- const existing = record2 ? meatArtifactOf(record2) : void 0;
32193
+ const record3 = await this.deps.store.get(runId3);
32194
+ const existing = record3 ? meatArtifactOf(record3) : void 0;
32096
32195
  return existing ? { state: "done", reused: true, artifact: summarize(existing) } : { state: "absent" };
32097
32196
  }
32098
32197
  /** The running job's final state, or the current state when none runs. */
@@ -32123,10 +32222,10 @@ var init_reviewAbridge = __esm({
32123
32222
  if (!(err2 instanceof AbridgeRefusal)) this.deps.warn(`[reading-diff] ${runId3} abridge failed: ${describe(err2)}`);
32124
32223
  }
32125
32224
  }
32126
- async produce(record2, git2, model) {
32225
+ async produce(record3, git2, model) {
32127
32226
  const at = () => this.deps.clock();
32128
32227
  try {
32129
- const input = await this.input(record2, git2);
32228
+ const input = await this.input(record3, git2);
32130
32229
  const meat = await this.deps.meat({ diff: input.diff, model, timeoutMs: this.deps.timeoutMs() });
32131
32230
  if (!meat.ok) throw new Error(meat.reason);
32132
32231
  const capped4 = capDiff(sanitizeArtifactText(meat.result.diff));
@@ -32142,12 +32241,12 @@ var init_reviewAbridge = __esm({
32142
32241
  inputBytes: input.bytes,
32143
32242
  model
32144
32243
  };
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) });
32244
+ const stored = await appendReviewArtifact(this.deps.store, record3.id, artifact, at());
32245
+ return this.conclude(record3.id, { state: "done", reused: false, artifact: summarize(stored) });
32147
32246
  } catch (err2) {
32148
32247
  const failed = { reason: redactSecrets(describe(err2)), at: at() };
32149
- this.failures.set(record2.id, failed);
32150
- return this.conclude(record2.id, { state: "failed", ...failed });
32248
+ this.failures.set(record3.id, failed);
32249
+ return this.conclude(record3.id, { state: "failed", ...failed });
32151
32250
  }
32152
32251
  }
32153
32252
  conclude(runId3, state) {
@@ -32156,8 +32255,8 @@ var init_reviewAbridge = __esm({
32156
32255
  }
32157
32256
  /** The complete diff: GitHub's compare of base...head first; the recorded
32158
32257
  * 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");
32258
+ async input(record3, git2) {
32259
+ const meta = record3.events.find((e) => e.type === "run_meta");
32161
32260
  let compareFailure;
32162
32261
  if (!meta?.repo || !meta.headSha) compareFailure = "the run records no repo and head to compare";
32163
32262
  else {
@@ -32176,7 +32275,7 @@ var init_reviewAbridge = __esm({
32176
32275
  }
32177
32276
  }
32178
32277
  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}`);
32278
+ this.deps.warn(`[reading-diff] ${record3.id} using the recorded diff: ${compareFailure}`);
32180
32279
  return { diff: git2.diff, source: "recorded", bytes: Buffer.byteLength(git2.diff) };
32181
32280
  }
32182
32281
  };
@@ -32882,18 +32981,20 @@ function createRunsService(deps) {
32882
32981
  try {
32883
32982
  const row2 = await ledger.planeQueued(id);
32884
32983
  if (!row2) return null;
32984
+ const stampedChannel = row2.request.channelId;
32885
32985
  const channelAt = row2.threadKey.lastIndexOf(":");
32886
- const waiting2 = waitingWords(row2.conditions);
32986
+ const channelId = typeof stampedChannel === "string" ? stampedChannel : channelAt > 0 ? row2.threadKey.slice(0, channelAt) : void 0;
32987
+ const waiting3 = waitingWords(row2.conditions);
32887
32988
  return {
32888
32989
  id: row2.runId,
32889
- ...channelAt > 0 ? { channelId: row2.threadKey.slice(0, channelAt) } : {},
32990
+ ...channelId !== void 0 ? { channelId } : {},
32890
32991
  userId: row2.requester,
32891
32992
  threadKey: row2.threadKey,
32892
32993
  startedAt: row2.queuedAt,
32893
32994
  finished: false,
32894
32995
  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"
32996
+ queued: { state: row2.state, position: row2.position, waiting: waiting3 },
32997
+ activity: row2.state === "waiting" ? `queued at position ${row2.position} \u2014 waiting on ${waiting3}` : row2.state === "admitted" ? "admitted \u2014 starting" : "withdrawn"
32897
32998
  };
32898
32999
  } catch (err2) {
32899
33000
  warn(`[runs] plane queued read failed for ${id}: ${describe13(err2)}`);
@@ -32978,7 +33079,7 @@ function createRunsService(deps) {
32978
33079
  if (row2) {
32979
33080
  const finishedAt = snap?.finishedAt ?? at;
32980
33081
  const m = row2.meta;
32981
- const record2 = assembleRunRecord({
33082
+ const record3 = assembleRunRecord({
32982
33083
  run: { id, ...summary?.label !== void 0 ? { label: summary.label } : {} },
32983
33084
  snap,
32984
33085
  agent: m.agent ?? "ship",
@@ -33006,7 +33107,7 @@ function createRunsService(deps) {
33006
33107
  }),
33007
33108
  seal
33008
33109
  });
33009
- const done = await ledger.finish(id, row2.ownerGen, record2);
33110
+ const done = await ledger.finish(id, row2.ownerGen, record3);
33010
33111
  if (!done.ok) warn(`[runs] run ledger finish refused for ${id} (${done.reason ?? "unknown"})`);
33011
33112
  }
33012
33113
  } catch (err2) {
@@ -33104,12 +33205,12 @@ function createRunsService(deps) {
33104
33205
  const queued = await queuedRun(id);
33105
33206
  return queued ? { ok: true, value: queued } : notFound;
33106
33207
  }
33107
- const record2 = await storeGet(id);
33108
- if (!record2) {
33208
+ const record3 = await storeGet(id);
33209
+ if (!record3) {
33109
33210
  const queued = await queuedRun(id);
33110
33211
  return queued ? { ok: true, value: queued } : notFound;
33111
33212
  }
33112
- const { events, ...rest } = record2;
33213
+ const { events, ...rest } = record3;
33113
33214
  const view = persistedView(rest, prices);
33114
33215
  view.events = events;
33115
33216
  return { ok: true, value: view };
@@ -34990,11 +35091,11 @@ var init_runLedgerWorker = __esm({
34990
35091
  this.checkIds(runId3, gen);
34991
35092
  return this.writeTurns(runId3, gen, turns, session);
34992
35093
  }
34993
- async step(runId3, gen, record2, turns, session) {
35094
+ async step(runId3, gen, record3, turns, session) {
34994
35095
  this.checkIds(runId3, gen);
34995
35096
  const written = await this.writeTurns(runId3, gen, turns, session);
34996
35097
  if (!written.ok) return written;
34997
- return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record2 }));
35098
+ return this.fenceResult(await this.post("/runs/step", { storeKey: this.opts.storeKey, runId: runId3, gen, record: record3 }));
34998
35099
  }
34999
35100
  checkSessionKey(key) {
35000
35101
  if (!SESSION_KEY_PATTERN.test(key))
@@ -35165,20 +35266,20 @@ var init_runLedgerWorker = __esm({
35165
35266
  this.checkIds(runId3, gen);
35166
35267
  return this.fenceResult(await this.post("/runs/finishing", { storeKey: this.opts.storeKey, runId: runId3, gen }));
35167
35268
  }
35168
- async finish(runId3, gen, record2) {
35269
+ async finish(runId3, gen, record3) {
35169
35270
  this.checkIds(runId3, gen);
35170
- const point = pointOf(record2, this.opts.prices);
35271
+ const point = pointOf(record3, this.opts.prices);
35171
35272
  const r = await this.post("/runs/finish", {
35172
35273
  storeKey: this.opts.storeKey,
35173
35274
  runId: runId3,
35174
35275
  gen,
35175
- record: record2,
35276
+ record: record3,
35176
35277
  ...point !== void 0 ? { point } : {}
35177
35278
  });
35178
35279
  const f = this.fenceResult(r);
35179
35280
  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(() => {
35281
+ if (record3.session)
35282
+ await this.post("/runs/session/clear-owner", { key: record3.session.key, runId: runId3, gen }).catch(() => {
35182
35283
  });
35183
35284
  return { ok: true, stored: r.data.stored === true };
35184
35285
  }
@@ -35877,6 +35978,19 @@ function runPageLink(id) {
35877
35978
  if (!base) return void 0;
35878
35979
  return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
35879
35980
  }
35981
+ function threadPageLink(threadKey, publicBaseUrl = process.env.PUBLIC_BASE_URL) {
35982
+ if (!publicBaseUrl?.trim()) return void 0;
35983
+ try {
35984
+ const base = new URL(publicBaseUrl.trim());
35985
+ if (!["https:", "http:"].includes(base.protocol) || base.username || base.password) return void 0;
35986
+ base.search = "";
35987
+ base.hash = "";
35988
+ base.pathname = `${base.pathname.replace(/\/+$/, "")}/threads/${encodeURIComponent(threadKey).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`;
35989
+ return base.href;
35990
+ } catch {
35991
+ return void 0;
35992
+ }
35993
+ }
35880
35994
  function artifactLink(id, key, token2) {
35881
35995
  const page2 = runPageLink(id);
35882
35996
  if (!page2) return void 0;
@@ -36554,9 +36668,9 @@ function recordPrOf(ctx) {
36554
36668
  }
36555
36669
  function inheritedPr(thread, records, repo) {
36556
36670
  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;
36671
+ const record3 = records?.pr && records.pr.repo === repo ? { repo, number: records.pr.number, source: "record", at: records.pr.at } : void 0;
36672
+ if (!turn || !record3) return turn ?? record3;
36673
+ return turn.at !== void 0 && record3.at !== void 0 && turn.at > record3.at ? turn : record3;
36560
36674
  }
36561
36675
  async function openPrHeadSha(pr) {
36562
36676
  const head = await prHead(pr).catch(() => void 0);
@@ -37164,7 +37278,7 @@ async function classifyMove(fetchPrCommits, q) {
37164
37278
  async function runReviewPostStep(input) {
37165
37279
  const { agent, repoCtx, verdict, carried, logKey } = input;
37166
37280
  const { reviewHead, observedHead } = input.heads;
37167
- const record2 = (outcome) => {
37281
+ const record3 = (outcome) => {
37168
37282
  if (input.publish && agent.name === "review" && !input.hardStopped) {
37169
37283
  const where = outcome.posted ? void 0 : repoCtx.repo && repoCtx.pr ? `${repoCtx.repo}#${repoCtx.pr}` : void 0;
37170
37284
  input.publish(
@@ -37263,7 +37377,7 @@ ${carriedFooter(carried)}` : rendered;
37263
37377
  console.error(`[review-post] ${logKey} failed for ${where}: ${reason2}`);
37264
37378
  await input.reply(`\u2139\uFE0F Review not posted to ${where}: ${reason2} \u2014 this verdict is Slack-only.`).catch(() => {
37265
37379
  });
37266
- return record2({ posted: false, reason: reason2 });
37380
+ return record3({ posted: false, reason: reason2 });
37267
37381
  }
37268
37382
  console.log(
37269
37383
  `[review-post] ${logKey} \u2192 ${where} (${verdict?.verdict ?? "no verdict"})${carried ? ` carried ${carried.reviewed.slice(0, 7)} \u2192 ${pinned.slice(0, 7)}` : ""}`
@@ -37279,14 +37393,14 @@ ${carriedFooter(carried)}` : rendered;
37279
37393
  await input.reply(moved).catch(() => {
37280
37394
  });
37281
37395
  }
37282
- return record2({
37396
+ return record3({
37283
37397
  posted: true,
37284
37398
  target: { repo: postTarget.repo, number: postTarget.number },
37285
37399
  head: pinned,
37286
37400
  ...verdict !== void 0 ? { verdict: verdict.verdict } : {}
37287
37401
  });
37288
37402
  }
37289
- return record2({ posted: false, reason: skipReason });
37403
+ return record3({ posted: false, reason: skipReason });
37290
37404
  }
37291
37405
  var init_reviewRound = __esm({
37292
37406
  "../../src/core/reviewRound.ts"() {
@@ -37643,11 +37757,11 @@ var init_authorize2 = __esm({
37643
37757
  });
37644
37758
 
37645
37759
  // ../../src/core/runHistoryWriter.ts
37646
- async function putOnce(sink, record2) {
37760
+ async function putOnce(sink, record3) {
37647
37761
  try {
37648
- return await sink.put(record2);
37762
+ return await sink.put(record3);
37649
37763
  } catch (err2) {
37650
- sink.abandoned(record2, describe2(err2));
37764
+ sink.abandoned(record3, describe2(err2));
37651
37765
  throw err2;
37652
37766
  }
37653
37767
  }
@@ -37671,28 +37785,28 @@ function createRunHistoryWriter(opts) {
37671
37785
  opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe2(err2)}`);
37672
37786
  }
37673
37787
  };
37674
- const attemptAll = async (record2, flag2, sink, span) => {
37788
+ const attemptAll = async (record3, flag2, sink, span) => {
37675
37789
  const attempts = RUN_HISTORY_RETRY_DELAYS_MS.length + 1;
37676
37790
  const giveUp = async (why) => {
37677
37791
  failures++;
37678
37792
  try {
37679
- sink.abandoned(record2, why);
37793
+ sink.abandoned(record3, why);
37680
37794
  } catch (err2) {
37681
- opts.warn(`[run-history] abandoned hook failed for ${record2.id}: ${describe2(err2)}`);
37795
+ opts.warn(`[run-history] abandoned hook failed for ${record3.id}: ${describe2(err2)}`);
37682
37796
  }
37683
37797
  if (sink === opts.store || flag2?.superseded) return;
37684
37798
  try {
37685
- await opts.store.put(record2, span ? { span } : void 0);
37686
- if (!flag2) persisted(record2.id);
37799
+ await opts.store.put(record3, span ? { span } : void 0);
37800
+ if (!flag2) persisted(record3.id);
37687
37801
  } catch (err2) {
37688
- opts.warn(`[run-history] ${record2.id} store fallback after give-up failed: ${describe2(err2)}`);
37802
+ opts.warn(`[run-history] ${record3.id} store fallback after give-up failed: ${describe2(err2)}`);
37689
37803
  }
37690
37804
  };
37691
37805
  for (let attempt = 1; ; attempt++) {
37692
37806
  if (flag2?.superseded) return;
37693
37807
  try {
37694
- await sink.put(record2, span ? { span } : void 0);
37695
- if (!flag2) persisted(record2.id);
37808
+ await sink.put(record3, span ? { span } : void 0);
37809
+ if (!flag2) persisted(record3.id);
37696
37810
  return;
37697
37811
  } catch (err2) {
37698
37812
  if (err2 instanceof RouteMissingError) {
@@ -37705,12 +37819,12 @@ function createRunHistoryWriter(opts) {
37705
37819
  return;
37706
37820
  }
37707
37821
  if (err2 instanceof PermanentStoreError) {
37708
- opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe2(err2)}`);
37822
+ opts.warn(`[run-history] ${record3.id} not persisted (permanent, not retried): ${describe2(err2)}`);
37709
37823
  await giveUp(describe2(err2));
37710
37824
  return;
37711
37825
  }
37712
37826
  if (attempt >= attempts) {
37713
- opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe2(err2)}`);
37827
+ opts.warn(`[run-history] ${record3.id} not persisted after ${attempts} attempts: ${describe2(err2)}`);
37714
37828
  await giveUp(`not persisted after ${attempts} attempts: ${describe2(err2)}`);
37715
37829
  return;
37716
37830
  }
@@ -37719,24 +37833,24 @@ function createRunHistoryWriter(opts) {
37719
37833
  }
37720
37834
  };
37721
37835
  return {
37722
- write(record2, writeOpts) {
37836
+ write(record3, writeOpts) {
37723
37837
  let flag2;
37724
37838
  if (writeOpts?.provisional === true) {
37725
- if (finals.has(record2.id)) return;
37726
- flag2 = { id: record2.id, superseded: false };
37839
+ if (finals.has(record3.id)) return;
37840
+ flag2 = { id: record3.id, superseded: false };
37727
37841
  provisionalFlags.add(flag2);
37728
37842
  } else {
37729
- finals.add(record2.id);
37730
- for (const f of provisionalFlags) if (f.id === record2.id) f.superseded = true;
37843
+ finals.add(record3.id);
37844
+ for (const f of provisionalFlags) if (f.id === record3.id) f.superseded = true;
37731
37845
  }
37732
37846
  const sink = writeOpts?.via ?? opts.store;
37733
- const p = attemptAll(record2, flag2, sink, writeOpts?.span).catch((err2) => {
37847
+ const p = attemptAll(record3, flag2, sink, writeOpts?.span).catch((err2) => {
37734
37848
  failures++;
37735
- opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe2(err2)}`);
37849
+ opts.warn(`[run-history] ${record3.id} writer failed unexpectedly: ${describe2(err2)}`);
37736
37850
  try {
37737
- sink.abandoned(record2, `writer failed unexpectedly: ${describe2(err2)}`);
37851
+ sink.abandoned(record3, `writer failed unexpectedly: ${describe2(err2)}`);
37738
37852
  } catch (hookErr) {
37739
- opts.warn(`[run-history] abandoned hook failed for ${record2.id}: ${describe2(hookErr)}`);
37853
+ opts.warn(`[run-history] abandoned hook failed for ${record3.id}: ${describe2(hookErr)}`);
37740
37854
  }
37741
37855
  }).finally(() => {
37742
37856
  if (flag2) provisionalFlags.delete(flag2);
@@ -38099,12 +38213,12 @@ async function closeRestartRow(adopted, restart, why) {
38099
38213
  async function closeResumedRow(adopted, resume, why, status3 = "interrupted", opts) {
38100
38214
  try {
38101
38215
  const finishedAt = (opts?.clock ?? systemClock)();
38102
- const record2 = {
38216
+ const record3 = {
38103
38217
  ...reclaimedRunRecord({ row: resume.row, events: resume.events, status: status3, finishedAt }),
38104
38218
  ...opts?.restarting === true ? { restarting: true, restartUntil: finishedAt + RESTART_CLAIM_GRACE_MS } : {}
38105
38219
  };
38106
- await putOnce(adopted.sink, record2);
38107
- return record2;
38220
+ await putOnce(adopted.sink, record3);
38221
+ return record3;
38108
38222
  } catch (err2) {
38109
38223
  console.warn(
38110
38224
  `[resume] ${resume.row.runId} could not be closed (${why}): ${err2 instanceof Error ? err2.message : String(err2)}`
@@ -40521,11 +40635,13 @@ async function ownerOf2(runs, unitsOf, threadKey) {
40521
40635
  const units = await unitsOf(instanceId).catch(() => []);
40522
40636
  const unit = units.find((u) => u.threadKey === threadKey && u.ending === void 0);
40523
40637
  if (unit !== void 0) return { kind: "unit", instanceId, unit };
40524
- const ended = units.find(
40638
+ const ended = units.filter(
40525
40639
  (u) => u.threadKey === threadKey && u.ending !== void 0 && u.ending.kind !== "merged" && u.ending.kind !== "already_landed"
40526
40640
  );
40527
40641
  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 };
40642
+ if (ended.length === 1 && ship !== void 0) return { kind: "pipeline", instanceId, run: ship, unit: ended[0] };
40643
+ if (ended.length > 1 && ship !== void 0)
40644
+ return { kind: "pipeline_ambiguous", instanceId, run: ship, units: ended };
40529
40645
  }
40530
40646
  const agent = stickyAgentOf(runs);
40531
40647
  if (agent !== void 0) return { kind: "session", agent };
@@ -40736,8 +40852,9 @@ function ownedProjection(p) {
40736
40852
  function ownerNote(owner) {
40737
40853
  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
40854
  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}`;
40855
+ if (owner.kind === "pipeline")
40856
+ 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.`;
40857
+ 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
40858
  }
40742
40859
  function operatorProjection(input) {
40743
40860
  return {
@@ -41122,14 +41239,13 @@ function runnableProposal(proposal, ctx) {
41122
41239
  return (ctx.repositories?.length ?? 0) > 0 || /https?:\/\/github\.com\/[\w.-]+\/[\w.-]+|(?:^|\s)[\w.-]+\/[\w.-]+(?:\s|[:#]|$)/i.test(request2);
41123
41240
  }
41124
41241
  function ownedDecisionRuns(event, owner, commands) {
41125
- if (event.outcome !== "binds") return false;
41242
+ if (owner.kind === "pipeline" || event.outcome !== "binds") return false;
41126
41243
  return (event.binds ?? []).every((bind) => {
41127
41244
  const parsed = commands ? parseChatCommand(bind.line, commands) : null;
41128
41245
  if (parsed?.kind !== "invoke") return false;
41129
41246
  const def = commands.list().find((c) => c.id === parsed.id);
41130
41247
  if (!def) return false;
41131
- if (def.id === "steer.run")
41132
- return owner.kind !== "pipeline" && !(owner.kind === "live" && owner.runId === void 0);
41248
+ if (def.id === "steer.run") return !(owner.kind === "live" && owner.runId === void 0);
41133
41249
  return boundBlastRadius(def, parsed.input) === "read";
41134
41250
  });
41135
41251
  }
@@ -41452,6 +41568,7 @@ async function executeOperatorDecision(deps, ctx) {
41452
41568
  "verbose"
41453
41569
  );
41454
41570
  const answered = { kind: "answered" };
41571
+ if (ctx.owner?.kind === "pipeline") return { kind: "fold" };
41455
41572
  if (event.outcome === "question") {
41456
41573
  await io.reply(event.question ?? "");
41457
41574
  await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
@@ -41811,21 +41928,21 @@ function parseSkillMarkdown(raw) {
41811
41928
  if (!fm || typeof fm !== "object" || Array.isArray(fm)) {
41812
41929
  throw new Error("skill frontmatter is not a YAML mapping");
41813
41930
  }
41814
- const record2 = fm;
41815
- const name = record2.name;
41931
+ const record3 = fm;
41932
+ const name = record3.name;
41816
41933
  if (typeof name !== "string" || name.trim() === "") {
41817
41934
  throw new Error("skill frontmatter is missing a non-empty string `name`");
41818
41935
  }
41819
- const description = record2.description;
41936
+ const description = record3.description;
41820
41937
  if (typeof description !== "string" || description.trim() === "") {
41821
41938
  throw new Error(`skill "${name}" frontmatter is missing a non-empty string \`description\``);
41822
41939
  }
41823
- const agents = record2.agents;
41940
+ const agents = record3.agents;
41824
41941
  if (!Array.isArray(agents) || agents.length === 0 || !agents.every((a) => typeof a === "string" && a.trim() !== "")) {
41825
41942
  throw new Error(`skill "${name}" frontmatter needs a non-empty \`agents\` array of strings (e.g. [review])`);
41826
41943
  }
41827
- const source2 = typeof record2.source === "string" ? record2.source.trim() : void 0;
41828
- const upstream = parseUpstream(record2.upstream, name);
41944
+ const source2 = typeof record3.source === "string" ? record3.source.trim() : void 0;
41945
+ const upstream = parseUpstream(record3.upstream, name);
41829
41946
  return {
41830
41947
  name: name.trim(),
41831
41948
  description: description.trim(),
@@ -45038,9 +45155,13 @@ function mapStringLeaves(value, fn) {
45038
45155
  }
45039
45156
  return value;
45040
45157
  }
45158
+ function requesterText(text) {
45159
+ return text.trim().replace(/\s+/g, " ").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/[\\`*_{}[\]()#!|~]/g, "\\$&").replace(/@/g, "&#64;");
45160
+ }
45041
45161
  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}`;
45162
+ const steered = by.steeredBy !== void 0 && by.steeredBy.length > 0 ? `; steered by ${by.steeredBy.map(requesterText).join(", ")}` : "";
45163
+ const thread = by.threadUrl ? ` \xB7 [Thread](${by.threadUrl})` : "";
45164
+ return `Requested by **${requesterText(by.name) || "Unknown requester"}**${thread}${steered}`;
45044
45165
  }
45045
45166
  function encodeGithubPathSegments(path) {
45046
45167
  return path.split("/").map(encodeURIComponent).join("/");
@@ -45063,6 +45184,7 @@ function renderPrDescriptionMarkdown(desc, ctx) {
45063
45184
  if (!SHA_RE2.test(ctx.headSha))
45064
45185
  throw new Error(`renderPrDescriptionMarkdown: headSha must be a full 40-char lowercase sha, got "${ctx.headSha}"`);
45065
45186
  const out = [];
45187
+ if (ctx.requestedBy) out.push(requestedByLine(ctx.requestedBy), "");
45066
45188
  out.push(desc.tldr, "");
45067
45189
  out.push(`${MAP_LABELS.why} ${desc.why}`, "");
45068
45190
  out.push(MAP_LABELS.whereToLook, "");
@@ -45089,10 +45211,7 @@ function renderPrDescriptionMarkdown(desc, ctx) {
45089
45211
  ...desc.validation.criteria.map((c) => `| ${cell2(c.criterion)} | ${cell2(c.proof)} |`)
45090
45212
  ])
45091
45213
  );
45092
- const agentLines = [
45093
- ...desc.agentNotes ? [desc.agentNotes] : [],
45094
- ...ctx.requestedBy ? [requestedByLine(ctx.requestedBy)] : []
45095
- ];
45214
+ const agentLines = desc.agentNotes ? [desc.agentNotes] : [];
45096
45215
  if (agentLines.length > 0) out.push(...details(FOLD_SUMMARIES.agents, agentLines));
45097
45216
  out.push(GENERATED_FOOTER, "");
45098
45217
  return out.join("\n");
@@ -45100,6 +45219,8 @@ function renderPrDescriptionMarkdown(desc, ctx) {
45100
45219
  function parsePrDescriptionMarkdown(body) {
45101
45220
  const problems = [];
45102
45221
  const lines = body.replace(/\r\n?/g, "\n").split("\n").filter((l) => l.trim() !== GENERATED_FOOTER);
45222
+ if (/^Requested by \*\*(?:\\.|[^*\\])+\*\*(?: · \[Thread\]\([^\s)]+\))?(?:; steered by .*)?$/.test(lines[0] ?? ""))
45223
+ lines.shift();
45103
45224
  const fenced = fenceMask(lines);
45104
45225
  const description = { pointers: [] };
45105
45226
  if (lines.some((l, i) => !fenced[i] && /^##\s+Tour\s*$/i.test(l))) {
@@ -46300,12 +46421,12 @@ var init_contract4 = __esm({
46300
46421
  };
46301
46422
  RELAUNCH_CEILING = 2;
46302
46423
  HarnessContainerReplacedError = class extends HarnessInterruptedError {
46303
- constructor(message4, said2, was, now, record2, condition = "word") {
46424
+ constructor(message4, said2, was, now, record3, condition = "word") {
46304
46425
  super(message4, "container replaced under the run", "container_replaced");
46305
46426
  this.said = said2;
46306
46427
  this.was = was;
46307
46428
  this.now = now;
46308
- this.record = record2;
46429
+ this.record = record3;
46309
46430
  this.condition = condition;
46310
46431
  this.name = "HarnessContainerReplacedError";
46311
46432
  if (condition !== "identity" !== (said2 !== void 0))
@@ -46392,11 +46513,11 @@ async function prepareRelaunch(deps, ctx) {
46392
46513
  } else {
46393
46514
  ctx.saveFacts(written);
46394
46515
  }
46395
- const { deadline, ...record2 } = replaced.record;
46516
+ const { deadline, ...record3 } = replaced.record;
46396
46517
  return {
46397
46518
  kind: "relaunch",
46398
46519
  resume: {
46399
- ...record2,
46520
+ ...record3,
46400
46521
  remainingMs: Math.max(0, deadline - ctx.clock()),
46401
46522
  facts: written,
46402
46523
  relaunch: {
@@ -47559,7 +47680,7 @@ var init_runBearers = __esm({
47559
47680
  * after `record` returned would carry the old hash ahead of the new one.
47560
47681
  * Refuses by name the runs `issue` mints nothing for, and hands `record`
47561
47682
  * nothing then. */
47562
- rotate(runId3, record2) {
47683
+ rotate(runId3, record3) {
47563
47684
  const entry = this.entries.get(runId3);
47564
47685
  if (!entry) return { ok: false, reason: "unknown_run" };
47565
47686
  if (entry.revoked) return { ok: false, reason: "revoked" };
@@ -47567,7 +47688,7 @@ var init_runBearers = __esm({
47567
47688
  const secret2 = randomBytes4(SECRET_BYTES);
47568
47689
  const hash = hashOf(secret2);
47569
47690
  entry.hashes.push(hash);
47570
- record2(hash.toString("hex"));
47691
+ record3(hash.toString("hex"));
47571
47692
  entry.hashes = [hash];
47572
47693
  return { ok: true, token: token(runId3, secret2), expiresAt: entry.grant.expiresAt };
47573
47694
  }
@@ -49370,6 +49491,82 @@ var init_process = __esm({
49370
49491
  }
49371
49492
  });
49372
49493
 
49494
+ // ../../src/core/providerToolSchemas.ts
49495
+ function responsesRejectsPattern(pattern) {
49496
+ return /\(\?(?:[=!]|<[=!])/.test(pattern);
49497
+ }
49498
+ function shapeResponsesSchema(value, tool, degradations) {
49499
+ const schema = record(value);
49500
+ if (!schema) {
49501
+ if (!Array.isArray(value)) return { value, changed: false };
49502
+ let changed2 = false;
49503
+ const items = value.map((item) => {
49504
+ const shaped = shapeResponsesSchema(item, tool, degradations);
49505
+ changed2 ||= shaped.changed;
49506
+ return shaped.value;
49507
+ });
49508
+ return { value: changed2 ? items : value, changed: changed2 };
49509
+ }
49510
+ let changed = false;
49511
+ const out = {};
49512
+ for (const [keyword, item] of Object.entries(schema)) {
49513
+ if (keyword === "pattern" && typeof item === "string" && responsesRejectsPattern(item)) {
49514
+ degradations.push({
49515
+ tool,
49516
+ keyword,
49517
+ why: "the Responses wire does not accept regular-expression lookaround"
49518
+ });
49519
+ changed = true;
49520
+ continue;
49521
+ }
49522
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record(item)) {
49523
+ let mapChanged = false;
49524
+ const mapped = {};
49525
+ for (const [name, child] of Object.entries(record(item))) {
49526
+ const shaped = shapeResponsesSchema(child, tool, degradations);
49527
+ mapChanged ||= shaped.changed;
49528
+ mapped[name] = shaped.value;
49529
+ }
49530
+ out[keyword] = mapChanged ? mapped : item;
49531
+ changed ||= mapChanged;
49532
+ continue;
49533
+ }
49534
+ if (["additionalProperties", "allOf", "anyOf", "if", "items", "not", "oneOf", "then", "else"].includes(keyword) && typeof item === "object" && item !== null) {
49535
+ const shaped = shapeResponsesSchema(item, tool, degradations);
49536
+ out[keyword] = shaped.value;
49537
+ changed ||= shaped.changed;
49538
+ continue;
49539
+ }
49540
+ out[keyword] = item;
49541
+ }
49542
+ return { value: changed ? out : value, changed };
49543
+ }
49544
+ function shapeToolSchemasForWire(shape, body) {
49545
+ if (shape !== "openai-responses" || !Array.isArray(body.tools)) return { body, degradations: [] };
49546
+ const degradations = [];
49547
+ let changed = false;
49548
+ const tools = body.tools.map((item) => {
49549
+ const tool = record(item);
49550
+ if (!tool) return item;
49551
+ const name = typeof tool.name === "string" ? tool.name : "unnamed";
49552
+ const shaped = shapeResponsesSchema(tool.parameters, name, degradations);
49553
+ if (!shaped.changed) return item;
49554
+ changed = true;
49555
+ return { ...tool, parameters: shaped.value };
49556
+ });
49557
+ const unique = degradations.filter(
49558
+ (candidate, index, all) => all.findIndex((other) => other.tool === candidate.tool && other.keyword === candidate.keyword) === index
49559
+ );
49560
+ return { body: changed ? { ...body, tools } : body, degradations: unique };
49561
+ }
49562
+ var record;
49563
+ var init_providerToolSchemas = __esm({
49564
+ "../../src/core/providerToolSchemas.ts"() {
49565
+ "use strict";
49566
+ record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
49567
+ }
49568
+ });
49569
+
49373
49570
  // ../../src/core/harness/piAi.ts
49374
49571
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
49375
49572
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
@@ -49537,6 +49734,7 @@ var init_piAi = __esm({
49537
49734
  "../../src/core/harness/piAi.ts"() {
49538
49735
  "use strict";
49539
49736
  init_provider();
49737
+ init_providerToolSchemas();
49540
49738
  init_secrets2();
49541
49739
  init_clock();
49542
49740
  ANTHROPIC_BASE_URL = "https://api.anthropic.com";
@@ -49624,7 +49822,10 @@ var init_piAi = __esm({
49624
49822
  ...this.fetchImpl ? { fetch: this.fetchImpl } : {}
49625
49823
  };
49626
49824
  try {
49627
- const message4 = await this.api$().stream(model, toPiContext(req, model, this.clock), options).result();
49825
+ const context = toPiContext(req, model, this.clock);
49826
+ const wire = this.api === "openai-completions" ? "openai-chat" : this.api;
49827
+ const shaped = shapeToolSchemasForWire(wire, context);
49828
+ const message4 = await this.api$().stream(model, shaped.body, options).result();
49628
49829
  return fromPiMessage(message4, this.name, req.tools);
49629
49830
  } catch (err2) {
49630
49831
  const failure2 = providerFailureOf(err2);
@@ -51263,8 +51464,8 @@ ${stagedLine}` : prompt;
51263
51464
  return;
51264
51465
  }
51265
51466
  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);
51467
+ const waiting3 = reissued.length === 0 ? inputs : inputs.filter((item) => !reissued.includes(item));
51468
+ if (waiting3.length > 0) run2.inbox?.requeue(waiting3);
51268
51469
  for (const item of reissued) {
51269
51470
  emit({
51270
51471
  type: "input",
@@ -51575,13 +51776,13 @@ var init_harness = __esm({
51575
51776
  }
51576
51777
  };
51577
51778
  PiContainerReplacedError = class extends HarnessContainerReplacedError {
51578
- constructor(said2, was, now, record2, condition = "word") {
51779
+ constructor(said2, was, now, record3, condition = "word") {
51579
51780
  super(
51580
51781
  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
51782
  said2,
51582
51783
  was,
51583
51784
  now,
51584
- record2,
51785
+ record3,
51585
51786
  condition
51586
51787
  );
51587
51788
  this.name = "PiContainerReplacedError";
@@ -51792,14 +51993,17 @@ var init_resume = __esm({
51792
51993
 
51793
51994
  // ../../src/execution/githubComments.ts
51794
51995
  async function postReviewComment(target, body) {
51996
+ const codePoints = [...body].length;
51997
+ if (codePoints > MAX_REVIEW_POST_CODE_POINTS) {
51998
+ throw new Error(
51999
+ `PR review body is ${codePoints} Unicode code points, over the ${MAX_REVIEW_POST_CODE_POINTS} limit; refusing to clip the structured verdict`
52000
+ );
52001
+ }
51795
52002
  const token2 = await resolveGithubToken();
51796
52003
  if (!token2) {
51797
52004
  throw new Error("no GitHub credential available to post the PR review comment");
51798
52005
  }
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 };
52006
+ const payload2 = { event: "COMMENT", body };
51803
52007
  if (target.commitId) payload2.commit_id = target.commitId;
51804
52008
  const res = await fetch(`https://api.github.com/repos/${target.repo}/pulls/${target.number}/reviews`, {
51805
52009
  method: "POST",
@@ -51817,13 +52021,12 @@ _(review truncated to fit GitHub's comment size limit)_` : body;
51817
52021
  throw new Error(`PR comment post failed: HTTP ${res.status} ${redactAndCap(text, 300)}`);
51818
52022
  }
51819
52023
  }
51820
- var MAX_COMMENT_CHARS;
51821
52024
  var init_githubComments = __esm({
51822
52025
  "../../src/execution/githubComments.ts"() {
51823
52026
  "use strict";
51824
52027
  init_githubApp();
51825
52028
  init_redact();
51826
- MAX_COMMENT_CHARS = 65e3;
52029
+ init_reviewVerdict();
51827
52030
  }
51828
52031
  });
51829
52032
 
@@ -51875,13 +52078,13 @@ function parsedPrDescriptionArtifact(facts2, ctx) {
51875
52078
  async function findSubmittedPrDescription(store, key, limit = SUBMITTED_LOOKUP_LIMIT) {
51876
52079
  const rows = await store.list({ limit, visibleTo: { kind: "repos-in", repos: [key.repo] } });
51877
52080
  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];
52081
+ const record3 = await store.get(row2.id);
52082
+ if (!record3) continue;
52083
+ for (let i = record3.events.length - 1; i >= 0; i--) {
52084
+ const ev = record3.events[i];
51882
52085
  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
52086
  const { type: _type, seq: _seq, at: _at, ...artifact } = ev;
51884
- return { artifact: { ...artifact, fromRunId: artifact.fromRunId ?? record2.id }, runId: record2.id };
52087
+ return { artifact: { ...artifact, fromRunId: artifact.fromRunId ?? record3.id }, runId: record3.id };
51885
52088
  }
51886
52089
  }
51887
52090
  }
@@ -52144,7 +52347,7 @@ async function runCodingPrPostStep(input) {
52144
52347
  });
52145
52348
  }
52146
52349
  const ownPr = target.ownPr;
52147
- const requestedBy = input.identity?.requestedBy;
52350
+ const requestedBy = input.requestedBy;
52148
52351
  const onOwnPrBranch = ownPr?.headBranch !== void 0 && branch2 === ownPr.headBranch;
52149
52352
  const pushedPastOwnPr = ownPr !== void 0 && onOwnPrBranch && pushed && headSha !== void 0 && !sameCommit(headSha, ownPr.headSha) ? headSha : void 0;
52150
52353
  if (prDescription && ownPr !== void 0 && ownPr.state !== "open" && pushedPastOwnPr !== void 0 && branch2 !== void 0) {
@@ -52355,16 +52558,17 @@ async function runCodingPrPostStep(input) {
52355
52558
  );
52356
52559
  }
52357
52560
  }
52358
- if (requestedBy !== void 0) {
52359
- const assignable = await input.identity.isAssignable(repo, requestedBy.login).catch(() => void 0);
52561
+ const requestedLogin = input.identity.requestedLogin;
52562
+ if (requestedLogin !== void 0) {
52563
+ const assignable = await input.identity.isAssignable(repo, requestedLogin).catch(() => void 0);
52360
52564
  if (assignable === true)
52361
- await input.identity.addAssignee(repo, opened.number, requestedBy.login).catch(
52565
+ await input.identity.addAssignee(repo, opened.number, requestedLogin).catch(
52362
52566
  (err2) => console.error(
52363
- `[pr-post] ${logKey} assignee add failed for ${requestedBy.login} on ${repo}#${opened.number}: ${err2 instanceof Error ? err2.message : String(err2)}`
52567
+ `[pr-post] ${logKey} assignee add failed for ${requestedLogin} on ${repo}#${opened.number}: ${err2 instanceof Error ? err2.message : String(err2)}`
52364
52568
  )
52365
52569
  );
52366
52570
  else if (assignable === false)
52367
- console.log(`[identity] ${logKey} ${requestedBy.login} is not assignable on ${repo}; skipped`);
52571
+ console.log(`[identity] ${logKey} ${requestedLogin} is not assignable on ${repo}; skipped`);
52368
52572
  }
52369
52573
  }
52370
52574
  console.log(
@@ -53175,9 +53379,9 @@ async function runLoop(deps, ctx) {
53175
53379
  throw new Error(
53176
53380
  `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
53381
  );
53178
- const judge4 = harnessOf(deps.harness);
53382
+ const judge5 = harnessOf(deps.harness);
53179
53383
  const container = deps.harness.containerFor?.(executor, profile.machine) ?? harnessContainerFor(executor, profile.machine);
53180
- const found = await judge4.find(facts2, container).catch((err2) => {
53384
+ const found = await judge5.find(facts2, container).catch((err2) => {
53181
53385
  if (!isContainerGone(err2)) throw err2;
53182
53386
  return "gone";
53183
53387
  });
@@ -53202,11 +53406,11 @@ async function runLoop(deps, ctx) {
53202
53406
  onEvent({
53203
53407
  type: "run_note",
53204
53408
  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`
53409
+ 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
53410
  });
53207
53411
  break;
53208
53412
  default:
53209
- await judge4.end(facts2, container);
53413
+ await judge5.end(facts2, container);
53210
53414
  }
53211
53415
  } else noteUnknownWord("the run finished on the answer it already had");
53212
53416
  windDownEnding = windDownEndingUnder(finish2.answer, loopEnding);
@@ -53559,7 +53763,9 @@ async function runLoop(deps, ctx) {
53559
53763
  });
53560
53764
  }
53561
53765
  if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted) {
53562
- const requestedLogin = identitySeam !== void 0 ? await identitySeam.requestedLogin(msg.userId).catch(() => void 0) : void 0;
53766
+ const instance = coordinator && deps.coordinatorInstances ? await deps.coordinatorInstances.get(coordinator.parentInstanceId).catch(() => void 0) : void 0;
53767
+ const requester = instance?.userId === msg.userId ? instance : msg;
53768
+ const requestedLogin = identitySeam !== void 0 ? await identitySeam.requestedLogin(requester.userId).catch(() => void 0) : void 0;
53563
53769
  prNote = await root.span(
53564
53770
  "run.pr_post_step",
53565
53771
  () => runCodingPrPostStep({
@@ -53572,6 +53778,10 @@ async function runLoop(deps, ctx) {
53572
53778
  remoteRepo: observedRemoteRepo
53573
53779
  },
53574
53780
  description: prDescription,
53781
+ requestedBy: {
53782
+ name: requester.userName?.trim() || requestedLogin || requester.userId,
53783
+ threadUrl: threadPageLink(requester.threadKey)
53784
+ },
53575
53785
  target: prTarget,
53576
53786
  openPullRequest: deps.openPullRequest ?? openPullRequest,
53577
53787
  findOpenPr: deps.findOpenPrByHead ?? findOpenPrByHead,
@@ -53584,7 +53794,7 @@ async function runLoop(deps, ctx) {
53584
53794
  pullRequestHead: identitySeam.pullRequestHead,
53585
53795
  isAssignable: identitySeam.isAssignable,
53586
53796
  addAssignee: identitySeam.addAssignee,
53587
- ...requestedLogin !== void 0 ? { requestedBy: { login: requestedLogin, surface: msg.channelId } } : {}
53797
+ ...requestedLogin !== void 0 ? { requestedLogin } : {}
53588
53798
  }
53589
53799
  } : {},
53590
53800
  descriptionTurnRan,
@@ -54221,7 +54431,7 @@ async function rowsFor(deps, p, selected, instanceId, carried) {
54221
54431
  p.graph.units.filter((u) => selected.includes(u.id)).map(async (u) => {
54222
54432
  const previous = carried?.get(u.id);
54223
54433
  const taskKey = p.recordTasks?.[u.id];
54224
- const record2 = taskKey !== void 0 ? await reserve(p.identity.repo, taskKey, previous?.record) : previous?.record;
54434
+ const record3 = taskKey !== void 0 ? await reserve(p.identity.repo, taskKey, previous?.record) : previous?.record;
54225
54435
  return {
54226
54436
  instanceId,
54227
54437
  unit: u.id,
@@ -54232,7 +54442,7 @@ async function rowsFor(deps, p, selected, instanceId, carried) {
54232
54442
  rounds: [],
54233
54443
  ...p.resume !== void 0 ? { resume: p.resume } : {},
54234
54444
  ...previous?.lastPush !== void 0 ? { lastPush: previous.lastPush } : {},
54235
- ...record2 !== void 0 ? { record: record2 } : {}
54445
+ ...record3 !== void 0 ? { record: record3 } : {}
54236
54446
  };
54237
54447
  })
54238
54448
  );
@@ -54648,7 +54858,11 @@ async function runShipBranch(deps, msg, io, ctx) {
54648
54858
  return { hostedLive: false };
54649
54859
  }
54650
54860
  const entry = pre.entry;
54651
- const caps = { ...resolveShipCaps(deps.config.config.ship), maxMinutes: profile.minutes };
54861
+ const configuredCaps = resolveShipCaps(deps.config.config.ship);
54862
+ const caps = {
54863
+ maxRounds: Math.min(configuredCaps.maxRounds, ctx.reissueCaps?.maxRounds ?? configuredCaps.maxRounds),
54864
+ maxMinutes: Math.min(profile.minutes, ctx.reissueCaps?.maxMinutes ?? profile.minutes)
54865
+ };
54652
54866
  const held2 = fit(caps);
54653
54867
  if (!held2.ok) {
54654
54868
  const reason2 = `budget ${caps.maxMinutes} min cannot hold the ship loop (${caps.maxRounds} review rounds need ${held2.need} min)`;
@@ -55359,41 +55573,41 @@ function prName(pr) {
55359
55573
  const m = GITHUB_PULL_URL.exec(pr.url);
55360
55574
  return m ? `${m[1]}#${pr.number} (${pr.url})` : `pull request ${pr.number} at ${pr.url}`;
55361
55575
  }
55362
- function runArtifacts(record2) {
55576
+ function runArtifacts(record3) {
55363
55577
  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}`);
55578
+ if (record3.pr) {
55579
+ const head = record3.pr.head ? `, head branch ${record3.pr.head}` : "";
55580
+ lines.push(`- pull request opened or edited: ${prName(record3.pr)}${head}`);
55367
55581
  }
55368
- if (record2.verdict) {
55369
- lines.push(`- verdict: ${record2.verdict.verdict} \u2014 ${record2.verdict.summary}`);
55370
- const findings = record2.verdict.findings ?? [];
55582
+ if (record3.verdict) {
55583
+ lines.push(`- verdict: ${record3.verdict.verdict} \u2014 ${record3.verdict.summary}`);
55584
+ const findings = record3.verdict.findings ?? [];
55371
55585
  lines.push(findings.length ? `- findings:
55372
55586
  ${indent(findings.map(formatFinding))}` : "- findings: none recorded");
55373
55587
  }
55374
- if (record2.dispositions) {
55588
+ if (record3.dispositions) {
55375
55589
  lines.push(
55376
- record2.dispositions.length ? `- dispositions:
55377
- ${indent(record2.dispositions.map(formatDisposition))}` : "- dispositions: none recorded"
55590
+ record3.dispositions.length ? `- dispositions:
55591
+ ${indent(record3.dispositions.map(formatDisposition))}` : "- dispositions: none recorded"
55378
55592
  );
55379
55593
  }
55380
- if (record2.handoff) {
55381
- const entries = handoffLines(record2.handoff);
55594
+ if (record3.handoff) {
55595
+ const entries = handoffLines(record3.handoff);
55382
55596
  lines.push(
55383
55597
  entries.length ? `- handoff:
55384
55598
  ${indent(entries)}` : "- handoff: nothing to hand off (three empty lists)"
55385
55599
  );
55386
55600
  }
55387
- const tldr = record2.pr ? tldrOf(record2.events) : void 0;
55601
+ const tldr = record3.pr ? tldrOf(record3.events) : void 0;
55388
55602
  if (tldr !== void 0) lines.push(`- description TL;DR: ${tldr}`);
55389
- if (record2.reviewPost) {
55390
- const post = record2.reviewPost;
55603
+ if (record3.reviewPost) {
55604
+ const post = record3.reviewPost;
55391
55605
  lines.push(
55392
55606
  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
55607
  );
55394
55608
  }
55395
55609
  if (lines.length === 0) return void 0;
55396
- return [`### run ${record2.id} (${record2.agent ?? "agent unknown"})`, ...lines].join("\n");
55610
+ return [`### run ${record3.id} (${record3.agent ?? "agent unknown"})`, ...lines].join("\n");
55397
55611
  }
55398
55612
  function threadArtifactsBlock(records, opts) {
55399
55613
  const maxRuns = opts.maxRuns ?? THREAD_ARTIFACTS_MAX_RUNS;
@@ -55654,6 +55868,23 @@ async function answerUnitOwnedThread(deps, ctx) {
55654
55868
  function activeRunCount() {
55655
55869
  return activeRuns;
55656
55870
  }
55871
+ function continuationBudgetOf(instance, unit) {
55872
+ if (unit.ending?.kind === "wall_clock_cap" || unit.ending?.kind === "review_pending")
55873
+ return { kind: "exhausted", budget: "wall-clock" };
55874
+ if (unit.ending?.kind === "round_cap") return { kind: "exhausted", budget: "review-round" };
55875
+ const caps = instance.caps;
55876
+ if (caps === void 0 || unit.startedAt === void 0 || unit.ending === void 0 || caps.maxMinutes <= 0 || caps.maxRounds <= 0 || unit.ending.at < unit.startedAt)
55877
+ return { kind: "unavailable" };
55878
+ const remainingMinutes = caps.maxMinutes - (unit.ending.at - unit.startedAt) / MINUTE_MS;
55879
+ if (remainingMinutes <= 0) return { kind: "exhausted", budget: "wall-clock" };
55880
+ const reviewRounds = unit.rounds.reduce(
55881
+ (highest, round2) => round2.agent === "review" ? Math.max(highest, round2.index) : highest,
55882
+ 0
55883
+ );
55884
+ const remainingRounds = caps.maxRounds - reviewRounds;
55885
+ if (remainingRounds <= 0) return { kind: "exhausted", budget: "review-round" };
55886
+ return { kind: "remaining", caps: { maxRounds: remainingRounds, maxMinutes: remainingMinutes } };
55887
+ }
55657
55888
  async function dispatch(deps, msg, io, opts = {}) {
55658
55889
  const ended = { status: "completed" };
55659
55890
  const resume = opts.resume;
@@ -55748,22 +55979,46 @@ async function dispatch(deps, msg, io, opts = {}) {
55748
55979
  try {
55749
55980
  const configuredOperator = !resume && !restart && !opts.parent && !opts.coordinator ? operatorModeOf(deps.config.config) : "off";
55750
55981
  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;
55982
+ let operatorMode = configuredOperator === "on" && typedDecision ? "off" : configuredOperator;
55752
55983
  let operatorEvent;
55753
55984
  let operatorPreset;
55754
55985
  let operatorRequest;
55755
55986
  let operatorModel;
55756
55987
  let operatorRepo;
55757
55988
  let reissuePlanId;
55989
+ let reissueCaps;
55758
55990
  let operatorThread;
55759
55991
  let pageOwner;
55760
55992
  if (operatorMode !== "off") {
55761
55993
  const runsService2 = deps.runs ?? createRunsService({ registry, store: deps.runStore });
55762
55994
  operatorThread = opts.thread ?? await readThread(runsService2, msg.threadKey);
55995
+ if (operatorThread !== void 0 && deps.coordinatorInstances !== void 0) {
55996
+ const unitReads = /* @__PURE__ */ new Map();
55997
+ const unitsOf = (id) => {
55998
+ let read = unitReads.get(id);
55999
+ if (read === void 0) {
56000
+ read = deps.coordinatorInstances.listUnits(id);
56001
+ unitReads.set(id, read);
56002
+ }
56003
+ return read;
56004
+ };
56005
+ pageOwner = await ownerOf2(operatorThread, unitsOf, msg.threadKey);
56006
+ if (operatorMode === "on" && parseDirectives(msg.text).agent === void 0) {
56007
+ const continuationOwner = pageOwner.kind === "pipeline" || pageOwner.kind === "pipeline_ambiguous" ? pageOwner : await ownerOf2(
56008
+ operatorThread.filter((run3) => run3.finished),
56009
+ unitsOf,
56010
+ msg.threadKey
56011
+ );
56012
+ if (continuationOwner.kind === "pipeline" || continuationOwner.kind === "pipeline_ambiguous") {
56013
+ pageOwner = continuationOwner;
56014
+ operatorMode = "off";
56015
+ }
56016
+ }
56017
+ }
56018
+ }
56019
+ if (operatorMode !== "off") {
55763
56020
  const slot = admission.get(msg.threadKey);
55764
56021
  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
56022
  const pendingQuestion = operatorMode === "on" ? pendingQuestionOf(operatorThread) : void 0;
55768
56023
  const joinedAnswer = pendingQuestion !== void 0 && !(pendingQuestion.proposal !== void 0 && isYesAnswer(msg.text)) ? joinedAnswerRequest(pendingQuestion, msg.text) : void 0;
55769
56024
  const doorMsg = joinedAnswer !== void 0 ? { ...msg, text: joinedAnswer } : msg;
@@ -55775,7 +56030,7 @@ async function dispatch(deps, msg, io, opts = {}) {
55775
56030
  // other, and the fold runs on to the seed refusal below, which
55776
56031
  // names the unit threads to reply in.
55777
56032
  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;
56033
+ ) : 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
56034
  operatorEvent = await root.span(
55780
56035
  "dispatch.operator",
55781
56036
  () => operatorStage(deps, {
@@ -55879,20 +56134,120 @@ async function dispatch(deps, msg, io, opts = {}) {
55879
56134
  await recordPendingOperator();
55880
56135
  return ended;
55881
56136
  }
56137
+ if (owner.kind === "pipeline_ambiguous" && directives.agent === void 0) {
56138
+ await io.reply(
56139
+ `This thread matches multiple ended plan units (${owner.units.map((unit) => unit.unit).join(", ")}), so continuation is ambiguous. Nothing started.`
56140
+ );
56141
+ await recordPendingOperator();
56142
+ return ended;
56143
+ }
55882
56144
  if (owner.kind === "pipeline" && directives.agent === void 0) {
56145
+ const instance = await deps.coordinatorInstances.get(owner.instanceId).catch(() => null);
56146
+ if (instance === null) {
56147
+ await io.reply(
56148
+ `The ended pipeline's record \`${owner.instanceId}\` is unavailable, so continuation did not start.`
56149
+ );
56150
+ await recordPendingOperator();
56151
+ return ended;
56152
+ }
56153
+ const actor = chatActorOf(deps.config, msg);
56154
+ if (authorizeSteerOwner({
56155
+ caller: { ids: actorIdsOf(actor), grants: effectiveGrants(actor) },
56156
+ target: { runId: owner.run.id, requesterId: instance.userId }
56157
+ }).kind === "refused") {
56158
+ await io.reply(STEER_OWNER_REFUSED);
56159
+ await recordPendingOperator();
56160
+ return ended;
56161
+ }
56162
+ if (instance.threadKey !== msg.threadKey || instance.plan === void 0 || instance.plan.path !== void 0) {
56163
+ await io.reply(
56164
+ `The ended pipeline's durable generated-task identity does not match this thread, so continuation did not start.`
56165
+ );
56166
+ await recordPendingOperator();
56167
+ return ended;
56168
+ }
56169
+ if (instance.repo !== owner.run.repo || instance.branch !== owner.unit.branch) {
56170
+ await io.reply(
56171
+ `The ended pipeline's repository or branch identity no longer matches unit ${owner.unit.unit}, so continuation did not start.`
56172
+ );
56173
+ await recordPendingOperator();
56174
+ return ended;
56175
+ }
56176
+ const continuationBudget = continuationBudgetOf(instance, owner.unit);
56177
+ if (continuationBudget.kind === "unavailable") {
56178
+ await io.reply(
56179
+ `Unit ${owner.unit.unit} does not retain a complete durable wall-clock and review-round budget, so continuation did not start. Nothing else ran.`
56180
+ );
56181
+ await recordPendingOperator();
56182
+ return ended;
56183
+ }
56184
+ if (continuationBudget.kind === "exhausted") {
56185
+ await io.reply(
56186
+ `Unit ${owner.unit.unit} exhausted the ended pipeline's ${continuationBudget.budget} budget, so continuation did not start. Nothing else ran.`
56187
+ );
56188
+ await recordPendingOperator();
56189
+ return ended;
56190
+ }
55883
56191
  const original = await shipRequestOf(runsService, owner.run.id);
55884
56192
  if (original === void 0) {
55885
56193
  await io.reply(
55886
- "This ship pipeline ended, but its original task could not be read. What was the pipeline's original task?"
56194
+ `The ended pipeline's original task is unavailable from run \`${owner.run.id}\`, so continuation did not start.`
55887
56195
  );
55888
56196
  await recordPendingOperator();
55889
56197
  return ended;
55890
56198
  }
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;
56199
+ const recordedPr = owner.unit.pr;
56200
+ if (recordedPr === void 0) {
56201
+ await io.reply(
56202
+ `Unit ${owner.unit.unit} does not retain a durable pull request identity, so continuation did not start. Nothing else ran.`
56203
+ );
56204
+ await recordPendingOperator();
56205
+ return ended;
56206
+ }
56207
+ const expectedHead = owner.unit.lastPush;
56208
+ if (expectedHead === void 0) {
56209
+ await io.reply(
56210
+ `Unit ${owner.unit.unit} does not retain the pull request's durable expected head, so continuation did not start. Nothing else ran.`
56211
+ );
56212
+ await recordPendingOperator();
56213
+ return ended;
56214
+ }
56215
+ const target = { repo: instance.repo, number: recordedPr.number };
56216
+ const facts2 = await (deps.fetchPrFacts ?? fetchPullRequestFacts)(target).catch(() => void 0);
56217
+ if (facts2 === void 0) {
56218
+ await io.reply(
56219
+ `GitHub did not return current facts for ${instance.repo}#${recordedPr.number}, so continuation did not start.`
56220
+ );
56221
+ await recordPendingOperator();
56222
+ return ended;
56223
+ }
56224
+ if (facts2.state !== "open") {
56225
+ await io.reply(
56226
+ `${instance.repo}#${recordedPr.number} is closed, so this ended pipeline has no open pull request to continue. Nothing started.`
56227
+ );
56228
+ await recordPendingOperator();
56229
+ return ended;
56230
+ }
56231
+ const verifiedHead = facts2.verifiedHead;
56232
+ 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) {
56233
+ await io.reply(
56234
+ `${instance.repo}#${recordedPr.number} no longer has the pipeline's verifiable \`${owner.unit.branch}\` head, so continuation did not start.`
56235
+ );
56236
+ await recordPendingOperator();
56237
+ return ended;
56238
+ }
56239
+ if (verifiedHead.sha !== expectedHead) {
56240
+ await io.reply(
56241
+ `${instance.repo}#${recordedPr.number} moved from the pipeline's expected head \`${expectedHead}\` to \`${verifiedHead.sha}\`, so continuation did not start. Nothing else ran.`
56242
+ );
56243
+ await recordPendingOperator();
56244
+ return ended;
56245
+ }
56246
+ reissuePlanId = instance.plan.id;
56247
+ reissueCaps = continuationBudget.caps;
56248
+ const durableRequest = parseDirectives(original);
56249
+ Object.assign(directives, durableRequest, { agent: void 0, budget: reissueCaps.maxMinutes });
55894
56250
  operatorPreset = "ship";
55895
- operatorRequest = original;
55896
56251
  settled = resolveCurrent();
55897
56252
  ({ sticky, resolved, agentSource } = settled);
55898
56253
  agentSource = "sticky";
@@ -56118,6 +56473,7 @@ async function dispatch(deps, msg, io, opts = {}) {
56118
56473
  card,
56119
56474
  directives,
56120
56475
  ...reissuePlanId !== void 0 ? { reissuePlanId } : {},
56476
+ ...reissueCaps !== void 0 ? { reissueCaps: { ...reissueCaps, maxMinutes: Math.min(reissueCaps.maxMinutes, profile.minutes) } } : {},
56121
56477
  sticky,
56122
56478
  history,
56123
56479
  repoCtx,
@@ -57046,6 +57402,7 @@ var init_dispatcher = __esm({
57046
57402
  init_route();
57047
57403
  init_operator();
57048
57404
  init_authorize2();
57405
+ init_authorize();
57049
57406
  init_messages();
57050
57407
  init_staging();
57051
57408
  init_runRecord();
@@ -57064,6 +57421,7 @@ var init_dispatcher = __esm({
57064
57421
  init_settle();
57065
57422
  init_reattach2();
57066
57423
  init_factory();
57424
+ init_githubPulls();
57067
57425
  init_sandboxErrors();
57068
57426
  init_lineage();
57069
57427
  init_seed();
@@ -57424,8 +57782,8 @@ function createLedgerWriteThrough(opts) {
57424
57782
  settleLanding(this.runId, entry, outcome);
57425
57783
  return value;
57426
57784
  },
57427
- abandoned: (record2, why) => {
57428
- if (record2.id !== this.runId) return;
57785
+ abandoned: (record3, why) => {
57786
+ if (record3.id !== this.runId) return;
57429
57787
  const entry = landing.get(this.runId);
57430
57788
  if (entry !== void 0) settleLanding(this.runId, entry, { kind: "failed", why });
57431
57789
  }
@@ -57435,18 +57793,18 @@ function createLedgerWriteThrough(opts) {
57435
57793
  async land(plain7) {
57436
57794
  await this.close();
57437
57795
  const session = this.sessionOnRecord();
57438
- const record2 = session ? { ...plain7, session } : plain7;
57796
+ const record3 = session ? { ...plain7, session } : plain7;
57439
57797
  let result;
57440
57798
  try {
57441
- result = await ledger.finish(this.runId, gen, record2);
57799
+ result = await ledger.finish(this.runId, gen, record3);
57442
57800
  } catch (err2) {
57443
57801
  if (!(err2 instanceof RouteMissingError)) throw err2;
57444
57802
  routeMissing();
57445
- return { value: await fallback.put(record2), outcome: { kind: "refused" } };
57803
+ return { value: await fallback.put(record3), outcome: { kind: "refused" } };
57446
57804
  }
57447
57805
  if (result.ok) return { value: result, outcome: { kind: "landed" } };
57448
57806
  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" } };
57807
+ return { value: await fallback.put(record3), outcome: { kind: "refused" } };
57450
57808
  }
57451
57809
  tracked() {
57452
57810
  return !this.detached;
@@ -57516,7 +57874,7 @@ function createLedgerWriteThrough(opts) {
57516
57874
  }));
57517
57875
  if (report2.compaction)
57518
57876
  turns.push({ idx: this.rowIndex(report2.firstIdx + report2.turns.length), compaction: report2.compaction });
57519
- const record2 = {
57877
+ const record3 = {
57520
57878
  step: ++this.stepNo,
57521
57879
  seq: this.lastSeq,
57522
57880
  turnIndex: report2.firstIdx + turns.length,
@@ -57527,10 +57885,10 @@ function createLedgerWriteThrough(opts) {
57527
57885
  turn: report2.turn,
57528
57886
  iteration: report2.iteration
57529
57887
  };
57530
- this.factRound = record2.step;
57531
- const kept = new Set(record2.inFlight.map((c) => c.callId));
57888
+ this.factRound = record3.step;
57889
+ const kept = new Set(record3.inFlight.map((c) => c.callId));
57532
57890
  for (const id of this.factCallStarts.keys()) if (!kept.has(id)) this.factCallStarts.delete(id);
57533
- const call2 = record2.inFlight[0];
57891
+ const call2 = record3.inFlight[0];
57534
57892
  this.factInFlight = call2 ? {
57535
57893
  callId: call2.callId,
57536
57894
  tool: call2.tool,
@@ -57542,13 +57900,13 @@ function createLedgerWriteThrough(opts) {
57542
57900
  } : void 0;
57543
57901
  for (let attempt = 1; ; attempt++) {
57544
57902
  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;
57903
+ const result = await ledger.step(this.runId, gen, record3, turns, this.sessionRow?.key);
57904
+ if (!result.ok) this.detach(`step ${record3.step} refused (${result.reason})`);
57905
+ else this.turnsWritten = record3.turnIndex;
57548
57906
  return;
57549
57907
  } catch (err2) {
57550
57908
  if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
57551
- this.detach(`step ${record2.step} failed: ${describe9(err2)}`);
57909
+ this.detach(`step ${record3.step} failed: ${describe9(err2)}`);
57552
57910
  return;
57553
57911
  }
57554
57912
  await sleep2(RETRY_MS[0]);
@@ -59376,8 +59734,8 @@ async function launchOpenCode(deps, spec, bearer) {
59376
59734
  let consumed = 0;
59377
59735
  for (const line4 of text.split("\n").slice(0, -1)) {
59378
59736
  consumed += Buffer.byteLength(line4, "utf8") + 1;
59379
- const record2 = parseFeedRecord(line4);
59380
- if (record2?.feed === "tailer" && record2.note === "connected") {
59737
+ const record3 = parseFeedRecord(line4);
59738
+ if (record3?.feed === "tailer" && record3.note === "connected") {
59381
59739
  feedOffset = consumed;
59382
59740
  break;
59383
59741
  }
@@ -59457,9 +59815,9 @@ var init_process2 = __esm({
59457
59815
  });
59458
59816
 
59459
59817
  // ../../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;
59818
+ function executionStartedFor(record3, sessionID) {
59819
+ if (record3.feed !== "event" || record3.event.type !== "session.execution.started") return false;
59820
+ const data = record3.event.data;
59463
59821
  return data?.sessionID === sessionID;
59464
59822
  }
59465
59823
  function openCodeToolNameWord(name) {
@@ -60131,18 +60489,18 @@ async function driveOpenCode(deps, run2, conn, kind = "loop") {
60131
60489
  const after = transport.consumedOffset;
60132
60490
  const reattachCatchUp = conn.reattach !== void 0 && after <= conn.reattach.catchUpTo;
60133
60491
  lastRecord = next.value;
60134
- const record2 = parseFeedRecord(next.value);
60135
- if (!record2) continue;
60136
- if (!reattachCatchUp && executionStartedFor(record2, conn.sessionID)) {
60492
+ const record3 = parseFeedRecord(next.value);
60493
+ if (!record3) continue;
60494
+ if (!reattachCatchUp && executionStartedFor(record3, conn.sessionID)) {
60137
60495
  executionOwned = true;
60138
60496
  awaiting = void 0;
60139
60497
  }
60140
60498
  bridge.observing = reattachCatchUp ? "catching-up" : executionOwned ? "own" : "earlier";
60141
- const obs = bridge.observe(record2);
60499
+ const obs = bridge.observe(record3);
60142
60500
  if (!reattachCatchUp && obs.boundary === true) conn.boundaries?.reached();
60143
60501
  if (!reattachCatchUp && obs.inboxDelivered !== void 0) conn.inboxFate?.deliver(obs.inboxDelivered);
60144
60502
  if (!reattachCatchUp && obs.inboxDropped === true) conn.inboxFate?.interruptAll();
60145
- if (record2.feed === "messages" && conn.saveOffset !== void 0) {
60503
+ if (record3.feed === "messages" && conn.saveOffset !== void 0) {
60146
60504
  const save = conn.saveOffset;
60147
60505
  void bridge.flush().then(() => save(after));
60148
60506
  }
@@ -60229,14 +60587,14 @@ async function driveOpenCode(deps, run2, conn, kind = "loop") {
60229
60587
  );
60230
60588
  }
60231
60589
  if (replacedBy !== void 0) {
60232
- const record2 = bridge.record(recordBase, deadline);
60590
+ const record3 = bridge.record(recordBase, deadline);
60233
60591
  bridge.closeOpenSpans((open2) => openCodeReplacedCallNote(open2.tool));
60234
60592
  const replaced = replacedBy.condition === "word" ? new OpenCodeContainerReplacedError(
60235
60593
  replacedBy.said.message,
60236
60594
  conn.containerWord,
60237
60595
  await conn.container.identity().catch(() => void 0),
60238
- record2
60239
- ) : new OpenCodeContainerReplacedError(void 0, replacedBy.was, replacedBy.now, record2, "identity");
60596
+ record3
60597
+ ) : new OpenCodeContainerReplacedError(void 0, replacedBy.was, replacedBy.now, record3, "identity");
60240
60598
  note("sandbox_restarted", replaced.message);
60241
60599
  throw replaced;
60242
60600
  }
@@ -60376,13 +60734,13 @@ var init_bridge3 = __esm({
60376
60734
  diagnostics;
60377
60735
  };
60378
60736
  OpenCodeContainerReplacedError = class extends HarnessContainerReplacedError {
60379
- constructor(said2, was, now, record2, condition = "word") {
60737
+ constructor(said2, was, now, record3, condition = "word") {
60380
60738
  super(
60381
60739
  `the container running OpenCode was replaced (${was ?? "unknown"} \u2192 ${now ?? "unknown"}; ${replacedBecause(condition, said2)})`,
60382
60740
  said2,
60383
60741
  was,
60384
60742
  now,
60385
- record2,
60743
+ record3,
60386
60744
  condition
60387
60745
  );
60388
60746
  this.name = "OpenCodeContainerReplacedError";
@@ -60831,25 +61189,25 @@ var init_bridge3 = __esm({
60831
61189
  emit(event) {
60832
61190
  this.deps.emit(event.at === void 0 ? { ...event, at: this.deps.clock() } : event);
60833
61191
  }
60834
- observe(record2) {
60835
- switch (record2.feed) {
61192
+ observe(record3) {
61193
+ switch (record3.feed) {
60836
61194
  case "event":
60837
- return this.onEvent(record2.event);
61195
+ return this.onEvent(record3.event);
60838
61196
  case "permissions":
60839
61197
  if (this.observing === "earlier") return { replies: [], settled: false };
60840
- return this.onPermissionsRefill(record2.data);
61198
+ return this.onPermissionsRefill(record3.data);
60841
61199
  case "messages": {
60842
- const out = this.onMessagesRefill(record2.data);
60843
- if (this.settleOwedTo !== void 0 && record2.reason === this.settleOwedTo) {
61200
+ const out = this.onMessagesRefill(record3.data);
61201
+ if (this.settleOwedTo !== void 0 && record3.reason === this.settleOwedTo) {
60844
61202
  this.settleOwedTo = void 0;
60845
61203
  out.settled = true;
60846
61204
  }
60847
61205
  return out;
60848
61206
  }
60849
61207
  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) {
61208
+ if (record3.note === "stream closed" || record3.note.includes("failed"))
61209
+ this.deps.onProgress?.(`opencode feed: ${redactAndCap(record3.note, 120)}`);
61210
+ if (this.settleOwedTo !== void 0 && (record3.note === "message refill failed" || record3.note === "refill failed") && record3.reason === this.settleOwedTo) {
60853
61211
  this.settleOwedTo = void 0;
60854
61212
  return { replies: [], settled: true };
60855
61213
  }
@@ -61809,7 +62167,7 @@ ${stagedLine}` : prompt;
61809
62167
  return "lost";
61810
62168
  return "running";
61811
62169
  };
61812
- const resolve13 = async (steerSeq, knownAtSteer, seen, waiting2) => {
62170
+ const resolve13 = async (steerSeq, knownAtSteer, seen, waiting3) => {
61813
62171
  let left = false;
61814
62172
  for (; ; ) {
61815
62173
  const verdict = await learnRow(steerSeq, knownAtSteer);
@@ -61824,7 +62182,7 @@ ${stagedLine}` : prompt;
61824
62182
  "the follow-up's steer",
61825
62183
  "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
62184
  );
61827
- waiting2();
62185
+ waiting3();
61828
62186
  if (await conn.boundaries.wait(seen) === "left") left = true;
61829
62187
  seen = conn.boundaries.count;
61830
62188
  }
@@ -62136,6 +62494,14 @@ var init_requester = __esm({
62136
62494
 
62137
62495
  // ../../src/channels/http.ts
62138
62496
  import { timingSafeEqual as timingSafeEqual4 } from "node:crypto";
62497
+ function ingressComponentError(name, value) {
62498
+ if (value.length === 0) return `\`${name}\` must be a non-empty string`;
62499
+ if (value.length > INGRESS_COMPONENT_MAX_LENGTH) {
62500
+ return `\`${name}\` must be at most ${INGRESS_COMPONENT_MAX_LENGTH} characters`;
62501
+ }
62502
+ if (/[:#]/.test(value)) return `\`${name}\` must not contain ':' or '#'`;
62503
+ return void 0;
62504
+ }
62139
62505
  function safeEqual2(a, b) {
62140
62506
  const ab = Buffer.from(a, "utf8");
62141
62507
  const bb = Buffer.from(b, "utf8");
@@ -62179,11 +62545,15 @@ function parseBody3(raw) {
62179
62545
  if (typeof obj2.text !== "string" || obj2.text.trim() === "") {
62180
62546
  return { error: "`text` is required and must be a non-empty string" };
62181
62547
  }
62182
- if (obj2.channel !== void 0 && typeof obj2.channel !== "string") {
62183
- return { error: "`channel` must be a string" };
62548
+ if (obj2.channel !== void 0) {
62549
+ if (typeof obj2.channel !== "string") return { error: "`channel` must be a string" };
62550
+ const error = ingressComponentError("channel", obj2.channel);
62551
+ if (error) return { error };
62184
62552
  }
62185
- if (obj2.thread !== void 0 && typeof obj2.thread !== "string") {
62186
- return { error: "`thread` must be a string" };
62553
+ if (obj2.thread !== void 0) {
62554
+ if (typeof obj2.thread !== "string") return { error: "`thread` must be a string" };
62555
+ const error = ingressComponentError("thread", obj2.thread);
62556
+ if (error) return { error };
62187
62557
  }
62188
62558
  if (obj2.async !== void 0 && typeof obj2.async !== "boolean") {
62189
62559
  return { error: "`async` must be a boolean" };
@@ -62236,6 +62606,10 @@ async function handleAuthorized(identity2, body, deps, options) {
62236
62606
  if ("error" in parsed) {
62237
62607
  return { status: 400, body: { error: parsed.error } };
62238
62608
  }
62609
+ if (identity2.channel !== void 0) {
62610
+ const error = ingressComponentError("channel", identity2.channel);
62611
+ if (error) return { status: 400, body: { error } };
62612
+ }
62239
62613
  const receivedAt = systemClock();
62240
62614
  const trace = startRequestRoot(deps, { channel: "http", receivedAt });
62241
62615
  const msg = {
@@ -62316,7 +62690,7 @@ function parseIngressTokens(secrets) {
62316
62690
  if (!parsed.ok) console.error(`[ingress] SWITCHBOARD_INGRESS_TOKENS is ${parsed.reason} \u2014 ingress disabled`);
62317
62691
  return { tokens: parsed.tokens };
62318
62692
  }
62319
- var PLATFORM, DEFAULT_CHANNEL, DEFAULT_THREAD, MAX_BODY_BYTES2, HttpIO, DISPATCH_ACTION;
62693
+ var PLATFORM, DEFAULT_CHANNEL, DEFAULT_THREAD, INGRESS_COMPONENT_MAX_LENGTH, MAX_BODY_BYTES2, HttpIO, DISPATCH_ACTION;
62320
62694
  var init_http = __esm({
62321
62695
  "../../src/channels/http.ts"() {
62322
62696
  "use strict";
@@ -62329,6 +62703,7 @@ var init_http = __esm({
62329
62703
  PLATFORM = "http";
62330
62704
  DEFAULT_CHANNEL = "default";
62331
62705
  DEFAULT_THREAD = "default";
62706
+ INGRESS_COMPONENT_MAX_LENGTH = 128;
62332
62707
  MAX_BODY_BYTES2 = 1e6;
62333
62708
  HttpIO = class {
62334
62709
  constructor(priorTurns = []) {
@@ -62525,7 +62900,7 @@ function openAiStopReason(raw) {
62525
62900
  }
62526
62901
  }
62527
62902
  function meterAnthropicMessage(json4) {
62528
- const msg = record(json4);
62903
+ const msg = record2(json4);
62529
62904
  if (!msg) return {};
62530
62905
  const usage = usageFromAnthropic(msg.usage);
62531
62906
  return {
@@ -62534,11 +62909,11 @@ function meterAnthropicMessage(json4) {
62534
62909
  };
62535
62910
  }
62536
62911
  function meterOpenAiCompletion(json4) {
62537
- const body = record(json4);
62912
+ const body = record2(json4);
62538
62913
  if (!body) return {};
62539
62914
  const usage = usageFromOpenAI(body.usage);
62540
62915
  const reported = reportedCostOf(body.usage);
62541
- const choice = Array.isArray(body.choices) ? record(body.choices[0]) : void 0;
62916
+ const choice = Array.isArray(body.choices) ? record2(body.choices[0]) : void 0;
62542
62917
  return {
62543
62918
  ...usage ? { usage } : {},
62544
62919
  ...reported ? { reported } : {},
@@ -62548,15 +62923,15 @@ function meterOpenAiCompletion(json4) {
62548
62923
  function responsesStopReason(response) {
62549
62924
  const output = Array.isArray(response.output) ? response.output : [];
62550
62925
  if (response.status === "completed") {
62551
- return output.some((item) => record(item)?.type === "function_call") ? "tool_use" : "end_turn";
62926
+ return output.some((item) => record2(item)?.type === "function_call") ? "tool_use" : "end_turn";
62552
62927
  }
62553
62928
  if (response.status === "incomplete") {
62554
- return record(response.incomplete_details)?.reason === "max_output_tokens" ? "max_tokens" : "other";
62929
+ return record2(response.incomplete_details)?.reason === "max_output_tokens" ? "max_tokens" : "other";
62555
62930
  }
62556
62931
  return "other";
62557
62932
  }
62558
62933
  function meterResponses(json4) {
62559
- const body = record(json4);
62934
+ const body = record2(json4);
62560
62935
  if (!body) return {};
62561
62936
  const usage = usageFromResponses(body.usage);
62562
62937
  const reported = reportedCostOf(body.usage);
@@ -62578,87 +62953,20 @@ function shapeTools(shape, body, marks) {
62578
62953
  const allowed2 = new Set(marks.turn.tools);
62579
62954
  const table = Array.isArray(body.tools) ? body.tools : [];
62580
62955
  const kept = table.filter((t) => {
62581
- const name = toolNameOf(shape, record(t));
62956
+ const name = toolNameOf(shape, record2(t));
62582
62957
  return typeof name === "string" && allowed2.has(name);
62583
62958
  });
62584
62959
  const { tool_choice: _choice, ...rest } = body;
62585
62960
  const shaped = { ...rest, tools: kept };
62586
62961
  return { body: shaped, toolChoice: toolChoiceWord(shape, void 0, kept.length) };
62587
62962
  }
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
62963
  function toolNameOf(shape, tool) {
62656
62964
  if (tool === void 0) return void 0;
62657
- return shape === "openai-chat" ? record(tool.function)?.name : tool.name;
62965
+ return shape === "openai-chat" ? record2(tool.function)?.name : tool.name;
62658
62966
  }
62659
62967
  function toolSchemaOf(shape, tool) {
62660
62968
  if (shape === "anthropic-messages") return tool.input_schema;
62661
- if (shape === "openai-chat") return record(tool.function)?.parameters;
62969
+ if (shape === "openai-chat") return record2(tool.function)?.parameters;
62662
62970
  return tool.parameters;
62663
62971
  }
62664
62972
  function schemaKeywords(value, into = /* @__PURE__ */ new Set()) {
@@ -62666,12 +62974,12 @@ function schemaKeywords(value, into = /* @__PURE__ */ new Set()) {
62666
62974
  for (const item of value) schemaKeywords(item, into);
62667
62975
  return into;
62668
62976
  }
62669
- const schema = record(value);
62977
+ const schema = record2(value);
62670
62978
  if (!schema) return into;
62671
62979
  for (const [keyword, item] of Object.entries(schema)) {
62672
62980
  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);
62981
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record2(item)) {
62982
+ for (const child of Object.values(record2(item))) schemaKeywords(child, into);
62675
62983
  } else {
62676
62984
  schemaKeywords(item, into);
62677
62985
  }
@@ -62682,7 +62990,7 @@ function providerErrorProse(body) {
62682
62990
  const strings2 = (value, into = []) => {
62683
62991
  if (typeof value === "string") into.push(value);
62684
62992
  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);
62993
+ else if (record2(value)) for (const item of Object.values(record2(value))) strings2(item, into);
62686
62994
  return into;
62687
62995
  };
62688
62996
  try {
@@ -62700,7 +63008,7 @@ function providerSchemaRejectionOf2(shape, body, errorBody) {
62700
63008
  prose2
62701
63009
  ))
62702
63010
  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(
63011
+ 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
63012
  (tool) => typeof tool.name === "string" && quoted(prose2, tool.name)
62705
63013
  );
62706
63014
  if (candidates2.length !== 1) return void 0;
@@ -62723,7 +63031,7 @@ function toolsOffered(shape, body) {
62723
63031
  const table = Array.isArray(body.tools) ? body.tools : [];
62724
63032
  const names = [];
62725
63033
  for (const t of table) {
62726
- const name = toolNameOf(shape, record(t));
63034
+ const name = toolNameOf(shape, record2(t));
62727
63035
  if (typeof name === "string") names.push(name);
62728
63036
  }
62729
63037
  names.sort();
@@ -62736,7 +63044,7 @@ function toolsOffered(shape, body) {
62736
63044
  function toolChoiceWord(shape, choice, tools) {
62737
63045
  if (choice === void 0 || choice === null) return tools > 0 ? "auto" : "none";
62738
63046
  if (shape === "anthropic-messages") {
62739
- const type = record(choice)?.type;
63047
+ const type = record2(choice)?.type;
62740
63048
  if (type === "none" || type === "any" || type === "tool") return type;
62741
63049
  if (type === "auto") return tools > 0 ? "auto" : "none";
62742
63050
  return tools > 0 ? "auto" : "none";
@@ -62744,7 +63052,7 @@ function toolChoiceWord(shape, choice, tools) {
62744
63052
  if (choice === "none") return "none";
62745
63053
  if (choice === "required") return "any";
62746
63054
  if (choice === "auto") return tools > 0 ? "auto" : "none";
62747
- return record(choice) ? "tool" : tools > 0 ? "auto" : "none";
63055
+ return record2(choice) ? "tool" : tools > 0 ? "auto" : "none";
62748
63056
  }
62749
63057
  function turnAttrs(grant2, meter, ttftMs, offered, price) {
62750
63058
  const u = meter.usage;
@@ -62853,7 +63161,7 @@ async function handleAdmitted(door, req, deps) {
62853
63161
  } catch {
62854
63162
  return refusalResponse(shape, 400, "invalid_body", "the body is not JSON");
62855
63163
  }
62856
- const body = record(parsed);
63164
+ const body = record2(parsed);
62857
63165
  if (!body) return refusalResponse(shape, 400, "invalid_body", "the body must be a JSON object");
62858
63166
  const upstream = upstreamFor(
62859
63167
  shape,
@@ -63124,7 +63432,7 @@ function createModelProxyHandler(deps) {
63124
63432
  })();
63125
63433
  };
63126
63434
  }
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;
63435
+ 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
63436
  var init_modelProxy = __esm({
63129
63437
  "../../src/channels/modelProxy.ts"() {
63130
63438
  "use strict";
@@ -63132,6 +63440,7 @@ var init_modelProxy = __esm({
63132
63440
  init_providerFailureAuth();
63133
63441
  init_usage();
63134
63442
  init_modelPricing();
63443
+ init_providerToolSchemas();
63135
63444
  init_provider();
63136
63445
  init_http();
63137
63446
  ANTHROPIC_MESSAGES_PATH = "/v1/messages";
@@ -63148,7 +63457,7 @@ var init_modelProxy = __esm({
63148
63457
  DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
63149
63458
  FORWARDED_REQUEST_HEADERS = ["accept", "anthropic-version", "anthropic-beta"];
63150
63459
  FORWARDED_RESPONSE_HEADERS = ["content-type", "request-id", "x-request-id"];
63151
- record = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
63460
+ record2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
63152
63461
  SseMeter = class {
63153
63462
  constructor(shape) {
63154
63463
  this.shape = shape;
@@ -63192,7 +63501,7 @@ var init_modelProxy = __esm({
63192
63501
  };
63193
63502
  }
63194
63503
  apply(json4) {
63195
- const event = record(json4);
63504
+ const event = record2(json4);
63196
63505
  if (!event) return;
63197
63506
  if (this.shape === "openai-responses") {
63198
63507
  if (event.type !== "response.completed" && event.type !== "response.incomplete" && event.type !== "response.failed")
@@ -63205,12 +63514,12 @@ var init_modelProxy = __esm({
63205
63514
  }
63206
63515
  if (this.shape === "anthropic-messages") {
63207
63516
  if (event.type === "message_start") {
63208
- const usage2 = usageFromAnthropic(record(event.message)?.usage);
63517
+ const usage2 = usageFromAnthropic(record2(event.message)?.usage);
63209
63518
  if (usage2) this.usage = { ...this.usage, ...usage2 };
63210
63519
  } else if (event.type === "message_delta") {
63211
- const stop = record(event.delta)?.stop_reason;
63520
+ const stop = record2(event.delta)?.stop_reason;
63212
63521
  if (typeof stop === "string") this.stopReason = anthropicStopReason(stop);
63213
- const usage2 = record(event.usage);
63522
+ const usage2 = record2(event.usage);
63214
63523
  if (usage2) {
63215
63524
  if (typeof usage2.output_tokens === "number") this.usage.outputTokens = usage2.output_tokens;
63216
63525
  if (typeof usage2.input_tokens === "number") this.usage.inputTokens = usage2.input_tokens;
@@ -63223,7 +63532,7 @@ var init_modelProxy = __esm({
63223
63532
  }
63224
63533
  return;
63225
63534
  }
63226
- const choice = Array.isArray(event.choices) ? record(event.choices[0]) : void 0;
63535
+ const choice = Array.isArray(event.choices) ? record2(event.choices[0]) : void 0;
63227
63536
  if (typeof choice?.finish_reason === "string") this.stopReason = openAiStopReason(choice.finish_reason);
63228
63537
  const usage = usageFromOpenAI(event.usage);
63229
63538
  if (usage) this.usage = { ...this.usage, ...usage };
@@ -63245,7 +63554,7 @@ function bearerOf(headers) {
63245
63554
  const m = value ? /^Bearer\s+(\S+)$/i.exec(value.trim()) : null;
63246
63555
  return m ? m[1] : void 0;
63247
63556
  }
63248
- function judge3(deps, presented) {
63557
+ function judge4(deps, presented) {
63249
63558
  const verdict = deps.bearers.verify(presented);
63250
63559
  if (!verdict.ok) {
63251
63560
  const status3 = verdict.reason === "unknown_run" ? 404 : verdict.reason === "malformed" ? 401 : verdict.reason === "unknown_bearer" ? 401 : 403;
@@ -63267,12 +63576,12 @@ function held(path, reason2, runId3) {
63267
63576
  async function admitHarnessRequest(deps, headers, path) {
63268
63577
  const presented = bearerOf(headers);
63269
63578
  if (!presented) return { ok: false, response: { status: 401, body: { error: "missing_bearer" } } };
63270
- let verdict = judge3(deps, presented);
63579
+ let verdict = judge4(deps, presented);
63271
63580
  if (verdict.ok) return verdict;
63272
63581
  if (!RESUME_CAN_OPEN.has(verdict.reason)) return refused6(verdict);
63273
63582
  if (!deps.takeover.settled) {
63274
63583
  await deps.takeover.whenSettled(DOOR_HOLD_MS);
63275
- verdict = judge3(deps, presented);
63584
+ verdict = judge4(deps, presented);
63276
63585
  if (verdict.ok) return verdict;
63277
63586
  if (!RESUME_CAN_OPEN.has(verdict.reason)) return refused6(verdict);
63278
63587
  if (!deps.takeover.settled) return held(path, "reclaim_pending", verdict.runId);
@@ -64608,13 +64917,13 @@ async function catchUpMissedMentions(opts) {
64608
64917
  const orphanCutoffMs = now - (opts.orphanWindowMs ?? ORPHAN_CARD_WINDOW_MS);
64609
64918
  const threadCutoffMs = sweep ? Math.min(cutoffMs, orphanCutoffMs) : cutoffMs;
64610
64919
  const { client, botUserId } = opts;
64611
- const record2 = opts.record ?? recordCatchUpOutcome;
64920
+ const record3 = opts.record ?? recordCatchUpOutcome;
64612
64921
  let channels;
64613
64922
  try {
64614
64923
  channels = await listChannels(client);
64615
64924
  } catch (err2) {
64616
64925
  log(`[catch-up] cannot list channels: ${errMsg(err2)}`);
64617
- record2({ at: now, channels: 0, missed: 0, skippedChannels: 0, error: errMsg(err2) });
64926
+ record3({ at: now, channels: 0, missed: 0, skippedChannels: 0, error: errMsg(err2) });
64618
64927
  return { channels: 0, missed: 0, orphans: 0, skippedChannels: 0 };
64619
64928
  }
64620
64929
  const scans = await mapLimit(channels, CATCH_UP_CHANNEL_CONCURRENCY, async (channel) => {
@@ -64683,7 +64992,7 @@ async function catchUpMissedMentions(opts) {
64683
64992
  log(
64684
64993
  `[catch-up] scanned ${channels.length} channel(s): ${missed} missed message(s), ${orphans} orphaned card(s), ${skippedChannels} skipped`
64685
64994
  );
64686
- record2({ at: now, channels: channels.length, missed, skippedChannels });
64995
+ record3({ at: now, channels: channels.length, missed, skippedChannels });
64687
64996
  return { channels: channels.length, missed, orphans, skippedChannels };
64688
64997
  }
64689
64998
  async function listChannels(client) {
@@ -66571,11 +66880,19 @@ ${JSON.stringify(result.value)}` }] });
66571
66880
  if (typeof args.text !== "string" || args.text.trim() === "") {
66572
66881
  return err(id, INVALID_PARAMS, "`text` is required and must be a non-empty string");
66573
66882
  }
66574
- if (args.channel !== void 0 && typeof args.channel !== "string") {
66575
- return err(id, INVALID_PARAMS, "`channel` must be a string");
66883
+ if (args.channel !== void 0) {
66884
+ if (typeof args.channel !== "string") return err(id, INVALID_PARAMS, "`channel` must be a string");
66885
+ const error = ingressComponentError("channel", args.channel);
66886
+ if (error) return err(id, INVALID_PARAMS, error);
66576
66887
  }
66577
- if (args.thread !== void 0 && typeof args.thread !== "string") {
66578
- return err(id, INVALID_PARAMS, "`thread` must be a string");
66888
+ if (args.thread !== void 0) {
66889
+ if (typeof args.thread !== "string") return err(id, INVALID_PARAMS, "`thread` must be a string");
66890
+ const error = ingressComponentError("thread", args.thread);
66891
+ if (error) return err(id, INVALID_PARAMS, error);
66892
+ }
66893
+ if (identity2.channel !== void 0) {
66894
+ const error = ingressComponentError("channel", identity2.channel);
66895
+ if (error) return err(id, INVALID_PARAMS, error);
66579
66896
  }
66580
66897
  const receivedAt = systemClock();
66581
66898
  const trace = startRequestRoot(deps, { channel: "mcp", receivedAt });
@@ -68211,16 +68528,16 @@ function residentStateTone(state) {
68211
68528
  }
68212
68529
  function residentsFleetTone(records) {
68213
68530
  if (records.length === 0) return "grey";
68214
- const tones = new Set(records.map((record2) => residentStateTone(residentLive(rec(record2)).state)));
68531
+ const tones = new Set(records.map((record3) => residentStateTone(residentLive(rec(record3)).state)));
68215
68532
  if (tones.has("red")) return "red";
68216
68533
  if (tones.has("amber")) return "amber";
68217
68534
  return tones.size === 1 && tones.has("green") ? "green" : "grey";
68218
68535
  }
68219
- function residentSlug(record2) {
68220
- return str8(record2.resource).replace(/^repo:/, "");
68536
+ function residentSlug(record3) {
68537
+ return str8(record3.resource).replace(/^repo:/, "");
68221
68538
  }
68222
- function residentLive(record2) {
68223
- const live = rec(record2.live);
68539
+ function residentLive(record3) {
68540
+ const live = rec(record3.live);
68224
68541
  if (typeof live.error === "string") return { ...live, state: "unreachable", reason: live.error };
68225
68542
  return { ...live, state: str8(live.state) || "unknown", reason: str8(live.reason) };
68226
68543
  }
@@ -68520,12 +68837,12 @@ function createResidentsViewHandler(deps) {
68520
68837
  finish2(200);
68521
68838
  return;
68522
68839
  }
68523
- const record2 = listing.residents.find((x) => residentSlug(x) === route2.slug);
68524
- if (!record2) {
68840
+ const record3 = listing.residents.find((x) => residentSlug(x) === route2.slug);
68841
+ if (!record3) {
68525
68842
  plainEnd(404, `${route2.slug} is not onboarded as a resident`);
68526
68843
  return;
68527
68844
  }
68528
- page2(req, res, 200, ctx.actor, route2.slug, { page: "resident", slug: route2.slug, record: record2 });
68845
+ page2(req, res, 200, ctx.actor, route2.slug, { page: "resident", slug: route2.slug, record: record3 });
68529
68846
  finish2(200);
68530
68847
  });
68531
68848
  return true;
@@ -70667,9 +70984,9 @@ function startReclaimSweep(opts) {
70667
70984
  return { stop: () => stop(timer) };
70668
70985
  }
70669
70986
  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" };
70987
+ const record3 = reclaimedRunRecord(input);
70988
+ const result = await ledger.finish(input.row.runId, gen, record3);
70989
+ return result.ok ? { ok: true, record: record3 } : { ok: false, reason: result.reason ?? "refused" };
70673
70990
  }
70674
70991
  async function completenessVerdict(ledger, row2, lastStep) {
70675
70992
  if (!lastStep) return { resumable: false, why: "no step record: killed before its conversation was stored" };
@@ -72219,11 +72536,11 @@ async function reviewPostedAtPatiently(deps, pr, verdict, head) {
72219
72536
  }
72220
72537
  return answer;
72221
72538
  }
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;
72539
+ function reviewPostedByRecord(record3, pr) {
72540
+ const post = record3.reviewPost;
72541
+ if (post === void 0 || record3.reviewHead === void 0 || record3.verdict === void 0) return void 0;
72225
72542
  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;
72543
+ const same = post.target.repo === pr.repo && post.target.number === pr.number && sameCommit(post.head.toLowerCase(), record3.reviewHead) && post.verdict === record3.verdict.verdict;
72227
72544
  return same ? { reviewPosted: true } : void 0;
72228
72545
  }
72229
72546
  function headBranchStateUnknown(facts2) {
@@ -72240,6 +72557,7 @@ function pullRequestState(number, fallbackUrl, facts2) {
72240
72557
  state: "merged",
72241
72558
  prNumber: number,
72242
72559
  url,
72560
+ ...facts2.headSha !== void 0 ? { headSha: facts2.headSha } : {},
72243
72561
  sha: facts2.mergeCommitSha,
72244
72562
  mergedAt: facts2.mergedAt,
72245
72563
  ...facts2.mergedBy !== void 0 ? { mergedBy: facts2.mergedBy } : {}
@@ -72310,15 +72628,16 @@ async function readRecord(body, deps) {
72310
72628
  at
72311
72629
  });
72312
72630
  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);
72631
+ const record3 = full.ok ? full.value : view;
72632
+ const finalReply = finalReplyOf2(record3.events);
72633
+ const reviewAskedAt = reviewAskedAtOf(record3.events);
72634
+ const description = record3.events?.some((event) => event.type === "pr_description") === true;
72635
+ const opened = prOpenedOf(record3.events);
72317
72636
  const pr = opened !== void 0 ? { number: opened.number, url: opened.url, created: opened.created } : void 0;
72318
72637
  let coordinatorUnit;
72319
72638
  if (instanceRow !== null && typeof body.unit === "string") {
72320
72639
  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;
72640
+ const pushedPrRef = opened?.head !== void 0 && record3.pushed?.some((pushed) => pushed.ref === opened.head) ? opened.head : void 0;
72322
72641
  if (coordinatorUnit !== void 0 && pushedPrRef !== void 0 && coordinatorUnit.branch !== pushedPrRef) {
72323
72642
  coordinatorUnit = { ...coordinatorUnit, branch: pushedPrRef };
72324
72643
  await deps.instances.putUnits([coordinatorUnit]);
@@ -72345,13 +72664,13 @@ async function readRecord(body, deps) {
72345
72664
  });
72346
72665
  }
72347
72666
  let posted;
72348
- if (record2.verdict !== void 0 && record2.reviewHead !== void 0 && typeof body.unit === "string") {
72667
+ if (record3.verdict !== void 0 && record3.reviewHead !== void 0 && typeof body.unit === "string") {
72349
72668
  const instance = instanceRow;
72350
72669
  if (instance && coordinatorUnit?.pr !== void 0) {
72351
72670
  const unitPr = { repo: instance.repo, number: coordinatorUnit.pr.number };
72352
- posted = reviewPostedByRecord(record2, unitPr);
72671
+ posted = reviewPostedByRecord(record3, unitPr);
72353
72672
  if (posted === void 0) {
72354
- const seen = await reviewPostedAtPatiently(deps, unitPr, record2.verdict.verdict, record2.reviewHead);
72673
+ const seen = await reviewPostedAtPatiently(deps, unitPr, record3.verdict.verdict, record3.reviewHead);
72355
72674
  if (seen !== void 0) posted = { reviewPosted: seen };
72356
72675
  }
72357
72676
  }
@@ -72363,27 +72682,31 @@ async function readRecord(body, deps) {
72363
72682
  run: {
72364
72683
  ...coordinatorRunView(view, id.value, finalReply),
72365
72684
  ...pr !== void 0 ? { pr } : {},
72366
- ...record2.verdict !== void 0 ? { verdict: record2.verdict } : {},
72367
- ...record2.reviewHead !== void 0 ? { reviewHead: record2.reviewHead } : {},
72685
+ ...record3.verdict !== void 0 ? { verdict: record3.verdict } : {},
72686
+ ...record3.reviewHead !== void 0 ? { reviewHead: record3.reviewHead } : {},
72368
72687
  ...reviewAskedAt !== void 0 ? { reviewAskedAt } : {},
72369
72688
  ...posted !== void 0 ? { reviewPosted: posted.reviewPosted } : {},
72370
72689
  ...posted?.reviewPostReason !== void 0 ? { reviewPostReason: posted.reviewPostReason } : {},
72371
- ...record2.dispositions !== void 0 ? { dispositions: record2.dispositions } : {},
72372
- ...record2.handoff !== void 0 ? { handoff: true } : {},
72690
+ ...record3.dispositions !== void 0 ? { dispositions: record3.dispositions } : {},
72691
+ // Findings readiness requires the typed description output. Its event is
72692
+ // durable on the child record, so the runner can verify it without
72693
+ // trusting final-reply prose.
72694
+ ...description ? { description: true } : {},
72695
+ ...record3.handoff !== void 0 ? { handoff: true } : {},
72373
72696
  // 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 } : {},
72697
+ ...record3.pushed !== void 0 ? { pushed: record3.pushed } : {},
72698
+ ...record3.lease !== void 0 ? { leaseStartedAt: record3.lease.startedAt } : {},
72376
72699
  // What the child cost, as the runs service prices it (costs.md item 4c):
72377
72700
  // null when unknown — no usage on the record, or a model without a price
72378
72701
  // — so a capped grant never renews on an understated total.
72379
- costUsd: record2.cost?.usd ?? null,
72380
- ...record2.handoff !== void 0 ? { handoffLists: record2.handoff } : {},
72702
+ costUsd: record3.cost?.usd ?? null,
72703
+ ...record3.handoff !== void 0 ? { handoffLists: record3.handoff } : {},
72381
72704
  // The failure by name (run-history item 57): a `provider_transient` lets
72382
72705
  // the machine re-run a round-0 child that pushed nothing (issue 1932).
72383
- ...record2.failure !== void 0 ? { failure: record2.failure } : {},
72706
+ ...record3.failure !== void 0 ? { failure: record3.failure } : {},
72384
72707
  // What ended an interrupted child (issue 1876), off its record's own
72385
72708
  // events: the unit's ending names the actual cause in the user's nouns.
72386
- ...view.status === "interrupted" ? interruptionOf(record2.events) : {}
72709
+ ...view.status === "interrupted" ? interruptionOf(record3.events) : {}
72387
72710
  },
72388
72711
  at
72389
72712
  });
@@ -72462,6 +72785,13 @@ async function recoverPushedBranch(deps, instance, row2, branch2, runId3) {
72462
72785
  if (subject2 !== void 0 && checkPrTitle(subject2, prTitleVocabulary_default).ok) title = subject2;
72463
72786
  }
72464
72787
  title ??= recoveredFallbackTitle(row2, branch2, instance.plan?.id ?? parsePlanBranch(branch2)?.planId);
72788
+ const header4 = requestedByLine({
72789
+ name: instance.userName?.trim() || instance.userId,
72790
+ threadUrl: threadPageLink(instance.threadKey, deps.runPageBase?.replace(/\/runs\/?$/, "") ?? "")
72791
+ });
72792
+ prBody = `${header4}
72793
+
72794
+ ${prBody}`;
72465
72795
  if (deps.rewriteIdentities !== void 0) {
72466
72796
  const rewritten = await deps.rewriteIdentities({
72467
72797
  repo: instance.repo,
@@ -73711,7 +74041,7 @@ ${planSummary(units)}`).catch(() => {
73711
74041
  const summary = deps.registry.getById(host.runId);
73712
74042
  const visibility = await deps.channelVisibilityOf(instance.channelId);
73713
74043
  const finishedAt = snap?.finishedAt ?? at;
73714
- const record2 = assembleRunRecord({
74044
+ const record3 = assembleRunRecord({
73715
74045
  run: { id: host.runId, ...summary?.label !== void 0 ? { label: summary.label } : {} },
73716
74046
  snap,
73717
74047
  agent: "ship",
@@ -73741,7 +74071,7 @@ ${planSummary(units)}`).catch(() => {
73741
74071
  seal
73742
74072
  });
73743
74073
  const handle2 = deps.ledgerRuns().find((run2) => run2.runId === host.runId);
73744
- deps.runHistoryWriter.write(record2, handle2 !== void 0 ? { via: handle2.sink } : void 0);
74074
+ deps.runHistoryWriter.write(record3, handle2 !== void 0 ? { via: handle2.sink } : void 0);
73745
74075
  }
73746
74076
  (deps.log ?? console.log)(`[coordinator] ${instance.id}: finished ${body.outcome} \u2014 record ${runId3}`);
73747
74077
  return json3(200, { ok: true, runId: runId3, at });
@@ -73867,6 +74197,8 @@ var init_adminCoordinator = __esm({
73867
74197
  "use strict";
73868
74198
  init_budgets();
73869
74199
  init_verbosity();
74200
+ init_prDescription();
74201
+ init_reply();
73870
74202
  init_registry();
73871
74203
  init_authorize();
73872
74204
  init_actor();