@coreplane/switchboard 1.226.1 → 1.227.0

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.
Files changed (25) hide show
  1. package/dist/assets/deploy/cloudflare-resident/worker.ts +238 -8
  2. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +7 -0
  3. package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.sh +67 -0
  4. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +49 -0
  5. package/dist/assets/package-lock.json +3 -3
  6. package/dist/assets/package.json +1 -1
  7. package/dist/assets/source.json +3 -3
  8. package/dist/assets/src/core/authz/policy.ts +8 -0
  9. package/dist/assets/src/core/runEvents.ts +16 -2
  10. package/dist/assets/src/core/runRecord.ts +61 -0
  11. package/dist/assets/src/execution/residentRebind.ts +233 -0
  12. package/dist/assets/src/execution/residentSteps.ts +1 -0
  13. package/dist/assets/src/execution/sandboxErrors.ts +99 -0
  14. package/dist/assets/web/dist/.vite/manifest.json +18 -18
  15. package/dist/assets/web/dist/assets/{ResidentDetailPage-BBOpejGX.js → ResidentDetailPage-Chvll3wy.js} +1 -1
  16. package/dist/assets/web/dist/assets/{ResidentsIndexPage-B8kFWHpB.js → ResidentsIndexPage-B5f8IwGF.js} +1 -1
  17. package/dist/assets/web/dist/assets/{RunRoutePage-COWeVtIE.js → RunRoutePage-CRvmCuXh.js} +4 -4
  18. package/dist/assets/web/dist/assets/{RunsIndexPage-BjH93cKx.js → RunsIndexPage-BTJuKFTv.js} +1 -1
  19. package/dist/assets/web/dist/assets/{ScheduledPage-grlNKvCA.js → ScheduledPage-BVfgUBvP.js} +1 -1
  20. package/dist/assets/web/dist/assets/{StatusDot-BQNoaXO6.js → StatusDot-CFXbAw7S.js} +1 -1
  21. package/dist/assets/web/dist/assets/{Tooltip-DVCeIzaa.js → Tooltip-DcHMtbHJ.js} +1 -1
  22. package/dist/assets/web/dist/assets/{dist-B5Wfk-oY.js → dist-DKhqHu0V.js} +1 -1
  23. package/dist/assets/web/dist/assets/{main-CN0U7d6s.js → main-CveRd2yk.js} +2 -2
  24. package/dist/cli.js +470 -190
  25. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -689,6 +689,13 @@ var init_policy = __esm({
689
689
  // List-shaped `runs.*`: the grant admits the command; the store predicate narrows the rows.
690
690
  { action: "runs:read", resource: "command", when: [grant("runs:read")] },
691
691
  { action: "runs:write", resource: "command", when: [grant("runs:write")] },
692
+ // ── conversations ────────────────────────────────────────────────────────
693
+ // Who may point the bot at another thread (record 0037). Asked for a
694
+ // pointing actor (`pointingActor`: one membership, the origin channel, no
695
+ // grants), so `member-of` reads: a public channel from anywhere, a private
696
+ // channel only from inside it, an admin's `all` not consulted. The row has
697
+ // no caller until the references dispatch step lands behind its flag.
698
+ { action: "conversation:read", resource: "channel", when: [MEMBER_OF] },
692
699
  // ── review ───────────────────────────────────────────────────────────────
693
700
  // `review abridge` spends one Opus-class call and rewrites a stored record:
694
701
  // the grant admits the command (admins through `all`, operators by name;
@@ -872,8 +879,8 @@ function principalOf(actor) {
872
879
  while (current.onBehalfOf) current = current.onBehalfOf;
873
880
  return current;
874
881
  }
875
- function deny(reason) {
876
- return { allow: false, reason };
882
+ function deny(reason2) {
883
+ return { allow: false, reason: reason2 };
877
884
  }
878
885
  function isKnownActorKind(kind) {
879
886
  return ACTOR_KINDS.includes(kind);
@@ -909,13 +916,13 @@ function authorizeWith(rules, actor, action, resource) {
909
916
  if (forOrigin.length === 0) return deny("origin-visibility");
910
917
  const grants = effectiveGrants(actor);
911
918
  const selfId = principalOf(actor).id;
912
- let reason;
919
+ let reason2;
913
920
  for (const rule of forOrigin) {
914
921
  const failed = rule.when.find((condition) => !evaluateCondition(condition, grants, selfId, attributes));
915
922
  if (!failed) return ALLOW;
916
- reason ??= FAILURE_REASON[failed.kind];
923
+ reason2 ??= FAILURE_REASON[failed.kind];
917
924
  }
918
- return deny(reason ?? "no-rule");
925
+ return deny(reason2 ?? "no-rule");
919
926
  }
920
927
  function authorize(actor, action, resource) {
921
928
  return authorizeWith(POLICY, actor, action, resource);
@@ -2856,6 +2863,7 @@ function validateConfig(cfg) {
2856
2863
  if (cfg.ship !== void 0) validateShip(cfg.ship);
2857
2864
  if (cfg.spawn !== void 0) validateSpawn(cfg.spawn);
2858
2865
  if (cfg.routing !== void 0) validateRouting(cfg.routing, cfg.providers);
2866
+ if (cfg.references !== void 0) validateReferences(cfg.references);
2859
2867
  if (cfg.artifacts !== void 0) validateArtifacts(cfg.artifacts);
2860
2868
  if (cfg.harness !== void 0) validateHarness(cfg.harness);
2861
2869
  if (cfg.pi !== void 0) validatePi(cfg.pi);
@@ -2885,6 +2893,14 @@ function validateHarness(harness) {
2885
2893
  throw new Error(`config.yaml: harness.${preset} must be one of ${HARNESSES.join(", ")}`);
2886
2894
  }
2887
2895
  }
2896
+ function validateReferences(references) {
2897
+ if (typeof references !== "object" || references === null || Array.isArray(references))
2898
+ throw new Error("config.yaml: references must be a mapping");
2899
+ for (const key of unknownKeys2(references, REFERENCES_KEYS))
2900
+ throw new Error(`config.yaml: references.${key} is not a known key`);
2901
+ if (references.enabled !== void 0 && typeof references.enabled !== "boolean")
2902
+ throw new Error("config.yaml: references.enabled must be true or false");
2903
+ }
2888
2904
  function validateRouting(routing, providers) {
2889
2905
  if (typeof routing !== "object" || routing === null || Array.isArray(routing))
2890
2906
  throw new Error("config.yaml: routing must be a mapping");
@@ -2993,7 +3009,7 @@ function validateRuntimeOverrides(ro) {
2993
3009
  }
2994
3010
  }
2995
3011
  }
2996
- var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, PI_KEYS, PI_COMPACTION_KEYS, ROUTE_ANSWER_MODES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
3012
+ var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, PI_KEYS, PI_COMPACTION_KEYS, ROUTE_ANSWER_MODES, REFERENCES_KEYS, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
2997
3013
  var init_validate = __esm({
2998
3014
  "../../src/config/validate.ts"() {
2999
3015
  "use strict";
@@ -3027,6 +3043,7 @@ var init_validate = __esm({
3027
3043
  ship: true,
3028
3044
  spawn: true,
3029
3045
  routing: true,
3046
+ references: true,
3030
3047
  harness: true,
3031
3048
  pi: true,
3032
3049
  slack: true,
@@ -3046,6 +3063,7 @@ var init_validate = __esm({
3046
3063
  PI_KEYS = { compaction: true };
3047
3064
  PI_COMPACTION_KEYS = { reserveTokens: true, keepRecentTokens: true };
3048
3065
  ROUTE_ANSWER_MODES = ["tool", "text"];
3066
+ REFERENCES_KEYS = { enabled: true };
3049
3067
  ROUTING_KEYS = { auto: true, model: true, answer: true };
3050
3068
  SHIP_KEYS = { maxRounds: true, maxMinutes: true };
3051
3069
  SPAWN_KEYS = { maxChildren: true };
@@ -4117,6 +4135,7 @@ var init_residentSteps = __esm({
4117
4135
  "for-each-ref": "listing the branches",
4118
4136
  checkout: "checking out the branch",
4119
4137
  "checkout-update": "updating the checkout",
4138
+ "rebind-checkout": "checking out the thread's own branch",
4120
4139
  "rev-parse": "reading the commit",
4121
4140
  "cat-file": "checking the mirror for the commit",
4122
4141
  "show-ref": "reading the branch tip",
@@ -5543,10 +5562,10 @@ function describeBase(base, code) {
5543
5562
  return "none";
5544
5563
  }
5545
5564
  }
5546
- function markdownReason(reason) {
5547
- if (reason.startsWith("unsure:"))
5548
- return reason.replace(/(unclassified path |import )(\S+)/, (_m, pre, p) => `${pre}\`${p}\``);
5549
- return reason.replace(/^(\S+?)(?=$| \(imported by |: (production )?dependencies)/, "`$1`").replace(/\(imported by (\S+)\)/, "(imported by `$1`)");
5565
+ function markdownReason(reason2) {
5566
+ if (reason2.startsWith("unsure:"))
5567
+ return reason2.replace(/(unclassified path |import )(\S+)/, (_m, pre, p) => `${pre}\`${p}\``);
5568
+ return reason2.replace(/^(\S+?)(?=$| \(imported by |: (production )?dependencies)/, "`$1`").replace(/\(imported by (\S+)\)/, "(imported by `$1`)");
5550
5569
  }
5551
5570
  function capped(reasons, render3, sep) {
5552
5571
  const shown = reasons.slice(0, REASONS_SHOWN).map(render3);
@@ -5670,19 +5689,19 @@ function servedCommit(body) {
5670
5689
  return typeof c === "string" && c !== "" ? c : void 0;
5671
5690
  }
5672
5691
  function decideReady(body, elapsedMs, deadlineMs, identify) {
5673
- let reason;
5692
+ let reason2;
5674
5693
  if (!body) {
5675
- reason = "/healthz not answering with JSON (container restarting, or unreachable)";
5694
+ reason2 = "/healthz not answering with JSON (container restarting, or unreachable)";
5676
5695
  } else {
5677
5696
  const verdict = identify(body);
5678
5697
  if (verdict.live) return { kind: "live", ...verdict.identity };
5679
5698
  if (body.draining === true && !verdict.sameIdentity) {
5680
5699
  const n2 = typeof body.inFlight === "number" ? body.inFlight : "?";
5681
5700
  const since = typeof body.drainStartedAt === "string" ? ` since ${body.drainStartedAt}` : "";
5682
- reason = `old container still draining \u2014 ${n2} run(s) in flight${since}`;
5683
- } else reason = verdict.reason;
5701
+ reason2 = `old container still draining \u2014 ${n2} run(s) in flight${since}`;
5702
+ } else reason2 = verdict.reason;
5684
5703
  }
5685
- return elapsedMs >= deadlineMs ? { kind: "timeout", reason } : { kind: "waiting", reason };
5704
+ return elapsedMs >= deadlineMs ? { kind: "timeout", reason: reason2 } : { kind: "waiting", reason: reason2 };
5686
5705
  }
5687
5706
  function sameCommit(a, b) {
5688
5707
  const x = a.trim();
@@ -6289,7 +6308,7 @@ var init_sandboxLiveGate = __esm({
6289
6308
  PROBE_COMMAND = "echo ok";
6290
6309
  PROBE_TIMEOUT_MS = 6e4;
6291
6310
  CONTAINER_STARTING = /Container is starting/i;
6292
- waiting = (reason) => ({ kind: "waiting", reason });
6311
+ waiting = (reason2) => ({ kind: "waiting", reason: reason2 });
6293
6312
  isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
6294
6313
  ANSI_SEQUENCE2 = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
6295
6314
  SECTION_GUTTER = /^[\s│├╭╰|]+/;
@@ -7346,10 +7365,7 @@ async function transferImage(copy, account2, credential, io = {}) {
7346
7365
  } catch (err2) {
7347
7366
  if (err2 instanceof TransferProblem)
7348
7367
  return { ok: false, problem: err2.message, ...err2.unauthorized ? { unauthorized: true } : {} };
7349
- return {
7350
- ok: false,
7351
- problem: `copying ${copy.source} failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`
7352
- };
7368
+ return { ok: false, problem: `copying ${copy.source} failed \u2014 ${reason(err2)}` };
7353
7369
  }
7354
7370
  }
7355
7371
  async function transfer(copy, account2, credential, io) {
@@ -7433,16 +7449,19 @@ async function transfer(copy, account2, credential, io) {
7433
7449
  if (started.status !== 202 || !startedAt)
7434
7450
  throw await refused(`starting the upload of ${short2(blob.digest)} to ${targetName} failed`, started);
7435
7451
  let location = resolveLocation(startedAt, `${targetBase}/blobs/uploads/`);
7436
- const source2 = await fromSource(
7437
- `reading ${short2(blob.digest)} from ${copy.source}`,
7438
- `${sourceBase}/v2/${repository}/blobs/${blob.digest}`,
7439
- { signal: AbortSignal.timeout(LONG_MS) }
7452
+ const blobUrl = `${sourceBase}/v2/${repository}/blobs/${blob.digest}`;
7453
+ const chunks = sourceBytes(
7454
+ blob,
7455
+ copy.source,
7456
+ log,
7457
+ (from) => fromSource(`reading ${short2(blob.digest)} from ${copy.source}`, blobUrl, {
7458
+ headers: from > 0 ? { range: `bytes=${from}-` } : {},
7459
+ signal: AbortSignal.timeout(LONG_MS)
7460
+ })
7440
7461
  );
7441
- if (!source2.ok || !source2.body)
7442
- throw new TransferProblem(`reading ${short2(blob.digest)} from ${copy.source} failed \u2014 ${await said(source2)}`);
7443
7462
  const hash = createHash2("sha256");
7444
7463
  let index = 0;
7445
- for await (const { part, bytes: partBytesBuffer } of partsOf(source2.body, parts, hash, blob, copy.source)) {
7464
+ for await (const { part, bytes: partBytesBuffer } of partsOf(chunks, parts, hash, blob, copy.source)) {
7446
7465
  index++;
7447
7466
  const label = `part ${index}/${parts.length} (${contentRange(part)})`;
7448
7467
  const patch = () => doFetch(location, {
@@ -7513,6 +7532,41 @@ function asBody(bytes) {
7513
7532
  function sha256(bytes) {
7514
7533
  return `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
7515
7534
  }
7535
+ async function* sourceBytes(blob, source2, log, open2) {
7536
+ let consumed = 0;
7537
+ let resumes = 0;
7538
+ for (; ; ) {
7539
+ const res = await open2(consumed);
7540
+ const what = consumed === 0 ? `reading ${short2(blob.digest)} from ${source2}` : `resuming ${short2(blob.digest)} from ${source2} at byte ${consumed}`;
7541
+ if (consumed === 0 ? !res.ok : res.status !== 206) {
7542
+ const words = consumed > 0 && res.status === 200 ? "HTTP 200 where 206 was expected (the source ignored the Range)" : await said(res);
7543
+ void res.body?.cancel().catch(() => {
7544
+ });
7545
+ throw new TransferProblem(`${what} failed \u2014 ${words}`);
7546
+ }
7547
+ const contentRange2 = res.headers.get("content-range");
7548
+ if (consumed > 0 && !contentRange2?.startsWith(`bytes ${consumed}-`))
7549
+ throw new TransferProblem(`${what} failed \u2014 the source answered Content-Range ${contentRange2 ?? "none"}`);
7550
+ if (!res.body) throw new TransferProblem(`${what} failed \u2014 no body`);
7551
+ try {
7552
+ for await (const chunk of res.body) {
7553
+ consumed += chunk.byteLength;
7554
+ yield chunk;
7555
+ }
7556
+ return;
7557
+ } catch (err2) {
7558
+ if (consumed >= blob.size) return;
7559
+ if (resumes >= RESUMES)
7560
+ throw new TransferProblem(
7561
+ `reading ${short2(blob.digest)} from ${source2} broke at byte ${consumed} of ${blob.size} after ${resumes} resumes \u2014 ${reason(err2)}`
7562
+ );
7563
+ resumes++;
7564
+ log(
7565
+ `[images] ${short2(blob.digest)} broke at ${mib(consumed)} of ${mib(blob.size)} (${reason(err2)}) \u2014 resuming from there, ${resumes} of ${RESUMES}`
7566
+ );
7567
+ }
7568
+ }
7569
+ }
7516
7570
  async function* partsOf(body, parts, hash, blob, source2) {
7517
7571
  let i = 0;
7518
7572
  let buffer = parts.length > 0 ? Buffer.allocUnsafe(parts[0].end - parts[0].start) : Buffer.alloc(0);
@@ -7542,7 +7596,7 @@ async function* partsOf(body, parts, hash, blob, source2) {
7542
7596
  `${short2(blob.digest)} from ${source2} ended after ${parts[i].start + filled} of its ${blob.size} bytes`
7543
7597
  );
7544
7598
  }
7545
- var REGISTRY_ENDPOINTS, SHORT_MS, LONG_MS, TransferProblem, refused, said, MiB, mib, short2;
7599
+ var REGISTRY_ENDPOINTS, SHORT_MS, LONG_MS, RESUMES, TransferProblem, refused, said, reason, MiB, mib, short2;
7546
7600
  var init_registryTransferHost = __esm({
7547
7601
  "../../src/deploy/registryTransferHost.ts"() {
7548
7602
  "use strict";
@@ -7555,6 +7609,7 @@ var init_registryTransferHost = __esm({
7555
7609
  };
7556
7610
  SHORT_MS = 6e4;
7557
7611
  LONG_MS = 30 * 6e4;
7612
+ RESUMES = 3;
7558
7613
  TransferProblem = class extends Error {
7559
7614
  constructor(message3, unauthorized = false) {
7560
7615
  super(message3);
@@ -7567,6 +7622,11 @@ var init_registryTransferHost = __esm({
7567
7622
  const text = (await res.text().catch(() => "")).trim();
7568
7623
  return `HTTP ${res.status}${text ? `: ${text}` : ""}`;
7569
7624
  };
7625
+ reason = (err2) => {
7626
+ if (!(err2 instanceof Error)) return String(err2);
7627
+ const cause = err2.cause instanceof Error ? err2.cause.message : void 0;
7628
+ return cause && cause !== err2.message ? `${err2.message} (${cause})` : err2.message;
7629
+ };
7570
7630
  MiB = 1024 * 1024;
7571
7631
  mib = (n2) => `${(n2 / MiB).toFixed(n2 >= 10 * MiB ? 0 : 1)} MiB`;
7572
7632
  short2 = (digest) => digest.slice(0, 19);
@@ -7590,9 +7650,9 @@ function imagesHostIO(options = {}) {
7590
7650
  const transferIO = { ...options.transferIO ?? {}, ...options.log ? { log: options.log } : {} };
7591
7651
  const credentials = /* @__PURE__ */ new Map();
7592
7652
  const credential = async (account2, replace = false) => {
7593
- const held = credentials.get(account2);
7594
- if (held && !replace && now() - held.mintedAt < CREDENTIAL_MINUTES * 6e4 - RENEW_BEFORE_MS)
7595
- return { ok: true, credential: held.credential };
7653
+ const held2 = credentials.get(account2);
7654
+ if (held2 && !replace && now() - held2.mintedAt < CREDENTIAL_MINUTES * 6e4 - RENEW_BEFORE_MS)
7655
+ return { ok: true, credential: held2.credential };
7596
7656
  const token2 = env[API_TOKEN_ENV];
7597
7657
  if (!token2) return { ok: false, problem: apiTokenMissingProblem() };
7598
7658
  const minted = await transfer2.mint(account2, token2, transferIO);
@@ -7994,7 +8054,7 @@ function isHeadMaterial(event) {
7994
8054
  case "route":
7995
8055
  return true;
7996
8056
  case "run_note":
7997
- return event.kind === "mcp_unavailable" || event.kind === "spans_dropped" || event.kind === "cold_sandbox";
8057
+ return event.kind === "mcp_unavailable" || event.kind === "spans_dropped" || event.kind === "cold_sandbox" || event.kind === "rebind_refused";
7998
8058
  case "span_start":
7999
8059
  return event.name === "request" || event.name === "slack.receive" || event.name.startsWith("dispatch.");
8000
8060
  case "span_end":
@@ -8060,6 +8120,7 @@ var init_runEvents = __esm({
8060
8120
  "seed",
8061
8121
  "description_turn",
8062
8122
  "cold_sandbox",
8123
+ "rebind_refused",
8063
8124
  "pr_not_opened",
8064
8125
  "review_not_posted",
8065
8126
  "compacted",
@@ -9163,12 +9224,12 @@ ${parts}`);
9163
9224
  });
9164
9225
 
9165
9226
  // ../../src/execution/residentState.ts
9166
- function degradedIsServiceable(reason) {
9167
- return SERVICEABLE_DEGRADED_REASON.test(reason ?? "");
9227
+ function degradedIsServiceable(reason2) {
9228
+ return SERVICEABLE_DEGRADED_REASON.test(reason2 ?? "");
9168
9229
  }
9169
- function isServiceable(state, reason) {
9230
+ function isServiceable(state, reason2) {
9170
9231
  if (!SERVICEABLE_STATES.has(state)) return false;
9171
- return state !== "degraded" || degradedIsServiceable(reason);
9232
+ return state !== "degraded" || degradedIsServiceable(reason2);
9172
9233
  }
9173
9234
  var SERVICEABLE_STATES, SERVICEABLE_DEGRADED_REASON;
9174
9235
  var init_residentState = __esm({
@@ -9280,8 +9341,8 @@ function wakeDecision(probe) {
9280
9341
  }
9281
9342
  return { wait: false, why: `the resident is ${seen}, which no wake recovers from; not waiting` };
9282
9343
  }
9283
- function describeState(state, reason) {
9284
- return reason ? `${state} (${reason})` : state;
9344
+ function describeState(state, reason2) {
9345
+ return reason2 ? `${state} (${reason2})` : state;
9285
9346
  }
9286
9347
  function describeProbe(probe) {
9287
9348
  return probe.kind === "status" ? describeState(probe.state, probe.reason) : `unreachable (${probe.error})`;
@@ -9381,8 +9442,8 @@ var init_residentAdmin = __esm({
9381
9442
  init_secrets2();
9382
9443
  RESIDENTS_OFF_MESSAGE = "Resident repo environments aren't configured \u2014 set `execution.resident.baseUrl` in config.yaml.";
9383
9444
  NullResidentAdminClient = class {
9384
- constructor(reason = RESIDENTS_OFF_MESSAGE) {
9385
- this.reason = reason;
9445
+ constructor(reason2 = RESIDENTS_OFF_MESSAGE) {
9446
+ this.reason = reason2;
9386
9447
  }
9387
9448
  reason;
9388
9449
  answer() {
@@ -9431,6 +9492,20 @@ function wakePause(ms, signal2) {
9431
9492
  signal2?.addEventListener("abort", onAbort, { once: true });
9432
9493
  });
9433
9494
  }
9495
+ function reboundOf(value) {
9496
+ if (typeof value !== "object" || value === null) return void 0;
9497
+ const v = value;
9498
+ if (typeof v.from !== "string" || typeof v.to !== "string" || typeof v.pr !== "number") return void 0;
9499
+ if (!v.from || !v.to || !Number.isSafeInteger(v.pr) || v.pr <= 0) return void 0;
9500
+ return { from: v.from, to: v.to, pr: v.pr };
9501
+ }
9502
+ function rebindRefusedOf(value) {
9503
+ if (typeof value !== "object" || value === null) return void 0;
9504
+ const v = value;
9505
+ if (typeof v.to !== "string" || typeof v.pr !== "number" || typeof v.reason !== "string") return void 0;
9506
+ if (!v.to || !v.reason || !Number.isSafeInteger(v.pr) || v.pr <= 0) return void 0;
9507
+ return { to: v.to, pr: v.pr, reason: v.reason, why: typeof v.why === "string" ? v.why : "" };
9508
+ }
9434
9509
  async function parseResidentBody(res) {
9435
9510
  const text = await res.text();
9436
9511
  let data = {};
@@ -9620,10 +9695,12 @@ var init_resident = __esm({
9620
9695
  }
9621
9696
  /** Bind/reuse this thread's worktree. Legible errors for every named
9622
9697
  * refusal the service can answer with. Answers the binding the resident
9623
- * reported: the bound ref (authoritative — a differing refHint is ignored)
9624
- * and the sha the worktree is at. A 200 without both fields is a
9625
- * malformed resident (the attach contract always carries them) and is an
9626
- * error, never a half-bound executor. */
9698
+ * reported: the bound ref — the resident's word, not the hint's: a differing
9699
+ * refHint is ignored unless `ownPr` names it as the thread's own pull
9700
+ * request and the resident moves a default-bound thread onto it (item 16;
9701
+ * `rebound` / `rebindRefused` say which) — and the sha the worktree is at. A
9702
+ * 200 without both fields is a malformed resident (the attach contract
9703
+ * always carries them) and is an error, never a half-bound executor. */
9627
9704
  async attach(span) {
9628
9705
  const answer = await this.attachOnce(span);
9629
9706
  if (answer.ok) return answer.binding;
@@ -9640,6 +9717,8 @@ var init_resident = __esm({
9640
9717
  if (this.opts.readonly) body.readonly = true;
9641
9718
  if (this.opts.sha) body.sha = this.opts.sha;
9642
9719
  if (this.opts.reuse) body.reuse = true;
9720
+ if (this.opts.ownPr) body.ownPr = this.opts.ownPr;
9721
+ if (this.opts.refByDefault) body.refByDefault = true;
9643
9722
  const answered = await this.call("/attach", body, timeoutMs, void 0, span);
9644
9723
  const data = answered.data;
9645
9724
  const status3 = answered.status === 200 && typeof data.error === "string" && typeof data.status === "number" ? data.status : answered.status;
@@ -9648,6 +9727,8 @@ var init_resident = __esm({
9648
9727
  throw new Error(`resident attach: malformed answer for ${this.opts.resource} (missing ref/sha)`);
9649
9728
  }
9650
9729
  const trace = sanitizeGraftedSteps(data.trace);
9730
+ const rebound = reboundOf(data.rebound);
9731
+ const rebindRefused = rebindRefusedOf(data.rebindRefused);
9651
9732
  this.lastBinding = {
9652
9733
  ref: data.ref,
9653
9734
  sha: data.sha,
@@ -9655,7 +9736,9 @@ var init_resident = __esm({
9655
9736
  ...typeof data.user === "string" && data.user ? { user: data.user } : {},
9656
9737
  ...typeof data.container === "string" && data.container ? { container: data.container } : {},
9657
9738
  ...trace.length > 0 ? { trace } : {},
9658
- ...typeof data.attachMs === "number" && Number.isFinite(data.attachMs) ? { attachMs: data.attachMs } : {}
9739
+ ...typeof data.attachMs === "number" && Number.isFinite(data.attachMs) ? { attachMs: data.attachMs } : {},
9740
+ ...rebound !== void 0 ? { rebound } : {},
9741
+ ...rebindRefused !== void 0 ? { rebindRefused } : {}
9659
9742
  };
9660
9743
  return { ok: true, binding: this.lastBinding };
9661
9744
  }
@@ -10086,7 +10169,8 @@ async function makeExecutor(opts, ctx, span) {
10086
10169
  threadKey: ctx.threadKey,
10087
10170
  refHint: ctx.ref,
10088
10171
  readonly,
10089
- sha: ctx.headSha
10172
+ sha: ctx.headSha,
10173
+ ...ctx.ownPr !== void 0 ? { ownPr: ctx.ownPr } : {}
10090
10174
  },
10091
10175
  nonWarm,
10092
10176
  span
@@ -10174,7 +10258,7 @@ async function openResident(opts, nonWarm, span) {
10174
10258
  } catch (err2) {
10175
10259
  if (!(err2 instanceof ResidentNeedsRefError) || !err2.defaultRef) throw err2;
10176
10260
  byDefault = true;
10177
- executor = new ResidentExecutor({ ...opts, refHint: err2.defaultRef });
10261
+ executor = new ResidentExecutor({ ...opts, refHint: err2.defaultRef, refByDefault: true });
10178
10262
  try {
10179
10263
  binding = await executor.attach(span);
10180
10264
  } catch (again) {
@@ -10189,6 +10273,7 @@ async function openResident(opts, nonWarm, span) {
10189
10273
  }
10190
10274
  const where = `${opts.resource.replace(/^repo:/, "")} \xB7 ${binding.ref}@${binding.sha.slice(0, 7)}`;
10191
10275
  const why = byDefault ? " (repo default \u2014 no branch named)" : "";
10276
+ const moved = rebindLabel(binding);
10192
10277
  return {
10193
10278
  executor,
10194
10279
  resident: true,
@@ -10196,9 +10281,17 @@ async function openResident(opts, nonWarm, span) {
10196
10281
  ...binding.trace ? { trace: binding.trace } : {},
10197
10282
  ...binding.attachMs !== void 0 ? { attachMs: binding.attachMs } : {},
10198
10283
  binding,
10199
- note: nonWarm ? `resident ${nonWarm} \xB7 ${where}${why} \u2014 attached to the last snapshot` : `resident \xB7 ${where}${why}`
10284
+ note: nonWarm ? `resident ${nonWarm} \xB7 ${where}${why}${moved} \u2014 attached to the last snapshot` : `resident \xB7 ${where}${why}${moved}`
10200
10285
  };
10201
10286
  }
10287
+ function rebindLabel(binding) {
10288
+ if (binding.rebound) return ` \xB7 rebound to ${binding.rebound.to} (this thread's PR #${binding.rebound.pr})`;
10289
+ if (binding.rebindRefused) {
10290
+ const r = binding.rebindRefused;
10291
+ return ` \xB7 rebind to ${r.to} (this thread's PR #${r.pr}) refused: ${r.reason}`;
10292
+ }
10293
+ return "";
10294
+ }
10202
10295
  function residentOnboardedProbe(cfg, secrets = processSecrets) {
10203
10296
  const token2 = cfg?.baseUrl ? secrets.named(cfg.tokenEnv ?? "RESIDENT_OPERATOR_TOKEN") : void 0;
10204
10297
  if (!cfg?.baseUrl || !token2) return void 0;
@@ -12461,9 +12554,12 @@ function describeIssue(issue, root) {
12461
12554
  function wrapUntrusted(text) {
12462
12555
  return `${UNTRUSTED_PREAMBLE}
12463
12556
  ${UNTRUSTED_OPEN}
12464
- ${text}
12557
+ ${breakFenceMarkers(text)}
12465
12558
  ${UNTRUSTED_CLOSE}`;
12466
12559
  }
12560
+ function breakFenceMarkers(text) {
12561
+ return text.replaceAll(UNTRUSTED_CLOSE, "UNTRUSTED>> >").replaceAll(UNTRUSTED_OPEN, "<< <UNTRUSTED");
12562
+ }
12467
12563
  function renderText(cmd, output, opts = {}) {
12468
12564
  if (opts.surface === "chat" && cmd.renderChat) return cmd.renderChat(output);
12469
12565
  return cmd.render ? cmd.render(output) : renderCompact(cmd.id, output, opts);
@@ -12588,14 +12684,14 @@ var init_commandRegistry = __esm({
12588
12684
  });
12589
12685
  return fail("not_found", `unknown command: ${id}`);
12590
12686
  }
12591
- const done = (res, reason) => {
12687
+ const done = (res, reason2) => {
12592
12688
  this.audit({
12593
12689
  commandId: cmd.id,
12594
12690
  callerKind: caller.kind,
12595
12691
  callerId: caller.id,
12596
12692
  effect: cmd.effect,
12597
12693
  outcome: res.ok ? "ok" : res.error,
12598
- ...reason === void 0 ? {} : { reason }
12694
+ ...reason2 === void 0 ? {} : { reason: reason2 }
12599
12695
  });
12600
12696
  return res;
12601
12697
  };
@@ -14389,6 +14485,38 @@ function prOfEvents(events) {
14389
14485
  for (const e of events) if (e.type === "pr_opened") pr = { number: e.number, url: e.url };
14390
14486
  return pr;
14391
14487
  }
14488
+ function routeOfEvents(events) {
14489
+ const meta = events.find((e) => e.type === "run_meta");
14490
+ if (meta?.type !== "run_meta" || meta.agentSource !== "route") return void 0;
14491
+ const r = events.find((e) => e.type === "route");
14492
+ if (r?.type !== "route") return void 0;
14493
+ return {
14494
+ preset: r.preset,
14495
+ reason: r.reason,
14496
+ model: r.model,
14497
+ ...r.parts ? { parts: r.parts.map((p) => ({ preset: p.preset, text: p.text })) } : {},
14498
+ ...r.collapsed ? { collapsed: { presets: [...r.collapsed.presets] } } : {}
14499
+ };
14500
+ }
14501
+ function isRunRouteShape(v) {
14502
+ if (typeof v !== "object" || v === null) return false;
14503
+ const r = v;
14504
+ if (typeof r.preset !== "string" || typeof r.reason !== "string" || typeof r.model !== "string") return false;
14505
+ if (r.parts !== void 0) {
14506
+ if (!Array.isArray(r.parts)) return false;
14507
+ for (const p of r.parts) {
14508
+ if (typeof p !== "object" || p === null) return false;
14509
+ const part = p;
14510
+ if (typeof part.preset !== "string" || typeof part.text !== "string") return false;
14511
+ }
14512
+ }
14513
+ if (r.collapsed !== void 0) {
14514
+ const c = r.collapsed;
14515
+ if (typeof c !== "object" || c === null) return false;
14516
+ if (!Array.isArray(c.presets) || !c.presets.every((p) => typeof p === "string")) return false;
14517
+ }
14518
+ return true;
14519
+ }
14392
14520
  function isRunPullRequestShape(v) {
14393
14521
  if (typeof v !== "object" || v === null) return false;
14394
14522
  const p = v;
@@ -14551,6 +14679,7 @@ function isRunRecord(v) {
14551
14679
  return false;
14552
14680
  if (r.dispositions !== void 0 && !isFindingDispositionsShape(r.dispositions)) return false;
14553
14681
  if (r.reviewPost !== void 0 && !isReviewPostShape(r.reviewPost)) return false;
14682
+ if (r.route !== void 0 && !isRunRouteShape(r.route)) return false;
14554
14683
  if (r.profile !== void 0 && !isRunProfileRecord(r.profile)) return false;
14555
14684
  if (r.pr !== void 0 && !isRunPullRequestShape(r.pr)) return false;
14556
14685
  if (r.parentRunId !== void 0 && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
@@ -17473,13 +17602,13 @@ function bridgeMcpTools(server, client, tools, opts) {
17473
17602
  const bytes = Buffer.byteLength(text, "utf8");
17474
17603
  const wrapped = wrapUntrusted(clipped);
17475
17604
  if (!ok2) {
17476
- const refused5 = classifyError(new Error(`MCP ${server.name}/${t.name} reported an error:
17605
+ const refused6 = classifyError(new Error(`MCP ${server.name}/${t.name} reported an error:
17477
17606
  ${wrapped}`), {
17478
17607
  kind: "refused"
17479
17608
  });
17480
- span?.fail(refused5);
17609
+ span?.fail(refused6);
17481
17610
  span?.end("error", { ok: ok2, bytes });
17482
- throw refused5;
17611
+ throw refused6;
17483
17612
  }
17484
17613
  span?.end("ok", { ok: ok2, bytes });
17485
17614
  return wrapped;
@@ -17598,9 +17727,9 @@ var init_source = __esm({
17598
17727
  tools: bridgeMcpTools(server, client, tools, { budget })
17599
17728
  };
17600
17729
  } catch (err2) {
17601
- const reason = redactAndCap(err2 instanceof Error ? err2.message : String(err2), 160);
17730
+ const reason2 = redactAndCap(err2 instanceof Error ? err2.message : String(err2), 160);
17602
17731
  return {
17603
- outcome: { server: server.name, unavailable: reason },
17732
+ outcome: { server: server.name, unavailable: reason2 },
17604
17733
  tools: []
17605
17734
  };
17606
17735
  }
@@ -19725,12 +19854,12 @@ function renderResidentList(data) {
19725
19854
  const live = obj(rec2.live);
19726
19855
  const slug = String(rec2.resource ?? "").replace(/^repo:/, "");
19727
19856
  const state = String(live.state ?? "unknown");
19728
- const reason = String(live.reason ?? "");
19857
+ const reason2 = String(live.reason ?? "");
19729
19858
  const sha = typeof live.sha === "string" && live.sha ? ` \xB7 sha \`${live.sha.slice(0, 8)}\`` : "";
19730
19859
  const refreshed = typeof live.lastRefreshAt === "string" && live.lastRefreshAt ? ` \xB7 refreshed ${live.lastRefreshAt}` : "";
19731
19860
  const d = obj(live.disk);
19732
19861
  const disk = typeof d.usedKiB === "number" && typeof d.totalKiB === "number" ? ` \xB7 disk ${formatDiskGauge({ usedKiB: d.usedKiB, totalKiB: d.totalKiB })}` : "";
19733
- return `\u2022 \`${slug}\` \u2014 *${state}*${reason ? ` (${reason})` : ""} \xB7 ref \`${String(rec2.defaultRef ?? "?")}\`${sha}${refreshed}${disk}`;
19862
+ return `\u2022 \`${slug}\` \u2014 *${state}*${reason2 ? ` (${reason2})` : ""} \xB7 ref \`${String(rec2.defaultRef ?? "?")}\`${sha}${refreshed}${disk}`;
19734
19863
  });
19735
19864
  const out = [`*Resident repos* (${n(data.count)}/${n(data.cap)}):`, ...lines];
19736
19865
  const t = data.testOverrides;
@@ -19754,7 +19883,7 @@ function listWords(words) {
19754
19883
  async function settleProvisioning(deps, slug) {
19755
19884
  const sleep2 = deps.repo.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
19756
19885
  const resource = repoResourceId(slug);
19757
- const hint = (reason) => /provision-failed at (install|build|test)\b/.test(reason) ? `Fix the command table with \`repo reconfigure ${slug} --install "\u2026" --build "\u2026" --test "\u2026"\`, then \`repo rebuild ${slug}\`.` : `Not a command-table failure \u2014 retry with \`repo rebuild ${slug}\`; if it recurs, the resident or Cloudflare side is at fault (\`repo list\` shows the live state).`;
19886
+ const hint = (reason2) => /provision-failed at (install|build|test)\b/.test(reason2) ? `Fix the command table with \`repo reconfigure ${slug} --install "\u2026" --build "\u2026" --test "\u2026"\`, then \`repo rebuild ${slug}\`.` : `Not a command-table failure \u2014 retry with \`repo rebuild ${slug}\`; if it recurs, the resident or Cloudflare side is at fault (\`repo list\` shows the live state).`;
19758
19887
  for (let elapsed2 = 0; elapsed2 <= SETTLE_MAX_MS; elapsed2 += SETTLE_POLL_MS) {
19759
19888
  if (elapsed2 > 0) await sleep2(SETTLE_POLL_MS);
19760
19889
  const api = await deps.repo.admin();
@@ -19780,9 +19909,9 @@ async function settleProvisioning(deps, slug) {
19780
19909
  if (state === "onboarding") continue;
19781
19910
  if (state === "warm") return { ok: true, text: `\u2705 \`${slug}\` is warm \u2014 provisioned and attach-ready.` };
19782
19911
  if (state === "down") {
19783
- const reason = str4(r.data.reason ?? "no reason recorded");
19784
- return { ok: false, text: `\u274C \`${slug}\` failed to provision: ${reason}
19785
- ${hint(reason)}` };
19912
+ const reason2 = str4(r.data.reason ?? "no reason recorded");
19913
+ return { ok: false, text: `\u274C \`${slug}\` failed to provision: ${reason2}
19914
+ ${hint(reason2)}` };
19786
19915
  }
19787
19916
  return {
19788
19917
  ok: true,
@@ -20708,6 +20837,7 @@ function ledgerView(row, events) {
20708
20837
  ...m.sourceUrl !== void 0 ? { sourceUrl: m.sourceUrl } : {},
20709
20838
  ...m.userName !== void 0 ? { userName: m.userName } : {},
20710
20839
  ...m.parentRunId !== void 0 ? { parentRunId: m.parentRunId } : {},
20840
+ ...m.route !== void 0 ? { route: m.route } : {},
20711
20841
  ...m.parentInstanceId !== void 0 ? { parentInstanceId: m.parentInstanceId } : {},
20712
20842
  ...m.idempotencyKey !== void 0 ? { idempotencyKey: m.idempotencyKey } : {},
20713
20843
  ...row.stop ? { stop: { mode: row.stop, state: "stopping" } } : {},
@@ -22944,6 +23074,7 @@ function reclaimedRunRecord(input) {
22944
23074
  // included); a row from before profiles existed leaves the record without one.
22945
23075
  ...row.meta.profile && row.meta.agent ? { profile: { preset: row.meta.agent, ...row.meta.profile } } : {},
22946
23076
  ...row.meta.parentRunId !== void 0 ? { parentRunId: row.meta.parentRunId } : {},
23077
+ ...row.meta.route !== void 0 ? { route: row.meta.route } : {},
22947
23078
  ...row.meta.seed !== void 0 ? { seed: row.meta.seed } : {},
22948
23079
  // The row's place in its session log (item 53): the range stays open on a
22949
23080
  // record closed here, since the closer has no turn count to end it with.
@@ -22979,6 +23110,7 @@ function assembleRunRecord(input) {
22979
23110
  const atFinish = snap?.events ?? [];
22980
23111
  const events = seal && seal.events.length > 0 ? [...atFinish, ...seal.events] : atFinish;
22981
23112
  const pr = prOfEvents(events);
23113
+ const route3 = input.route ?? routeOfEvents(events);
22982
23114
  const fitted = fitRecordToBudget({
22983
23115
  id: run2.id,
22984
23116
  ...run2.label !== void 0 ? { label: run2.label } : {},
@@ -23015,6 +23147,7 @@ function assembleRunRecord(input) {
23015
23147
  ...input.reviewHead !== void 0 ? { reviewHead: input.reviewHead } : {},
23016
23148
  ...input.dispositions !== void 0 ? { dispositions: redactDispositions(input.dispositions) } : {},
23017
23149
  ...input.reviewPost !== void 0 ? { reviewPost: redactReviewPost(input.reviewPost) } : {},
23150
+ ...route3 !== void 0 ? { route: route3 } : {},
23018
23151
  ...input.profile !== void 0 ? { profile: input.profile } : {},
23019
23152
  ...input.parentRunId !== void 0 ? { parentRunId: input.parentRunId } : {},
23020
23153
  ...coordinatorFields(input.coordinator),
@@ -23038,6 +23171,7 @@ function writeTombstone(deps, ctx) {
23038
23171
  run: run2,
23039
23172
  registry,
23040
23173
  resume,
23174
+ route: route3,
23041
23175
  parentRunId,
23042
23176
  coordinator,
23043
23177
  seed
@@ -23055,6 +23189,7 @@ function writeTombstone(deps, ctx) {
23055
23189
  channelVisibility,
23056
23190
  repo: repoCtx.repo,
23057
23191
  profile: profileRecordOf(agent, profile),
23192
+ ...route3 !== void 0 ? { route: route3 } : {},
23058
23193
  ...parentRunId !== void 0 ? { parentRunId } : {},
23059
23194
  ...coordinator !== void 0 ? { coordinator } : {},
23060
23195
  ...seed !== void 0 ? { seed } : {},
@@ -23091,6 +23226,7 @@ function registerFinishRecord(deps, ctx) {
23091
23226
  reviewHead,
23092
23227
  dispositions,
23093
23228
  reviewPost,
23229
+ route: route3,
23094
23230
  parentRunId,
23095
23231
  coordinator,
23096
23232
  seed
@@ -23117,6 +23253,7 @@ function registerFinishRecord(deps, ctx) {
23117
23253
  ...reviewHead !== void 0 ? { reviewHead } : {},
23118
23254
  ...dispositions !== void 0 ? { dispositions } : {},
23119
23255
  ...reviewPost !== void 0 ? { reviewPost } : {},
23256
+ ...route3 !== void 0 ? { route: route3 } : {},
23120
23257
  ...parentRunId !== void 0 ? { parentRunId } : {},
23121
23258
  ...coordinator !== void 0 ? { coordinator } : {},
23122
23259
  ...seed !== void 0 ? { seed } : {}
@@ -23666,8 +23803,8 @@ async function mergePullRequest(pr, opts) {
23666
23803
  return { ok: true, sha };
23667
23804
  }
23668
23805
  if (res.status === 405 || res.status === 409 || res.status === 422) {
23669
- const reason = typeof body?.message === "string" && body.message ? body.message : redactAndCap(text, 300);
23670
- return { ok: false, status: res.status, reason };
23806
+ const reason2 = typeof body?.message === "string" && body.message ? body.message : redactAndCap(text, 300);
23807
+ return { ok: false, status: res.status, reason: reason2 };
23671
23808
  }
23672
23809
  throw new Error(`merge failed for ${pr.repo}#${pr.number}: HTTP ${res.status} ${redactAndCap(text, 300)}`);
23673
23810
  }
@@ -23980,9 +24117,12 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
23980
24117
  if (head.base) out.baseRef = head.base;
23981
24118
  if (head.size) out.prSize = head.size;
23982
24119
  if (head.facts) out.prDescription = head.facts;
23983
- if (inherited.source === "record" && !ref && head.ref) {
23984
- out.ref = head.ref;
23985
- out.refFromPr = true;
24120
+ if (inherited.source === "record") {
24121
+ out.prFromRecord = true;
24122
+ if (!ref && head.ref) {
24123
+ out.ref = head.ref;
24124
+ out.refFromPr = true;
24125
+ }
23986
24126
  }
23987
24127
  } else {
23988
24128
  out.prUnpostable = { number: inherited.number, reason: head.reason };
@@ -23991,6 +24131,10 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
23991
24131
  }
23992
24132
  return out;
23993
24133
  }
24134
+ function ownPrOf(ctx) {
24135
+ if (!ctx.prFromRecord || !ctx.refFromPr || ctx.pr === void 0 || ctx.ref === void 0) return void 0;
24136
+ return { number: ctx.pr, ref: ctx.ref };
24137
+ }
23994
24138
  function inheritedPr(thread, records, repo) {
23995
24139
  const turn = thread.pr && thread.pr.repo === repo ? { repo, number: thread.pr.number, source: "turn", at: thread.prAt } : void 0;
23996
24140
  const record2 = records?.pr && records.pr.repo === repo ? { repo, number: records.pr.number, source: "record", at: records.pr.at } : void 0;
@@ -25591,9 +25735,9 @@ var init_diffDigest2 = __esm({
25591
25735
  const listing = `git diff --numstat ${range} && echo ${NAME_STATUS_MARK} && git diff --name-status ${range}`;
25592
25736
  let raw = await ctx.executor.exec(listing);
25593
25737
  const incomplete = () => {
25594
- const reason = "the file listing exceeded the executor's output cap";
25595
- ctx.onDigest?.({ complete: false, base: baseName, reason });
25596
- return `diff_digest: ${reason} \u2014 the digest cannot state totals for this change (base \`${baseName}\`). Read the diff file by file (\`git diff --name-only ${baseName}...HEAD\`).`;
25738
+ const reason2 = "the file listing exceeded the executor's output cap";
25739
+ ctx.onDigest?.({ complete: false, base: baseName, reason: reason2 });
25740
+ return `diff_digest: ${reason2} \u2014 the digest cannot state totals for this change (base \`${baseName}\`). Read the diff file by file (\`git diff --name-only ${baseName}...HEAD\`).`;
25597
25741
  };
25598
25742
  if (OUTPUT_CUT_RE.test(raw)) return incomplete();
25599
25743
  let marker = NAME_STATUS_MARK_LINE.exec(raw);
@@ -25896,8 +26040,8 @@ ${clip2(c.body.trim(), 2e3)}`).join("\n\n")}` : "";
25896
26040
  async run(input, ctx) {
25897
26041
  const repo = repoOf(input);
25898
26042
  if (typeof repo !== "string") return `github_issue_create: ${repo.error}`;
25899
- const refused5 = writeGate("github_issue_create", ctx, repo);
25900
- if (refused5) return refused5;
26043
+ const refused6 = writeGate("github_issue_create", ctx, repo);
26044
+ if (refused6) return refused6;
25901
26045
  const title = String(input.title ?? "").trim();
25902
26046
  if (!title) return "github_issue_create: title is required.";
25903
26047
  try {
@@ -25933,8 +26077,8 @@ ${issue.url}`;
25933
26077
  async run(input, ctx) {
25934
26078
  const repo = repoOf(input);
25935
26079
  if (typeof repo !== "string") return `github_issue_update: ${repo.error}`;
25936
- const refused5 = writeGate("github_issue_update", ctx, repo);
25937
- if (refused5) return refused5;
26080
+ const refused6 = writeGate("github_issue_update", ctx, repo);
26081
+ if (refused6) return refused6;
25938
26082
  const number = numberOf(input);
25939
26083
  if (typeof number !== "number") return `github_issue_update: ${number.error}`;
25940
26084
  const patch = {
@@ -25970,8 +26114,8 @@ ${issue.url}`;
25970
26114
  async run(input, ctx) {
25971
26115
  const repo = repoOf(input);
25972
26116
  if (typeof repo !== "string") return `github_issue_comment: ${repo.error}`;
25973
- const refused5 = writeGate("github_issue_comment", ctx, repo);
25974
- if (refused5) return refused5;
26117
+ const refused6 = writeGate("github_issue_comment", ctx, repo);
26118
+ if (refused6) return refused6;
25975
26119
  const number = numberOf(input);
25976
26120
  if (typeof number !== "number") return `github_issue_comment: ${number.error}`;
25977
26121
  const body = String(input.body ?? "").trim();
@@ -25999,8 +26143,8 @@ ${url}`;
25999
26143
  async run(input, ctx) {
26000
26144
  const repo = repoOf(input);
26001
26145
  if (typeof repo !== "string") return `github_issue_delete: ${repo.error}`;
26002
- const refused5 = writeGate("github_issue_delete", ctx, repo);
26003
- if (refused5) return refused5;
26146
+ const refused6 = writeGate("github_issue_delete", ctx, repo);
26147
+ if (refused6) return refused6;
26004
26148
  const number = numberOf(input);
26005
26149
  if (typeof number !== "number") return `github_issue_delete: ${number.error}`;
26006
26150
  try {
@@ -26172,22 +26316,22 @@ function watchedChild(io, on) {
26172
26316
  return watched2;
26173
26317
  }
26174
26318
  async function spawnChild(deps, parent, request) {
26175
- const refused5 = (reason2, message3) => ({ kind: "refused", reason: reason2, message: message3 });
26319
+ const refused6 = (reason3, message3) => ({ kind: "refused", reason: reason3, message: message3 });
26176
26320
  if (parent.depth >= MAX_SPAWN_DEPTH) {
26177
- return refused5(
26321
+ return refused6(
26178
26322
  "spawn_depth",
26179
26323
  `a child run cannot spawn: this run is itself a child, and a tree is ${MAX_SPAWN_DEPTH} level deep \u2014 the run that started it is the one to ask`
26180
26324
  );
26181
26325
  }
26182
26326
  if (AGENTS[request.preset]?.identity === "write") {
26183
- return refused5(
26327
+ return refused6(
26184
26328
  "spawn_identity",
26185
26329
  `\`${request.preset}\` runs as a \`write\` identity \u2014 it pushes branches and opens pull requests \u2014 and a spawned child never writes: it reads this conversation and reports; the person who asked starts that work by hand with \`agent:${request.preset}\``
26186
26330
  );
26187
26331
  }
26188
26332
  const minutesLeft = Math.floor(parent.remainingMs / 6e4);
26189
26333
  if (minutesLeft < MIN_BOUNDARY_MINUTES) {
26190
- return refused5(
26334
+ return refused6(
26191
26335
  "spawn_budget",
26192
26336
  `this run has under ${MIN_BOUNDARY_MINUTES} minutes left \u2014 too little to hand a child, which could never run a command in it; wrap up instead`
26193
26337
  );
@@ -26195,13 +26339,13 @@ async function spawnChild(deps, parent, request) {
26195
26339
  const live = deps.registry.listActive().filter((s) => !s.finished && s.parentRunId === parent.runId).length;
26196
26340
  const cap = maxChildrenOf(deps.core.config.config.spawn);
26197
26341
  if (live >= cap) {
26198
- return refused5(
26342
+ return refused6(
26199
26343
  "spawn_fanout",
26200
26344
  `${live} child run${live === 1 ? " is" : "s are"} live already \u2014 the cap is ${cap} (\`spawn.maxChildren\`); wait for one to finish before spawning another`
26201
26345
  );
26202
26346
  }
26203
26347
  if (!parent.io.openThread) {
26204
- return refused5(
26348
+ return refused6(
26205
26349
  "spawn_unsupported",
26206
26350
  `the ${platformOf(parent.msg.channelId)} channel cannot open a thread of its own, so a child run cannot be spawned from it`
26207
26351
  );
@@ -26210,7 +26354,7 @@ async function spawnChild(deps, parent, request) {
26210
26354
  try {
26211
26355
  opened = await parent.io.openThread(childThreadLead(parent, request));
26212
26356
  } catch (err2) {
26213
- return refused5("spawn_failed", `the channel could not open the child's thread: ${describe3(err2)}`);
26357
+ return refused6("spawn_failed", `the channel could not open the child's thread: ${describe3(err2)}`);
26214
26358
  }
26215
26359
  const child = {
26216
26360
  channelId: parent.msg.channelId,
@@ -26265,10 +26409,10 @@ async function spawnChild(deps, parent, request) {
26265
26409
  ...opened.thread.sourceUrl !== void 0 ? { url: opened.thread.sourceUrl } : {}
26266
26410
  };
26267
26411
  }
26268
- if (first.kind === "threw") return refused5("spawn_failed", describe3(first.err));
26269
- const reason = first.outcome.refusal ?? "spawn_failed";
26270
- console.log(`[spawn] ${parent.msg.threadKey} run ${parent.runId}: ${request.preset} child not started (${reason})`);
26271
- return refused5(reason, lastReply ?? `the child ended (${first.outcome.status}) before it started`);
26412
+ if (first.kind === "threw") return refused6("spawn_failed", describe3(first.err));
26413
+ const reason2 = first.outcome.refusal ?? "spawn_failed";
26414
+ console.log(`[spawn] ${parent.msg.threadKey} run ${parent.runId}: ${request.preset} child not started (${reason2})`);
26415
+ return refused6(reason2, lastReply ?? `the child ended (${first.outcome.status}) before it started`);
26272
26416
  }
26273
26417
  function spawnCapabilityFor(deps, parent) {
26274
26418
  const outcomes = /* @__PURE__ */ new Map();
@@ -28169,8 +28313,8 @@ function attachRule(attachers) {
28169
28313
  "A request that asks for a file, a screenshot, a recording or an attachment to be posted, attached or sent back routes there too, however read-only the rest of it sounds; a read-only preset can describe a file but never post one."
28170
28314
  ].join(" ");
28171
28315
  }
28172
- function tidyReason(reason) {
28173
- const line3 = oneLine(redactAndCap(reason, ROUTE_REASON_CAP));
28316
+ function tidyReason(reason2) {
28317
+ const line3 = oneLine(redactAndCap(reason2, ROUTE_REASON_CAP));
28174
28318
  return line3.length > 0 ? line3 : "no reason given";
28175
28319
  }
28176
28320
  function parseRouteAnswer(raw, allowed2, compound) {
@@ -28183,21 +28327,21 @@ function parseRouteAnswer(raw, allowed2, compound) {
28183
28327
  }
28184
28328
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
28185
28329
  return { preset: void 0, reason: `not a single JSON object: ${tidyReason(trimmed)}` };
28186
- const { preset: named, reason, parts } = parsed;
28330
+ const { preset: named, reason: reason2, parts } = parsed;
28187
28331
  const preset = typeof named === "string" ? named : Array.isArray(parts) ? COMPOUND_PRESET : void 0;
28188
28332
  const inferred = typeof named !== "string" && preset === COMPOUND_PRESET;
28189
28333
  if (preset === void 0)
28190
28334
  return { preset: void 0, reason: `missing preset in the router's answer: ${tidyReason(trimmed)}` };
28191
28335
  const partsAnswer = preset === COMPOUND_PRESET && Array.isArray(parts);
28192
- if (typeof reason !== "string" && !partsAnswer)
28336
+ if (typeof reason2 !== "string" && !partsAnswer)
28193
28337
  return { preset: void 0, reason: `missing reason in the router's answer: ${tidyReason(trimmed)}` };
28194
- const tidy = typeof reason === "string" ? tidyReason(reason) : inferred ? "compound inferred from parts" : "no reason given";
28338
+ const tidy = typeof reason2 === "string" ? tidyReason(reason2) : inferred ? "compound inferred from parts" : "no reason given";
28195
28339
  if (preset === COMPOUND_PRESET) return parseCompound(parts, tidy, allowed2, compound);
28196
28340
  if (!allowed2.includes(preset))
28197
28341
  return { preset: void 0, reason: `router said "${tidyReason(preset)}", not a preset the requester may run` };
28198
28342
  return { preset, reason: tidy };
28199
28343
  }
28200
- function parseCompound(parts, reason, allowed2, offer) {
28344
+ function parseCompound(parts, reason2, allowed2, offer) {
28201
28345
  const rejected = (why) => ({
28202
28346
  preset: void 0,
28203
28347
  reason: `compound_rejected: ${why}`,
@@ -28221,8 +28365,8 @@ function parseCompound(parts, reason, allowed2, offer) {
28221
28365
  out.push({ preset, text: tidy });
28222
28366
  }
28223
28367
  const writer = out.find((p) => identityOf(p.preset) === "write");
28224
- if (writer) return { preset: writer.preset, reason, collapsed: { presets: out.map((p) => p.preset) } };
28225
- return { preset: COMPOUND_PRESET, reason, parts: out };
28368
+ if (writer) return { preset: writer.preset, reason: reason2, collapsed: { presets: out.map((p) => p.preset) } };
28369
+ return { preset: COMPOUND_PRESET, reason: reason2, parts: out };
28226
28370
  }
28227
28371
  async function route(input, model, opts = {}) {
28228
28372
  const { compound: offer, ...rest } = input;
@@ -28342,8 +28486,8 @@ async function routeRequest(deps, ctx) {
28342
28486
  }
28343
28487
  };
28344
28488
  }
28345
- function routedLabel(reason, collapsed) {
28346
- return `${ROUTED_LABEL_PREFIX} ${reason}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}`;
28489
+ function routedLabel(reason2, collapsed) {
28490
+ return `${ROUTED_LABEL_PREFIX} ${reason2}${collapsed ? ` (compound collapsed: ${collapsed.presets.join("+")})` : ""}`;
28347
28491
  }
28348
28492
  function routedPartLines(parts) {
28349
28493
  return parts.map((p) => {
@@ -29269,6 +29413,7 @@ async function attachRoundWorkspace(input) {
29269
29413
  repo: input.round.repo,
29270
29414
  ref: input.round.ref,
29271
29415
  headSha: input.round.headSha,
29416
+ ...input.round.ownPr !== void 0 ? { ownPr: input.round.ownPr } : {},
29272
29417
  ...input.round.reattach !== void 0 ? { reattach: input.round.reattach } : {}
29273
29418
  },
29274
29419
  input.span
@@ -29541,11 +29686,11 @@ ${carriedFooter(carried)}` : buildReviewPostBody(input.answer, verdict);
29541
29686
  try {
29542
29687
  await input.post(target, body);
29543
29688
  } catch (err2) {
29544
- const reason = err2 instanceof Error ? err2.message : String(err2);
29545
- console.error(`[review-post] ${logKey} failed for ${where}: ${reason}`);
29546
- await input.reply(`\u2139\uFE0F Review not posted to ${where}: ${reason} \u2014 this verdict is Slack-only.`).catch(() => {
29689
+ const reason2 = err2 instanceof Error ? err2.message : String(err2);
29690
+ console.error(`[review-post] ${logKey} failed for ${where}: ${reason2}`);
29691
+ await input.reply(`\u2139\uFE0F Review not posted to ${where}: ${reason2} \u2014 this verdict is Slack-only.`).catch(() => {
29547
29692
  });
29548
- return record2({ posted: false, reason });
29693
+ return record2({ posted: false, reason: reason2 });
29549
29694
  }
29550
29695
  console.log(
29551
29696
  `[review-post] ${logKey} \u2192 ${where} (${verdict?.verdict ?? "no verdict"})${carried ? ` carried ${carried.reviewed.slice(0, 7)} \u2192 ${pinned.slice(0, 7)}` : ""}`
@@ -30600,6 +30745,7 @@ async function reserveRun(deps, ctx) {
30600
30745
  }
30601
30746
  async function attachWorkspace(deps, ctx) {
30602
30747
  const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock3, agent, profile, repoCtx, root, reattach } = ctx;
30748
+ const ownPr = ownPrOf(repoCtx);
30603
30749
  let round2;
30604
30750
  try {
30605
30751
  round2 = await root.span("dispatch.workspace.attach", async (span) => {
@@ -30625,6 +30771,9 @@ async function attachWorkspace(deps, ctx) {
30625
30771
  repo: repoCtx.repo,
30626
30772
  ref: repoCtx.ref,
30627
30773
  headSha: repoCtx.headSha,
30774
+ // The thread's own pull request, when the ref is its head (resident-
30775
+ // repos item 16): the one reason the resident may move a binding.
30776
+ ...ownPr !== void 0 ? { ownPr } : {},
30628
30777
  ...reattach !== void 0 ? { reattach } : {}
30629
30778
  },
30630
30779
  logKey: msg.threadKey,
@@ -30804,6 +30953,7 @@ var init_provision = __esm({
30804
30953
  init_selfDescription();
30805
30954
  init_customInstructions();
30806
30955
  init_reviewRound();
30956
+ init_repoContext();
30807
30957
  init_runEvents();
30808
30958
  init_runRecord();
30809
30959
  init_inboxMessage();
@@ -32021,10 +32171,10 @@ var init_bridge2 = __esm({
32021
32171
  return this.openTools.has(callId);
32022
32172
  }
32023
32173
  /** End whatever tool spans a stopped pi left open, so no span outlives the run. */
32024
- closeOpenSpans(reason) {
32174
+ closeOpenSpans(reason2) {
32025
32175
  for (const [callId, open2] of this.openTools) {
32026
32176
  open2.span?.end("error", { callId, ok: false });
32027
- this.emit({ type: "tool_result", tool: open2.tool, ok: false, callId, summary: redactAndCap(reason) });
32177
+ this.emit({ type: "tool_result", tool: open2.tool, ok: false, callId, summary: redactAndCap(reason2) });
32028
32178
  }
32029
32179
  this.openTools.clear();
32030
32180
  this.vetted.clear();
@@ -32535,7 +32685,7 @@ async function runPiHarnessOpen(deps, run2) {
32535
32685
  run2.saveFacts?.(facts2);
32536
32686
  }
32537
32687
  };
32538
- const held = () => {
32688
+ const held2 = () => {
32539
32689
  if (transport) mirrored = transport.consumedOffset;
32540
32690
  save();
32541
32691
  };
@@ -32812,9 +32962,9 @@ ${stagedLine}` : prompt,
32812
32962
  if (!continuing) mirror.expectSeedEcho();
32813
32963
  }
32814
32964
  }
32815
- if (obs.message && await mirror.onMessage(obs.message, bridge.turns)) held();
32965
+ if (obs.message && await mirror.onMessage(obs.message, bridge.turns)) held2();
32816
32966
  if (obs.compaction) {
32817
- if (await mirror.onCompaction(obs.compaction, bridge.turns)) held();
32967
+ if (await mirror.onCompaction(obs.compaction, bridge.turns)) held2();
32818
32968
  if (!writeUp && !hardStopped && !catchingUp) {
32819
32969
  let notepad;
32820
32970
  let unavailable = false;
@@ -32994,8 +33144,8 @@ var init_harness = __esm({
32994
33144
  CALL_SEEN_TICK_MS = 50;
32995
33145
  CONTINUE_PROMPT = "Continue where you left off: the bot restarted mid-run, so re-check the effects of your last command before relying on them.";
32996
33146
  PromptRefused = class extends Error {
32997
- constructor(reason) {
32998
- super(`pi refused the prompt: ${reason}`);
33147
+ constructor(reason2) {
33148
+ super(`pi refused the prompt: ${reason2}`);
32999
33149
  this.name = "PromptRefused";
33000
33150
  }
33001
33151
  };
@@ -33466,9 +33616,9 @@ async function runCodingPrPostStep(input) {
33466
33616
  });
33467
33617
  return opened.created ? `\u{1F500} PR opened: ${opened.htmlUrl} (\`${branch2}\` \u2192 \`${base}\`)` : `\u{1F500} PR updated: ${opened.htmlUrl} \u2014 body re-rendered at \`${headSha.slice(0, 7)}\``;
33468
33618
  } catch (err2) {
33469
- const reason = err2 instanceof Error ? err2.message : String(err2);
33470
- console.error(`[pr-post] ${logKey} open/edit failed for ${repo} ${branch2}: ${reason}`);
33471
- return `\u26A0\uFE0F The branch \`${branch2}\` is pushed but the PR could not be opened: ${reason} \u2014 compare & open manually: ${compareUrl}`;
33619
+ const reason2 = err2 instanceof Error ? err2.message : String(err2);
33620
+ console.error(`[pr-post] ${logKey} open/edit failed for ${repo} ${branch2}: ${reason2}`);
33621
+ return `\u26A0\uFE0F The branch \`${branch2}\` is pushed but the PR could not be opened: ${reason2} \u2014 compare & open manually: ${compareUrl}`;
33472
33622
  }
33473
33623
  }
33474
33624
  if (prDescription && pushedBranch && !base) {
@@ -33722,6 +33872,7 @@ async function runLoop2(deps, ctx) {
33722
33872
  steer,
33723
33873
  wait,
33724
33874
  session,
33875
+ route: route3,
33725
33876
  parentRunId,
33726
33877
  coordinator,
33727
33878
  seed
@@ -34246,6 +34397,7 @@ async function runLoop2(deps, ctx) {
34246
34397
  ...reviewHead !== void 0 ? { reviewHead } : {},
34247
34398
  ...dispositions !== void 0 ? { dispositions } : {},
34248
34399
  ...reviewPost !== void 0 ? { reviewPost } : {},
34400
+ ...route3 !== void 0 ? { route: route3 } : {},
34249
34401
  ...parentRunId !== void 0 ? { parentRunId } : {},
34250
34402
  ...coordinator !== void 0 ? { coordinator } : {},
34251
34403
  ...seed !== void 0 ? { seed } : {}
@@ -34373,8 +34525,8 @@ async function shipPreflight(input) {
34373
34525
  }
34374
34526
  const info = await input.repoInfo(repo).catch(() => void 0);
34375
34527
  if (info?.allowAutoMerge !== false) {
34376
- const reason = info?.allowAutoMerge === true ? `auto-merge is enabled on \`${repo}\` \u2014 ship's approving LGTM triggers the org auto-approve workflow, and with auto-merge on the PR would merge with no human. Disable auto-merge on the repo to use ship.` : `could not verify that auto-merge is disabled on \`${repo}\` (the repository lookup failed or answered without the setting) \u2014 refusing fail-closed.`;
34377
- return refuse2("auto-merge", "not started (auto-merge)", `\u{1F6AB} ${reason}`);
34528
+ const reason2 = info?.allowAutoMerge === true ? `auto-merge is enabled on \`${repo}\` \u2014 ship's approving LGTM triggers the org auto-approve workflow, and with auto-merge on the PR would merge with no human. Disable auto-merge on the repo to use ship.` : `could not verify that auto-merge is disabled on \`${repo}\` (the repository lookup failed or answered without the setting) \u2014 refusing fail-closed.`;
34529
+ return refuse2("auto-merge", "not started (auto-merge)", `\u{1F6AB} ${reason2}`);
34378
34530
  }
34379
34531
  const task = shipTaskText(input.requestText, repo);
34380
34532
  if (repoCtx.prUnpostable?.reason === "unreachable") {
@@ -35808,6 +35960,13 @@ function stickyAgentOf(runs, onPi) {
35808
35960
  if (newest === void 0 || !continuable(newest) || !onPi(newest.agent)) return void 0;
35809
35961
  return newest.agent;
35810
35962
  }
35963
+ function threadRouteOf(runs, agent) {
35964
+ const newest = runs[0];
35965
+ if (newest === void 0 || !continuable(newest) || newest.agent !== agent || newest.route === void 0)
35966
+ return void 0;
35967
+ const { preset, reason: reason2, model } = newest.route;
35968
+ return { preset, reason: reason2, model };
35969
+ }
35811
35970
  function previousRunOf(runs, agent) {
35812
35971
  const run2 = runs.find((r) => r.agent === agent && continuable(r));
35813
35972
  if (run2 === void 0) return void 0;
@@ -36042,9 +36201,9 @@ async function dispatch(deps, msg, io, opts = {}) {
36042
36201
  const receivedAt = trace.receivedAt;
36043
36202
  const queuedBeforeMs = msg.originAt !== void 0 ? Math.max(0, receivedAt - msg.originAt) : void 0;
36044
36203
  const queued = queuedCaption("behind", opts.queuedBehindMs) ?? queuedCaption("before", queuedBeforeMs);
36045
- let refused5 = false;
36204
+ let refused6 = false;
36046
36205
  const refuse3 = (outcome, fn) => {
36047
- refused5 = true;
36206
+ refused6 = true;
36048
36207
  ended.refusal ??= outcome;
36049
36208
  return root.span("dispatch.refuse", fn, { attrs: { outcome } });
36050
36209
  };
@@ -36131,6 +36290,8 @@ async function dispatch(deps, msg, io, opts = {}) {
36131
36290
  agentSource = "route";
36132
36291
  }
36133
36292
  const routeEvent = routing.kind === "routed" ? routing.route : routing.rejected;
36293
+ if (routing.kind === "unrouted" && stickyAgent !== void 0 && agentSource === "sticky" && thread)
36294
+ route3 = threadRouteOf(thread, resolved.agentName);
36134
36295
  if (resume?.row.meta.route) route3 = resume.row.meta.route;
36135
36296
  if ((await authorizeAgent(deps, { msg, io, refuse: refuse3, agentName: resolved.agentName })).kind === "refused")
36136
36297
  return ended;
@@ -36481,6 +36642,12 @@ async function dispatch(deps, msg, io, opts = {}) {
36481
36642
  if (note) shell.setLabel(`${shell.label} \xB7 ${oneLine(note)}`);
36482
36643
  if (note && !resident)
36483
36644
  registry.publish(run2.id, { type: "run_note", kind: "cold_sandbox", summary: oneLine(note), at: clock3() });
36645
+ const bound = round2.selection.binding;
36646
+ const kept = bound?.rebindRefused;
36647
+ if (bound && kept) {
36648
+ const summary = `kept on ${bound.ref} \u2014 rebind to ${kept.to} (this thread's PR #${kept.pr}) refused: ${kept.reason}${kept.why ? ` \u2014 ${kept.why}` : ""}`;
36649
+ registry.publish(run2.id, { type: "run_note", kind: "rebind_refused", summary: oneLine(summary), at: clock3() });
36650
+ }
36484
36651
  console.log(`[run] ${msg.threadKey} user=${msg.userId} agent=${agent.name} model=${resolved.modelRef}`);
36485
36652
  setupCard = void 0;
36486
36653
  clearInterval(setupHeartbeat);
@@ -36502,6 +36669,7 @@ async function dispatch(deps, msg, io, opts = {}) {
36502
36669
  run: run2,
36503
36670
  registry,
36504
36671
  resume,
36672
+ ...route3 !== void 0 ? { route: route3 } : {},
36505
36673
  parentRunId,
36506
36674
  coordinator,
36507
36675
  seed
@@ -36592,6 +36760,7 @@ async function dispatch(deps, msg, io, opts = {}) {
36592
36760
  steer,
36593
36761
  wait,
36594
36762
  ...sessionTools ? { session: sessionTools } : {},
36763
+ ...route3 !== void 0 ? { route: route3 } : {},
36595
36764
  parentRunId,
36596
36765
  coordinator,
36597
36766
  seed,
@@ -36675,7 +36844,7 @@ async function dispatch(deps, msg, io, opts = {}) {
36675
36844
  const settled = settleThread(deps, { msg, admitted, runLoopStarted, control: registered?.control });
36676
36845
  if (settled.kind === "dropped") await tellDropped(root, settled.pending);
36677
36846
  const stopMode = settled.kind === "handed-on" ? void 0 : settled.stopMode;
36678
- ended.status = caught ? "failed" : refused5 ? "refused" : stopMode ? "stopped" : "completed";
36847
+ ended.status = caught ? "failed" : refused6 ? "refused" : stopMode ? "stopped" : "completed";
36679
36848
  root.end(caught ? "error" : "ok", { status: ended.status });
36680
36849
  if (restartRequest) {
36681
36850
  const pending2 = settled.kind === "handed-on" ? settled.pending : [];
@@ -37340,14 +37509,14 @@ function createLedgerWriteThrough(opts) {
37340
37509
  return !this.detached;
37341
37510
  }
37342
37511
  /** One warning, then silence: the run continues untracked. */
37343
- detach(reason) {
37512
+ detach(reason2) {
37344
37513
  if (this.detached) return;
37345
37514
  this.detached = true;
37346
37515
  this.broken = true;
37347
- warn(`[ledger] ${this.threadKey} run ${this.runId} detached: ${reason} \u2014 this run is not resumable`);
37516
+ warn(`[ledger] ${this.threadKey} run ${this.runId} detached: ${reason2} \u2014 this run is not resumable`);
37348
37517
  this.stopHeartbeat();
37349
37518
  live.delete(this);
37350
- if (reason.includes("(fenced)")) {
37519
+ if (reason2.includes("(fenced)")) {
37351
37520
  this.fencedOut = true;
37352
37521
  if (this.onFenced && !this.fencedTold) {
37353
37522
  this.fencedTold = true;
@@ -38447,6 +38616,35 @@ var init_invokedAsScript = __esm({
38447
38616
  }
38448
38617
  });
38449
38618
 
38619
+ // ../../src/channels/slack/threadTurns.ts
38620
+ function threadTurns(messages, opts) {
38621
+ const kept = [];
38622
+ for (const mm of messages) {
38623
+ if (opts.skipTs !== void 0 && mm.ts === opts.skipTs) continue;
38624
+ const raw = mm.text ?? "";
38625
+ const text = opts.botUserId ? raw.replaceAll(`<@${opts.botUserId}>`, "").trim() : raw;
38626
+ if (STATUS_PREFIXES.some((p) => text.startsWith(p))) continue;
38627
+ const files = mm.bot_id ? void 0 : mm.files;
38628
+ if (!text && !files?.length) continue;
38629
+ const at = mm.ts !== void 0 && Number.isFinite(Number(mm.ts)) ? Math.round(Number(mm.ts) * 1e3) : void 0;
38630
+ kept.push({
38631
+ role: mm.bot_id ? "assistant" : "user",
38632
+ text,
38633
+ ...at !== void 0 ? { at } : {},
38634
+ ...mm.user !== void 0 ? { user: mm.user } : {},
38635
+ ...mm.bot_id !== void 0 ? { botId: mm.bot_id } : {},
38636
+ files
38637
+ });
38638
+ }
38639
+ return kept;
38640
+ }
38641
+ var init_threadTurns = __esm({
38642
+ "../../src/channels/slack/threadTurns.ts"() {
38643
+ "use strict";
38644
+ init_reply();
38645
+ }
38646
+ });
38647
+
38450
38648
  // ../../src/core/statusBudget.ts
38451
38649
  function createStatusBudget(opts) {
38452
38650
  const now = opts.now ?? Date.now;
@@ -39587,7 +39785,7 @@ var init_slack = __esm({
39587
39785
  "../../src/channels/slack.ts"() {
39588
39786
  "use strict";
39589
39787
  init_dispatcher();
39590
- init_reply();
39788
+ init_threadTurns();
39591
39789
  init_statusBudget();
39592
39790
  init_requestTrace();
39593
39791
  init_clock();
@@ -39810,18 +40008,7 @@ ${file.text}`));
39810
40008
  ts: this.ev.threadTs,
39811
40009
  limit: 50
39812
40010
  })).messages ?? [];
39813
- const kept = [];
39814
- for (const m of thread) {
39815
- const mm = m;
39816
- if (mm.ts === this.ev.ts) continue;
39817
- const raw = mm.text ?? "";
39818
- const text = this.ev.botUserId ? raw.replaceAll(`<@${this.ev.botUserId}>`, "").trim() : raw;
39819
- if (STATUS_PREFIXES.some((p) => text.startsWith(p))) continue;
39820
- const files = mm.bot_id ? void 0 : mm.files;
39821
- if (!text && !files?.length) continue;
39822
- const at = mm.ts !== void 0 && Number.isFinite(Number(mm.ts)) ? Math.round(Number(mm.ts) * 1e3) : void 0;
39823
- kept.push({ role: mm.bot_id ? "assistant" : "user", text, ...at !== void 0 ? { at } : {}, files });
39824
- }
40011
+ const kept = threadTurns(thread, { skipTs: this.ev.ts, botUserId: this.ev.botUserId });
39825
40012
  const imagesByIndex = [];
39826
40013
  const documentsByIndex = [];
39827
40014
  let imagesLeft = MAX_HISTORY_IMAGES;
@@ -41340,15 +41527,15 @@ function createResidentsViewHandler(client, shell, trace) {
41340
41527
  };
41341
41528
  bound.residents().then((r) => {
41342
41529
  if (r.status !== 200) {
41343
- const reason = (typeof r.data.error === "string" ? r.data.error : JSON.stringify(r.data)).slice(
41530
+ const reason2 = (typeof r.data.error === "string" ? r.data.error : JSON.stringify(r.data)).slice(
41344
41531
  0,
41345
41532
  UPSTREAM_REASON_MAX
41346
41533
  );
41347
41534
  if (r.status === 503) {
41348
- plainEnd(503, reason);
41535
+ plainEnd(503, reason2);
41349
41536
  return;
41350
41537
  }
41351
- plainEnd(502, `resident Worker answered ${r.status} to /residents: ${reason}`);
41538
+ plainEnd(502, `resident Worker answered ${r.status} to /residents: ${reason2}`);
41352
41539
  return;
41353
41540
  }
41354
41541
  const residents = Array.isArray(r.data.residents) ? r.data.residents : [];
@@ -41430,8 +41617,8 @@ function createCostsViewHandler(service, shell) {
41430
41617
  res.writeHead(200, WEB_HTML_HEADERS);
41431
41618
  res.end(shell(`${report2.label} spend`, { page: "costs", report: report2, groups }));
41432
41619
  }).catch((err2) => {
41433
- const reason = (err2 instanceof Error ? err2.message : String(err2)).slice(0, UPSTREAM_REASON_MAX2);
41434
- plain2(res, 502, `cost sources unavailable: ${reason}`);
41620
+ const reason2 = (err2 instanceof Error ? err2.message : String(err2)).slice(0, UPSTREAM_REASON_MAX2);
41621
+ plain2(res, 502, `cost sources unavailable: ${reason2}`);
41435
41622
  });
41436
41623
  return true;
41437
41624
  };
@@ -41512,8 +41699,8 @@ function createDeliveryViewHandler(deps, shell) {
41512
41699
  res.writeHead(200, WEB_HTML_HEADERS);
41513
41700
  res.end(html);
41514
41701
  }).catch((err2) => {
41515
- const reason = (err2 instanceof Error ? err2.message : String(err2)).slice(0, UPSTREAM_REASON_MAX3);
41516
- plain3(res, 502, `delivery sources unavailable: ${reason}`);
41702
+ const reason2 = (err2 instanceof Error ? err2.message : String(err2)).slice(0, UPSTREAM_REASON_MAX3);
41703
+ plain3(res, 502, `delivery sources unavailable: ${reason2}`);
41517
41704
  });
41518
41705
  return true;
41519
41706
  };
@@ -42797,6 +42984,11 @@ async function launchResumes(deps, resumable, opts) {
42797
42984
  });
42798
42985
  const dispatchFn = opts.dispatchFn ?? (await Promise.resolve().then(() => (init_dispatcher(), dispatcher_exports))).dispatch;
42799
42986
  const outcome = { launched: [], closed: [] };
42987
+ const start3 = (row, dispatched, failure2) => {
42988
+ void dispatched.catch(
42989
+ (err2) => warn(`[resume] ${row.runId} ${row.threadKey}: ${failure2}: ${err2 instanceof Error ? err2.message : String(err2)}`)
42990
+ ).finally(() => opts.onDone?.(row.runId));
42991
+ };
42800
42992
  for (const run2 of resumable) {
42801
42993
  const { row } = run2;
42802
42994
  const agent = opts.agentFor(row.meta.agent);
@@ -42819,11 +43011,7 @@ async function launchResumes(deps, resumable, opts) {
42819
43011
  `[resume] ${row.runId} ${row.threadKey}: restarting from its request (killed while attaching; ${run2.inbox.length} follow-up(s) pending)`
42820
43012
  );
42821
43013
  outcome.launched.push(row.runId);
42822
- void dispatchFn(deps, restored.msg, io2, { restart: { row, inbox: run2.inbox } }).catch(
42823
- (err2) => warn(
42824
- `[resume] ${row.runId} ${row.threadKey}: restart dispatch failed: ${err2 instanceof Error ? err2.message : String(err2)}`
42825
- )
42826
- );
43014
+ start3(row, dispatchFn(deps, restored.msg, io2, { restart: { row, inbox: run2.inbox } }), "restart dispatch failed");
42827
43015
  continue;
42828
43016
  }
42829
43017
  const plan3 = planResume({ transcript: run2.transcript, lastStep: run2.lastStep, tools: knownToolsFor(agent) });
@@ -42850,11 +43038,7 @@ async function launchResumes(deps, resumable, opts) {
42850
43038
  plan3.kind === "finish" ? `[resume] ${row.runId} ${row.threadKey}: finishing (the model had answered at step ${plan3.step}; running the post-steps with its answer)` : `[resume] ${row.runId} ${row.threadKey}: resuming (${plan3.stepRecorded ? "settling" : "running fresh"} step ${plan3.step}, ${plan3.settlements.length} call(s), ${Math.round(plan3.remainingMs / 6e4)} min left)`
42851
43039
  );
42852
43040
  outcome.launched.push(row.runId);
42853
- void dispatchFn(deps, resumeMessage(row, inputTextOf(run2.events)), io, { resume: ctx }).catch(
42854
- (err2) => warn(
42855
- `[resume] ${row.runId} ${row.threadKey}: dispatch failed: ${err2 instanceof Error ? err2.message : String(err2)}`
42856
- )
42857
- );
43041
+ start3(row, dispatchFn(deps, resumeMessage(row, inputTextOf(run2.events)), io, { resume: ctx }), "dispatch failed");
42858
43042
  }
42859
43043
  return outcome;
42860
43044
  async function closeWith(run2, why) {
@@ -42864,6 +43048,7 @@ async function launchResumes(deps, resumable, opts) {
42864
43048
  } catch (err2) {
42865
43049
  warn(`[resume] ${run2.row.runId}: could not close: ${err2 instanceof Error ? err2.message : String(err2)}`);
42866
43050
  }
43051
+ opts.onDone?.(run2.row.runId);
42867
43052
  }
42868
43053
  }
42869
43054
  var init_resumeLaunch = __esm({
@@ -42875,6 +43060,60 @@ var init_resumeLaunch = __esm({
42875
43060
  }
42876
43061
  });
42877
43062
 
43063
+ // ../../src/core/runLedger/takeover.ts
43064
+ var LedgerTakeover;
43065
+ var init_takeover = __esm({
43066
+ "../../src/core/runLedger/takeover.ts"() {
43067
+ "use strict";
43068
+ LedgerTakeover = class {
43069
+ listed = false;
43070
+ waiters = [];
43071
+ /** Handed to the launcher; each leaves when `done` names it. */
43072
+ resuming = /* @__PURE__ */ new Set();
43073
+ /** The latest listing's rows under another generation's lease. */
43074
+ elsewhere = /* @__PURE__ */ new Set();
43075
+ get settled() {
43076
+ return this.listed;
43077
+ }
43078
+ /** No ledger to take over from: settled with nothing pending. */
43079
+ settle() {
43080
+ this.listed = true;
43081
+ for (const wake2 of this.waiters.splice(0)) wake2();
43082
+ }
43083
+ /** One reclaim pass's outcome: its resumable runs join the pending set (until
43084
+ * `done`), its listing of rows live elsewhere replaces the previous one, and
43085
+ * the takeover counts as settled. */
43086
+ take(outcome) {
43087
+ for (const r of outcome.resumable) this.resuming.add(r.row.runId);
43088
+ this.elsewhere = new Set(outcome.liveElsewhere.map((r) => r.runId));
43089
+ this.settle();
43090
+ }
43091
+ /** The run's resume is over — its dispatch settled or the launcher closed it. */
43092
+ done(runId3) {
43093
+ this.resuming.delete(runId3);
43094
+ }
43095
+ pending(runId3) {
43096
+ return this.resuming.has(runId3) || this.elsewhere.has(runId3);
43097
+ }
43098
+ whenSettled(withinMs) {
43099
+ if (this.listed) return Promise.resolve(true);
43100
+ return new Promise((resolve12) => {
43101
+ const timer = setTimeout(() => {
43102
+ const at = this.waiters.indexOf(wake2);
43103
+ if (at >= 0) this.waiters.splice(at, 1);
43104
+ resolve12(false);
43105
+ }, withinMs);
43106
+ const wake2 = () => {
43107
+ clearTimeout(timer);
43108
+ resolve12(true);
43109
+ };
43110
+ this.waiters.push(wake2);
43111
+ });
43112
+ }
43113
+ };
43114
+ }
43115
+ });
43116
+
42878
43117
  // ../../src/core/nullChannelIo.ts
42879
43118
  function nullChannelIO(logKey, log = console.log) {
42880
43119
  let children = 0;
@@ -42954,18 +43193,18 @@ function presentedBearer(headers) {
42954
43193
  return apiKey ? apiKey : void 0;
42955
43194
  }
42956
43195
  function decideDoor(method, path, headers, bearers) {
42957
- const refused5 = (shape2, status3, code, message3, runId3) => ({
43196
+ const refused6 = (shape2, status3, code, message3, runId3) => ({
42958
43197
  ok: false,
42959
43198
  code,
42960
43199
  response: refusalResponse(shape2, status3, code, message3),
42961
43200
  ...runId3 !== void 0 ? { runId: runId3 } : {}
42962
43201
  });
42963
43202
  const shape = proxyShapeOf(path);
42964
- if (!shape) return refused5(void 0, 404, "not_found", "no model proxy at this path");
42965
- if ((method ?? "GET").toUpperCase() !== "POST") return refused5(shape, 405, "method_not_allowed", "POST only");
43203
+ if (!shape) return refused6(void 0, 404, "not_found", "no model proxy at this path");
43204
+ if ((method ?? "GET").toUpperCase() !== "POST") return refused6(shape, 405, "method_not_allowed", "POST only");
42966
43205
  const presented = presentedBearer(headers);
42967
43206
  if (presented === void 0) {
42968
- return refused5(
43207
+ return refused6(
42969
43208
  shape,
42970
43209
  401,
42971
43210
  "missing_bearer",
@@ -42976,15 +43215,15 @@ function decideDoor(method, path, headers, bearers) {
42976
43215
  if (verdict.ok) return { ok: true, shape, grant: verdict.grant, turns: verdict.turns };
42977
43216
  switch (verdict.reason) {
42978
43217
  case "malformed":
42979
- return refused5(shape, 401, "malformed_bearer", "the bearer is not a run bearer");
43218
+ return refused6(shape, 401, "malformed_bearer", "the bearer is not a run bearer");
42980
43219
  case "unknown_run":
42981
- return refused5(shape, 404, "unknown_run", "the bearer names a run this bot does not hold", verdict.runId);
43220
+ return refused6(shape, 404, "unknown_run", "the bearer names a run this bot does not hold", verdict.runId);
42982
43221
  case "unknown_bearer":
42983
- return refused5(shape, 401, "unknown_bearer", "the bearer was not minted for its run", verdict.runId);
43222
+ return refused6(shape, 401, "unknown_bearer", "the bearer was not minted for its run", verdict.runId);
42984
43223
  case "expired":
42985
- return refused5(shape, 403, "expired", "the bearer expired with the run's budget", verdict.runId);
43224
+ return refused6(shape, 403, "expired", "the bearer expired with the run's budget", verdict.runId);
42986
43225
  case "revoked":
42987
- return refused5(shape, 403, "revoked", "the run ended and its bearer with it", verdict.runId);
43226
+ return refused6(shape, 403, "revoked", "the run ended and its bearer with it", verdict.runId);
42988
43227
  }
42989
43228
  }
42990
43229
  function pinRequest(shape, body, grant2) {
@@ -43125,9 +43364,9 @@ function meteredStream(source2, hooks) {
43125
43364
  controller.error(err2);
43126
43365
  }
43127
43366
  },
43128
- cancel(reason) {
43129
- settle2(() => hooks.onError(reason ?? new Error("cancelled")));
43130
- void reader.cancel(reason).catch(() => {
43367
+ cancel(reason2) {
43368
+ settle2(() => hooks.onError(reason2 ?? new Error("cancelled")));
43369
+ void reader.cancel(reason2).catch(() => {
43131
43370
  });
43132
43371
  }
43133
43372
  });
@@ -43579,8 +43818,8 @@ var init_toolRules = __esm({
43579
43818
  READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
43580
43819
  NONE_REACH = /* @__PURE__ */ new Set();
43581
43820
  allowed = { verdict: "allowed" };
43582
- refused4 = (reason) => ({ verdict: "refused", reason });
43583
- outside = (reason) => ({ verdict: "outside-profile", reason });
43821
+ refused4 = (reason2) => ({ verdict: "refused", reason: reason2 });
43822
+ outside = (reason2) => ({ verdict: "outside-profile", reason: reason2 });
43584
43823
  isRecord5 = (v) => typeof v === "object" && v !== null;
43585
43824
  CREDENTIAL_FILE = /(^|[\s/'"`])(\.git\/)?(github-credentials|\.git-credentials)($|[\s'"`|;&])/;
43586
43825
  ENV_DUMP = /(^|[;&|(]\s*)((printenv|env)(\s+-[A-Za-z0-9-]+)*|export\s+-p|declare\s+-[a-zA-Z]*[px][a-zA-Z]*|set)\s*($|[|;&>)])|\/proc\/(self|\d+)\/environ|process\.env(?![.[\w])/;
@@ -43618,13 +43857,13 @@ function relayedToolDefinitions(harness) {
43618
43857
  function authorizeToolCall(harness, ask) {
43619
43858
  harness.gateSaw(ask.toolCallId);
43620
43859
  const blocked = harness.toolsBlocked();
43621
- const refuse3 = (reason) => {
43860
+ const refuse3 = (reason2) => {
43622
43861
  harness.emit({
43623
43862
  type: "run_note",
43624
43863
  kind: "tool_refused",
43625
- summary: redactAndCap(`${ask.tool} refused: ${reason}`, 300)
43864
+ summary: redactAndCap(`${ask.tool} refused: ${reason2}`, 300)
43626
43865
  });
43627
- return { allow: false, reason };
43866
+ return { allow: false, reason: reason2 };
43628
43867
  };
43629
43868
  if (blocked !== void 0) return refuse3(blocked);
43630
43869
  if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
@@ -43756,19 +43995,42 @@ function bearerOf(headers) {
43756
43995
  const m = value ? /^Bearer\s+(\S+)$/i.exec(value.trim()) : null;
43757
43996
  return m ? m[1] : void 0;
43758
43997
  }
43759
- function admitHarnessRequest(deps, headers) {
43760
- const presented = bearerOf(headers);
43761
- if (!presented) return { ok: false, response: { status: 401, body: { error: "missing_bearer" } } };
43998
+ function judge2(deps, presented) {
43762
43999
  const verdict = deps.bearers.verify(presented);
43763
44000
  if (!verdict.ok) {
43764
44001
  const status3 = verdict.reason === "unknown_run" ? 404 : verdict.reason === "malformed" ? 401 : verdict.reason === "unknown_bearer" ? 401 : 403;
43765
- return { ok: false, response: { status: status3, body: { error: verdict.reason } } };
44002
+ return {
44003
+ ok: false,
44004
+ status: status3,
44005
+ reason: verdict.reason,
44006
+ ...verdict.reason === "malformed" ? {} : { runId: verdict.runId }
44007
+ };
43766
44008
  }
43767
44009
  const runId3 = verdict.grant.runId;
43768
- if (!deps.harnesses.get(runId3))
43769
- return { ok: false, response: { status: 404, body: { error: "run_not_on_harness" } } };
44010
+ if (!deps.harnesses.get(runId3)) return { ok: false, status: 404, reason: "run_not_on_harness", runId: runId3 };
43770
44011
  return { ok: true, runId: runId3 };
43771
44012
  }
44013
+ function held(path, reason2, runId3) {
44014
+ const response = path === HARNESS_TOOL_PATH ? { status: 202, body: { pending: true, reason: reason2 } } : { status: 503, body: { error: reason2 }, headers: { "retry-after": String(HELD_RETRY_AFTER_S) } };
44015
+ return { ok: false, response, held: { reason: reason2, ...runId3 !== void 0 ? { runId: runId3 } : {} } };
44016
+ }
44017
+ async function admitHarnessRequest(deps, headers, path) {
44018
+ const presented = bearerOf(headers);
44019
+ if (!presented) return { ok: false, response: { status: 401, body: { error: "missing_bearer" } } };
44020
+ let verdict = judge2(deps, presented);
44021
+ if (verdict.ok) return verdict;
44022
+ if (!RESUME_CAN_OPEN.has(verdict.reason)) return refused5(verdict);
44023
+ if (!deps.takeover.settled) {
44024
+ await deps.takeover.whenSettled(DOOR_HOLD_MS);
44025
+ verdict = judge2(deps, presented);
44026
+ if (verdict.ok) return verdict;
44027
+ if (!RESUME_CAN_OPEN.has(verdict.reason)) return refused5(verdict);
44028
+ if (!deps.takeover.settled) return held(path, "reclaim_pending", verdict.runId);
44029
+ }
44030
+ if (verdict.runId !== void 0 && deps.takeover.pending(verdict.runId))
44031
+ return held(path, "run_resuming", verdict.runId);
44032
+ return refused5(verdict);
44033
+ }
43772
44034
  function askOf(body) {
43773
44035
  if (typeof body !== "object" || body === null) return void 0;
43774
44036
  const b = body;
@@ -43780,7 +44042,7 @@ async function handleHarnessRequest(deps, req) {
43780
44042
  const wantsGet = req.path === HARNESS_TOOLS_PATH;
43781
44043
  if ((req.method ?? "GET") !== (wantsGet ? "GET" : "POST"))
43782
44044
  return { status: 405, body: { error: "method_not_allowed" } };
43783
- const door = admitHarnessRequest(deps, req.headers);
44045
+ const door = await admitHarnessRequest(deps, req.headers, req.path);
43784
44046
  if (!door.ok) return door.response;
43785
44047
  const harness = deps.harnesses.get(door.runId);
43786
44048
  if (wantsGet) return { status: 200, body: { tools: relayedToolDefinitions(harness) } };
@@ -43807,14 +44069,17 @@ function createHarnessRoutesHandler(deps) {
43807
44069
  const log = deps.log ?? ((line3) => console.log(line3));
43808
44070
  return (req, res) => {
43809
44071
  const json2 = (r) => {
43810
- res.writeHead(r.status, { "content-type": "application/json", "cache-control": "no-store" });
44072
+ res.writeHead(r.status, { ...r.headers, "content-type": "application/json", "cache-control": "no-store" });
43811
44073
  res.end(JSON.stringify(r.body));
43812
44074
  };
43813
44075
  void (async () => {
43814
44076
  const path = (req.url ?? "/").split("?")[0];
43815
- const door = admitHarnessRequest({ ...deps, log }, req.headers);
44077
+ const door = await admitHarnessRequest({ ...deps, log }, req.headers, path);
43816
44078
  if (!door.ok) {
43817
- log(`[harness] ${door.response.status} ${ROUTE_WORD[path] ?? "other"} \u2014 ${String(door.response.body.error)}`);
44079
+ const word = ROUTE_WORD[path] ?? "other";
44080
+ log(
44081
+ door.held ? `[harness] ${door.response.status} ${word} \u2014 held (${door.held.reason})${door.held.runId ? ` run=${door.held.runId}` : ""}` : `[harness] ${door.response.status} ${word} \u2014 ${String(door.response.body.error)}`
44082
+ );
43818
44083
  json2(door.response);
43819
44084
  req.resume();
43820
44085
  return;
@@ -43840,22 +44105,30 @@ function createHarnessRoutesHandler(deps) {
43840
44105
  });
43841
44106
  };
43842
44107
  }
43843
- var HARNESS_TOOLS_PATH, HARNESS_AUTHORIZE_PATH, HARNESS_TOOL_PATH, HARNESS_PATHS, MAX_HARNESS_BODY_BYTES, ROUTE_WORD;
44108
+ var HARNESS_TOOLS_PATH, HARNESS_AUTHORIZE_PATH, HARNESS_TOOL_PATH, HARNESS_PATHS, MAX_HARNESS_BODY_BYTES, DOOR_HOLD_MS, HELD_RETRY_AFTER_S, ROUTE_WORD, RESUME_CAN_OPEN, refused5;
43844
44109
  var init_harnessRoutes = __esm({
43845
44110
  "../../src/channels/harnessRoutes.ts"() {
43846
44111
  "use strict";
43847
44112
  init_relay();
44113
+ init_drain();
43848
44114
  init_http();
43849
44115
  HARNESS_TOOLS_PATH = "/harness/tools";
43850
44116
  HARNESS_AUTHORIZE_PATH = "/harness/authorize";
43851
44117
  HARNESS_TOOL_PATH = "/harness/tool";
43852
44118
  HARNESS_PATHS = [HARNESS_TOOLS_PATH, HARNESS_AUTHORIZE_PATH, HARNESS_TOOL_PATH];
43853
44119
  MAX_HARNESS_BODY_BYTES = 4 * 1024 * 1024;
44120
+ DOOR_HOLD_MS = HANDOFF_BUDGET_MS + 4e3;
44121
+ HELD_RETRY_AFTER_S = 2;
43854
44122
  ROUTE_WORD = {
43855
44123
  [HARNESS_TOOLS_PATH]: "tools",
43856
44124
  [HARNESS_AUTHORIZE_PATH]: "authorize",
43857
44125
  [HARNESS_TOOL_PATH]: "tool"
43858
44126
  };
44127
+ RESUME_CAN_OPEN = /* @__PURE__ */ new Set(["unknown_run", "unknown_bearer", "run_not_on_harness"]);
44128
+ refused5 = (j) => ({
44129
+ ok: false,
44130
+ response: { status: j.status, body: { error: j.reason } }
44131
+ });
43859
44132
  }
43860
44133
  });
43861
44134
 
@@ -44714,7 +44987,7 @@ async function merge(body, deps, subject2) {
44714
44987
  const headSha = normalizeHead(body.headSha);
44715
44988
  if (headSha === void 0) return json(400, { ok: false, error: "headSha must be the approved head (7 to 40 hex)" });
44716
44989
  const at = (deps.clock ?? systemClock)();
44717
- const refused5 = (reason) => json(200, { ok: true, outcome: "refused", reason, at });
44990
+ const refused6 = (reason2) => json(200, { ok: true, outcome: "refused", reason: reason2, at });
44718
44991
  const instance = await deps.instances.get(id.value);
44719
44992
  if (!instance) return json(404, { ok: false, error: "unknown_instance" });
44720
44993
  const unit = await unitRowOf(deps, instance, body.unit);
@@ -44723,12 +44996,12 @@ async function merge(body, deps, subject2) {
44723
44996
  const log = deps.log ?? console.log;
44724
44997
  const actor = resolveActor({ surface: "http", subjectId: subject2 }, deps.grantsFor);
44725
44998
  if (!authorize(actor, PLAN_MERGE_ACTION, { type: "command", id: "coordinator.merge" }).allow)
44726
- return refused5(
44999
+ return refused6(
44727
45000
  `the runner holds no ${PLAN_MERGE_ACTION} grant (grants["http:${subject2}"] in config.yaml) \u2014 a person merges`
44728
45001
  );
44729
45002
  const planBranch = parsePlanBranch(row.branch);
44730
45003
  if (instance.plan === void 0 || planBranch === void 0 || planBranch.planId !== instance.plan.id)
44731
- return refused5(
45004
+ return refused6(
44732
45005
  `\`${row.branch}\` is not a branch of plan \`${instance.plan?.id ?? "(none)"}\` \u2014 waits for a person's merge`
44733
45006
  );
44734
45007
  const pr = { repo: instance.repo, number: body.prNumber };
@@ -44742,12 +45015,12 @@ async function merge(body, deps, subject2) {
44742
45015
  if (facts2 === void 0)
44743
45016
  return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
44744
45017
  if (isReleasePullRequest(facts2))
44745
- return refused5(`${where} is the release pull request \u2014 always a person's merge, never the runner's`);
44746
- if (facts2.state !== "open") return refused5(`${where} is ${facts2.state}`);
45018
+ return refused6(`${where} is the release pull request \u2014 always a person's merge, never the runner's`);
45019
+ if (facts2.state !== "open") return refused6(`${where} is ${facts2.state}`);
44747
45020
  if (facts2.headRef !== row.branch)
44748
- return refused5(`${where} heads \`${facts2.headRef ?? "?"}\`, not the unit's branch \`${row.branch}\``);
45021
+ return refused6(`${where} heads \`${facts2.headRef ?? "?"}\`, not the unit's branch \`${row.branch}\``);
44749
45022
  if (facts2.headSha === void 0 || !sameCommit2(facts2.headSha, headSha))
44750
- return refused5(
45023
+ return refused6(
44751
45024
  `the head of ${where} moved: \`${facts2.headSha?.slice(0, 7) ?? "?"}\` is not the approved \`${headSha.slice(0, 7)}\``
44752
45025
  );
44753
45026
  const approved = await reviewPostedAt(deps, pr, "approve", headSha);
@@ -44758,7 +45031,7 @@ async function merge(body, deps, subject2) {
44758
45031
  message: `the reviews of ${where} could not be read`,
44759
45032
  at
44760
45033
  });
44761
- if (!approved) return refused5(`no approving review by the bot stands on ${where} at \`${headSha.slice(0, 7)}\``);
45034
+ if (!approved) return refused6(`no approving review by the bot stands on ${where} at \`${headSha.slice(0, 7)}\``);
44762
45035
  const checks = await deps.fetchCommitChecks(instance.repo, headSha).catch(() => void 0);
44763
45036
  if (checks === void 0)
44764
45037
  return json(502, {
@@ -44767,7 +45040,7 @@ async function merge(body, deps, subject2) {
44767
45040
  message: `the checks at ${headSha.slice(0, 7)} could not be read`,
44768
45041
  at
44769
45042
  });
44770
- if (checks.failed.length > 0) return refused5(`CI is red at \`${headSha.slice(0, 7)}\`: ${checks.failed.join(", ")}`);
45043
+ if (checks.failed.length > 0) return refused6(`CI is red at \`${headSha.slice(0, 7)}\`: ${checks.failed.join(", ")}`);
44771
45044
  if (checks.total === 0)
44772
45045
  return json(200, {
44773
45046
  ok: true,
@@ -44795,7 +45068,7 @@ async function merge(body, deps, subject2) {
44795
45068
  log(
44796
45069
  `[coordinator] ${instance.id} ${row.unit}: GitHub refused the merge of ${where} (HTTP ${merged.status}): ${merged.reason}`
44797
45070
  );
44798
- return refused5(`GitHub refused the merge of ${where} (HTTP ${merged.status}): ${merged.reason}`);
45071
+ return refused6(`GitHub refused the merge of ${where} (HTTP ${merged.status}): ${merged.reason}`);
44799
45072
  }
44800
45073
  log(
44801
45074
  `[coordinator] ${instance.id} ${row.unit}: merged ${where} at ${headSha.slice(0, 7)} \u2192 ${merged.sha.slice(0, 7)}`
@@ -45376,6 +45649,7 @@ async function runBot() {
45376
45649
  );
45377
45650
  }
45378
45651
  const threadsElsewhere = new ThreadsElsewhere();
45652
+ const takeover = new LedgerTakeover();
45379
45653
  const residentAdmin = capabilities.residents ? residentAdminFromConfig(config, processSecrets) : new NullResidentAdminClient();
45380
45654
  const residentAdminClient = "unavailable" in residentAdmin ? new NullResidentAdminClient(residentAdmin.unavailable) : residentAdmin;
45381
45655
  const spanLog = createSpanLog();
@@ -45486,7 +45760,7 @@ async function runBot() {
45486
45760
  secrets: processSecrets,
45487
45761
  clock: systemClock
45488
45762
  });
45489
- const harnessRoutes = createHarnessRoutesHandler({ bearers: runBearers, harnesses });
45763
+ const harnessRoutes = createHarnessRoutesHandler({ bearers: runBearers, harnesses, takeover });
45490
45764
  const coordinatorAdmin = createAdminCoordinatorHandler({
45491
45765
  tokens: processSecrets.get("SWITCHBOARD_INGRESS_TOKENS"),
45492
45766
  grantsFor: (id) => config.grantsFor(id),
@@ -45764,6 +46038,7 @@ async function runBot() {
45764
46038
  `[resume] ${run2.row.runId} ${run2.row.threadKey} closed interrupted (${why})${closed.ok ? "" : ` \u2014 finish refused (${closed.reason})`}`
45765
46039
  );
45766
46040
  },
46041
+ onDone: (runId3) => takeover.done(runId3),
45767
46042
  log: (l) => console.log(l),
45768
46043
  warn: (w) => console.warn(w)
45769
46044
  });
@@ -45776,11 +46051,14 @@ async function runBot() {
45776
46051
  log: (l) => console.log(l),
45777
46052
  warn: (w) => console.warn(w)
45778
46053
  });
46054
+ takeover.take(bootReclaim);
45779
46055
  await guardCards(bootReclaim);
45780
46056
  const client = ledgerReclaim.client;
45781
46057
  setForeignLiveCardsSource(
45782
46058
  async () => (await client.listLive()).filter((r) => r.ownerGen !== generation && r.leaseUntil > systemClock()).flatMap((r) => r.card ? [r.card] : [])
45783
46059
  );
46060
+ } else {
46061
+ takeover.settle();
45784
46062
  }
45785
46063
  await app.start();
45786
46064
  if (ledgerReclaim && bootReclaim) {
@@ -45791,6 +46069,7 @@ async function runBot() {
45791
46069
  log: (l) => console.log(l),
45792
46070
  warn: (w) => console.warn(w),
45793
46071
  onOutcome: async (outcome) => {
46072
+ takeover.take(outcome);
45794
46073
  await guardCards(outcome);
45795
46074
  await launch(outcome);
45796
46075
  }
@@ -45903,6 +46182,7 @@ var init_index = __esm({
45903
46182
  init_boot();
45904
46183
  init_resumeLaunch();
45905
46184
  init_threadsElsewhere();
46185
+ init_takeover();
45906
46186
  init_nullChannelIo();
45907
46187
  init_registry();
45908
46188
  init_trace();