@coreplane/switchboard 1.213.0 → 1.214.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.
package/dist/cli.js CHANGED
@@ -1404,19 +1404,19 @@ function clampBashTimeout(requested) {
1404
1404
  return Math.min(Math.max(Math.trunc(requested), BASH_TIMEOUT_MIN_MS), BASH_TIMEOUT_MAX_MS);
1405
1405
  }
1406
1406
  function bashBudgetWithinRun(wantedMs, remainingMs) {
1407
- const secs = (ms) => Math.max(0, Math.round(ms / 1e3));
1407
+ const secs2 = (ms) => Math.max(0, Math.round(ms / 1e3));
1408
1408
  const cap = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
1409
1409
  if (cap < BASH_TIMEOUT_MIN_MS) {
1410
1410
  return {
1411
1411
  kind: "exhausted",
1412
- note: `run budget exhausted \u2014 ${secs(remainingMs)}s of wall clock left, inside the ${secs(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
1412
+ note: `run budget exhausted \u2014 ${secs2(remainingMs)}s of wall clock left, inside the ${secs2(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
1413
1413
  };
1414
1414
  }
1415
1415
  if (cap >= wantedMs) return { kind: "unchanged" };
1416
1416
  return {
1417
1417
  kind: "clipped",
1418
1418
  timeoutMs: cap,
1419
- note: `[timeout clipped to ${secs(cap)}s \u2014 the run's wall clock ends in ${secs(remainingMs)}s]`
1419
+ note: `[timeout clipped to ${secs2(cap)}s \u2014 the run's wall clock ends in ${secs2(remainingMs)}s]`
1420
1420
  };
1421
1421
  }
1422
1422
  function bashTimeoutNote(timeoutMs) {
@@ -1758,6 +1758,31 @@ function runawayTurnCap(maxMinutes) {
1758
1758
  function loopBudget(maxMinutes) {
1759
1759
  return { maxMinutes, maxTurns: runawayTurnCap(maxMinutes) };
1760
1760
  }
1761
+ function conductorSystem(siblings) {
1762
+ const readers = siblings.filter((a) => a.identity !== "write");
1763
+ const writers = siblings.filter((a) => a.identity === "write");
1764
+ const rows = readers.map(
1765
+ (a) => `- \`${a.name}\`${machineNeedsRepo(a.machine) ? " (needs the repository)" : ""}: ${a.description}`
1766
+ );
1767
+ return `You are Switchboard's conductor: you coordinate other runs instead of doing the work yourself, answering a request from Slack.
1768
+
1769
+ You have no workspace and no shell. Your tools: \`spawn_run\` (start a child run), \`send_to_run\` (steer a live child: your text reaches it as a follow-up at its next step), \`await_runs\` (wait for your children to end and get each end \u2014 its status and final reply \u2014 back as data), \`list_runs\` (the runs you may see \u2014 your own children by default), \`get_run_status\` (one run: whether it is running, what it is doing, and its final reply once it finished), the GitHub reads \u2014 \`github_repos\`, \`github_tree\` / \`github_file\` (browse and read our repositories), \`github_search_code\`, \`github_issue_list\` / \`github_issue_get\` \u2014 \`web_fetch\` (read a public URL), and \`update_status\`.
1770
+
1771
+ WHAT A CHILD IS. A child is an ordinary Switchboard run started as the person who asked you \u2014 exactly the run they could start by hand with \`agent:<preset>\` \u2014 in a thread of its own in this channel, visible to everyone there, with its own status card and run page, and under their permissions: a preset they may not run, a repository they may not use, or a profile a boundary caps is refused in the child's thread, and the refusal comes back to you as the tool result naming the gate. Children cannot spawn children. You may have a few live at once (the deployment's \`spawn.maxChildren\`, three by default); a spawn past the cap is refused until one finishes. A child's wall clock is capped by what is left of yours.
1772
+
1773
+ THE PRESETS a child can run \u2014 a child reads, so only a preset whose identity is \`none\` or \`read\`:
1774
+ ${rows.join("\n")}
1775
+
1776
+ A preset that writes \u2014 ${writers.map((a) => `\`${a.name}\``).join(", ")} \u2014 is refused by name (\`spawn_identity\`): a spawned child never holds a write credential, so pushing a branch or opening a pull request is the requester's to start by hand with \`agent:<preset>\`; say so in your answer instead of spawning it.
1777
+
1778
+ ROUTED COMPOUNDS. A request may arrive already split: the router found independent parts, and the message ends with the line "Routed as a compound request: N independent parts" followed by a numbered list, one part per line as \`<preset>\`: <text>. Spawn exactly those children \u2014 one \`spawn_run\` per line, the preset as listed, the line's text as the child's prompt (it already stands alone; add the repository where the preset needs one) \u2014 then \`await_runs\` them all and compile. Never merge, drop or add a part; a part whose spawn is refused is reported as refused, by the gate's name.
1779
+
1780
+ HOW TO WORK. Fan out, await, compile. Read the request and split it into children only where the parts are independent; a request one preset answers is one child. Spawn each child with a prompt that says what it should do, and the repository where the preset needs one: a child starts from this conversation's text so far \u2014 every user and assistant turn before your call, never your tool calls, their results or your thinking \u2014 and your prompt is its one new turn, so tell it what to do rather than repeat what was said. Then call \`await_runs\` once with every child's id: it returns when all of them have ended, or earlier \u2014 at the edge of your own budget, at a stop, or when a follow-up lands in this thread \u2014 and \`ended\` says which; a child still running at the cut keeps running (name it in your answer, or await again after a follow-up). Steer a child with \`send_to_run\` when the request changes or a child is heading the wrong way. A child that ended \u2014 finished, failed, interrupted by a restart \u2014 is reported as it ended and never restarted; spawn a new child if the work still matters. Then compile: one answer from the write-ups \`await_runs\` returned. Never do a child's job yourself, and never claim a child finished or found something you did not read from \`await_runs\` or \`get_run_status\`.
1781
+
1782
+ Maintain the user-facing status card with the update_status tool: one item per child (\u25CB pending, \u2731 running, \u2713 finished \u2014 only once await_runs or get_run_status said so).
1783
+
1784
+ Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks). Your final message is posted to Slack: lead with the outcome, then one line per child \u2014 its preset, its thread, its status and its result in a sentence \u2014 and what is still running, if anything.`;
1785
+ }
1761
1786
  function presetDoor(def) {
1762
1787
  if (def.routable !== false) return "routed";
1763
1788
  return def.name === COMPOUND_PRESET ? "compound" : "directive";
@@ -1769,7 +1794,7 @@ function getAgent(name) {
1769
1794
  }
1770
1795
  return a;
1771
1796
  }
1772
- var MACHINE_CLASSES, IDENTITIES, HARNESSES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, CONDUCTOR_SYSTEM, COMPOUND_PRESET, AGENTS;
1797
+ var MACHINE_CLASSES, IDENTITIES, HARNESSES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, COMPOUND_PRESET, WORK_PRESETS, AGENTS;
1773
1798
  var init_registry = __esm({
1774
1799
  "../../src/agents/registry.ts"() {
1775
1800
  "use strict";
@@ -1797,6 +1822,8 @@ EVERY PR includes one that already exists when you push \u2014 opened by a perso
1797
1822
  RESIDENT_TOOLCHAIN = `The resident image carries ${IMAGE_TOOLCHAIN}; plus yarn and bun \u2014 and no Docker.`;
1798
1823
  SHOW_FILES = `Files the person should SEE go through the attach_file tool: a screenshot from \`playwright screenshot\`, a rendered PDF, a recording \u2014 it posts the workspace file into this conversation, where an image renders inline. Use it whenever you produce an image worth showing (a visual change, a rendered page, a before/after); a link to a file on GitHub is not a picture.
1799
1824
  SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshots, or the change is visual, every capture is attached here with attach_file AND published on the pull request \u2014 commit the images to an assets branch (never the PR's own diff) and reference them from the description's validation section or a PR comment so they render inline there too \u2014 unless the request names one destination. Never attach a subset and link the rest.
1825
+ Whole files: up to 1 GiB where the artifact store is configured (a recording, a large PDF), 10 MiB otherwise \u2014 the tool's result says which applies; over the limit, link to the file.
1826
+ Files the person dropped on the thread that were too large to show you inline are already in ./attachments/ in your workspace when the turn's text names them (a video for ffmpeg, a large PDF, a zip); read them from there \u2014 never ask for a re-upload.
1800
1827
  Text stays in your message; do not attach what you can say.`;
1801
1828
  CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
1802
1829
 
@@ -1953,26 +1980,11 @@ READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch,
1953
1980
  Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending) once you have it, and update items as they start (\u2731) and finish (\u2713 \u2014 only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
1954
1981
 
1955
1982
  Report outcomes faithfully: a check you could not run is "could not check", never a guess. Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks \u2014 render the claim table as aligned rows inside a code block). Your final message is posted to Slack: lead with the overall verdict in one line, then the claim table, then what a follow-up should do.`;
1956
- CONDUCTOR_SYSTEM = `You are Switchboard's conductor: you coordinate other runs instead of doing the work yourself, answering a request from Slack.
1957
-
1958
- You have no workspace and no shell. Your tools: \`spawn_run\` (start a child run), \`send_to_run\` (steer a live child: your text reaches it as a follow-up at its next step), \`await_runs\` (wait for your children to end and get each end \u2014 its status and final reply \u2014 back as data), \`list_runs\` (the runs you may see \u2014 your own children by default), \`get_run_status\` (one run: whether it is running, what it is doing, and its final reply once it finished), the GitHub reads \u2014 \`github_repos\`, \`github_tree\` / \`github_file\` (browse and read our repositories), \`github_search_code\`, \`github_issue_list\` / \`github_issue_get\` \u2014 \`web_fetch\` (read a public URL), and \`update_status\`.
1959
-
1960
- WHAT A CHILD IS. A child is an ordinary Switchboard run started as the person who asked you \u2014 exactly the run they could start by hand with \`agent:<preset>\` \u2014 in a thread of its own in this channel, visible to everyone there, with its own status card and run page, and under their permissions: a preset they may not run, a repository they may not use, or a profile a boundary caps is refused in the child's thread, and the refusal comes back to you as the tool result naming the gate. Children cannot spawn children. You may have a few live at once (the deployment's \`spawn.maxChildren\`, three by default); a spawn past the cap is refused until one finishes. A child's wall clock is capped by what is left of yours.
1961
-
1962
- THE PRESETS a child can run: \`research\` (a question the web or our repositories answer), \`coding\` (implement a change and open a pull request; needs the repository), \`review\` (review a pull request; needs its URL), \`explore\` (a long, read-only investigation with a shell; needs the repository), \`general\` (a quick answer with the GitHub tools), \`ship\` (coding, review and fixes until a pull request is merge-ready; needs the repository).
1963
-
1964
- ROUTED COMPOUNDS. A request may arrive already split: the router found independent parts, and the message ends with the line "Routed as a compound request: N independent parts" followed by a numbered list, one part per line as \`<preset>\`: <text>. Spawn exactly those children \u2014 one \`spawn_run\` per line, the preset as listed, the line's text as the child's prompt (it already stands alone; add the repository where the preset needs one) \u2014 then \`await_runs\` them all and compile. Never merge, drop or add a part; a part whose spawn is refused is reported as refused, by the gate's name.
1965
-
1966
- HOW TO WORK. Fan out, await, compile. Read the request and split it into children only where the parts are independent; a request one preset answers is one child. Spawn each child with a self-contained prompt \u2014 everything it needs, since it sees none of this thread \u2014 and the repository where the preset needs one. Then call \`await_runs\` once with every child's id: it returns when all of them have ended, or earlier \u2014 at the edge of your own budget, at a stop, or when a follow-up lands in this thread \u2014 and \`ended\` says which; a child still running at the cut keeps running (name it in your answer, or await again after a follow-up). Steer a child with \`send_to_run\` when the request changes or a child is heading the wrong way. A child that ended \u2014 finished, failed, interrupted by a restart \u2014 is reported as it ended and never restarted; spawn a new child if the work still matters. Then compile: one answer from the write-ups \`await_runs\` returned. Never do a child's job yourself, and never claim a child finished or found something you did not read from \`await_runs\` or \`get_run_status\`.
1967
-
1968
- Maintain the user-facing status card with the update_status tool: one item per child (\u25CB pending, \u2731 running, \u2713 finished \u2014 only once await_runs or get_run_status said so).
1969
-
1970
- Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks). Your final message is posted to Slack: lead with the outcome, then one line per child \u2014 its preset, its thread, its status and its result in a sentence \u2014 and what is still running, if anything.`;
1971
1983
  COMPOUND_PRESET = "conductor";
1972
- AGENTS = {
1984
+ WORK_PRESETS = {
1973
1985
  general: {
1974
1986
  name: "general",
1975
- description: "Default assistant on the configured model: answers directly, reads the org's repos and manages their issues over GitHub, reads URLs. No workspace or shell.",
1987
+ description: "Default assistant: answers directly, reads URLs, manages issues; the preset for any question the org's GitHub answers. No workspace or shell.",
1976
1988
  system: GENERAL_SYSTEM,
1977
1989
  toolset: "assistant",
1978
1990
  // The GitHub tools are REST in the bot process, so a general ask never
@@ -2047,7 +2059,7 @@ Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks
2047
2059
  },
2048
2060
  research: {
2049
2061
  name: "research",
2050
- description: "Answers questions with web search, URL reading, and read access to the org's repos and issues over GitHub. No workspace.",
2062
+ description: "Answers questions that need the web (search, URL reading); GitHub for context, not for a question GitHub alone answers. No workspace.",
2051
2063
  system: RESEARCH_SYSTEM,
2052
2064
  toolset: "web",
2053
2065
  machine: "none",
@@ -2073,11 +2085,14 @@ Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks
2073
2085
  // would expire between them, so the 2× write buys reads for the whole run.
2074
2086
  cacheTtl: "1h"
2075
2087
  // No built-in effort: the deployment decides, as for coding.
2076
- },
2088
+ }
2089
+ };
2090
+ AGENTS = {
2091
+ ...WORK_PRESETS,
2077
2092
  conductor: {
2078
2093
  name: "conductor",
2079
2094
  description: "Coordinates other runs: spawns child runs as the requester \u2014 each in a thread of its own, under their permissions \u2014 follows them, and reports. No workspace or shell.",
2080
- system: CONDUCTOR_SYSTEM,
2095
+ system: conductorSystem(Object.values(WORK_PRESETS)),
2081
2096
  toolset: "conductor",
2082
2097
  // Nothing is provisioned and no credential minted: the run tools call the
2083
2098
  // dispatcher, the GitHub reads are REST in the bot process.
@@ -2821,6 +2836,8 @@ function validateRouting(routing, providers) {
2821
2836
  throw new Error(`config.yaml: routing.${key} is not a known key`);
2822
2837
  if (routing.auto !== void 0 && typeof routing.auto !== "boolean")
2823
2838
  throw new Error("config.yaml: routing.auto must be true or false");
2839
+ if (routing.answer !== void 0 && !ROUTE_ANSWER_MODES.includes(routing.answer))
2840
+ throw new Error(`config.yaml: routing.answer must be ${ROUTE_ANSWER_MODES.join(" or ")}`);
2824
2841
  if (routing.model !== void 0) {
2825
2842
  if (typeof routing.model !== "string" || !routing.model.includes("/"))
2826
2843
  throw new Error("config.yaml: routing.model must be a <provider>/<model> ref");
@@ -2917,7 +2934,7 @@ function validateRuntimeOverrides(ro) {
2917
2934
  }
2918
2935
  }
2919
2936
  }
2920
- var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
2937
+ var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTE_ANSWER_MODES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
2921
2938
  var init_validate = __esm({
2922
2939
  "../../src/config/validate.ts"() {
2923
2940
  "use strict";
@@ -2966,7 +2983,8 @@ var init_validate = __esm({
2966
2983
  };
2967
2984
  BOUNDARY_KEYS = { maxMinutes: true, maxIdentity: true, machines: true };
2968
2985
  MIN_BOUNDARY_MINUTES = 2;
2969
- ROUTING_KEYS = { auto: true, model: true };
2986
+ ROUTE_ANSWER_MODES = ["tool", "text"];
2987
+ ROUTING_KEYS = { auto: true, model: true, answer: true };
2970
2988
  SHIP_KEYS = { maxRounds: true, maxMinutes: true };
2971
2989
  SPAWN_KEYS = { maxChildren: true };
2972
2990
  }
@@ -2987,7 +3005,7 @@ function identityWithin(identity2, cap) {
2987
3005
  return IDENTITY_ORDER[identity2] <= IDENTITY_ORDER[cap];
2988
3006
  }
2989
3007
  function budgetedAgent(agent, profile) {
2990
- return { ...agent, maxMinutes: profile.minutes };
3008
+ return { ...agent, maxMinutes: profile.minutes, maxTurns: runawayTurnCap(profile.minutes) };
2991
3009
  }
2992
3010
  function intersectBoundaries(layers) {
2993
3011
  const out = {};
@@ -3058,6 +3076,7 @@ var IDENTITY_ORDER, MACHINE_ORDER;
3058
3076
  var init_profile = __esm({
3059
3077
  "../../src/config/profile.ts"() {
3060
3078
  "use strict";
3079
+ init_registry();
3061
3080
  IDENTITY_ORDER = { none: 0, read: 1, write: 2 };
3062
3081
  MACHINE_ORDER = { none: 0, blank: 1, "repo-cold": 2, "repo-resident": 3 };
3063
3082
  }
@@ -4833,7 +4852,14 @@ var init_profile2 = __esm({
4833
4852
  images: z2.enum(IMAGE_MODES).default("build"),
4834
4853
  /** The Cloudflare Access application in front of the bot's dashboards, when
4835
4854
  * there is one: the team domain the JWT is issued by and the app's AUD. */
4836
- access: z2.object({ teamDomain: hostname, aud: z2.string().regex(/^[0-9a-f]{64}$/) }).optional()
4855
+ access: z2.object({ teamDomain: hostname, aud: z2.string().regex(/^[0-9a-f]{64}$/) }).optional(),
4856
+ /** The artifact store's bucket (docs/reference/specs/execution.md item 20), when the installation
4857
+ * has one: the bot Worker's template binds it as `ARTIFACTS` and `deploy` creates it before the
4858
+ * upload. The bot's runtime config (`artifacts.r2.bucket`) must name the same bucket — the two
4859
+ * are held equal by `artifacts check`, not by the profile. R2's own bucket-name rules. */
4860
+ artifacts: z2.object({
4861
+ bucket: z2.string().regex(/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/, "an R2 bucket name: 3\u201363 lowercase letters, digits and hyphens")
4862
+ }).optional()
4837
4863
  });
4838
4864
  EXAMPLE_ACCOUNT = "00000000000000000000000000000000";
4839
4865
  }
@@ -4906,7 +4932,11 @@ function planDeploy(opts, host, loaded, images) {
4906
4932
  setEnv: w.preflight ? { [w.preflight.baseUrlEnv]: w.baseUrl, ...opts.force ? { [w.preflight.forceEnv]: "1" } : {} } : {},
4907
4933
  ...opts.force && w.preflight ? { forcedBy: w.preflight.forceEnv } : {},
4908
4934
  requiredEnv: w.requiredEnv ?? [],
4909
- capabilities: w.capabilities ?? [],
4935
+ // The bot's `npm run deploy` creates the artifacts bucket before the upload
4936
+ // (deploy/cloudflare/ensure-bucket.mjs) when the profile names one — that
4937
+ // needs R2 on the credential, checked up front like every capability; an
4938
+ // installation without artifacts asks nothing extra of its token.
4939
+ capabilities: [...w.capabilities ?? [], ...w.name === "bot" && profile.artifacts ? [R2_CAPABILITY] : []],
4910
4940
  retryOnPreflightRefusal: !!w.preflight && !opts.force,
4911
4941
  ...w.waitMaxMs !== void 0 ? { waitMaxMs: w.waitMaxMs } : {},
4912
4942
  ...w.preflight?.healthUrl ? { healthUrl: w.preflight.healthUrl } : {},
@@ -5039,7 +5069,7 @@ function classifyDeployOutput(exitCode, output) {
5039
5069
  const firstError = lines.find((l) => isErrorish(l) && !/^npm ERR!/i.test(l)) ?? lines.find(isErrorish);
5040
5070
  return { kind: "failed", reason: firstError ?? `exit ${exitCode}` };
5041
5071
  }
5042
- var UNSET_ENV, SANDBOX_CONTAINER_CLASS, SANDBOX_BEARER_ENV, RESIDENT_BEARER_ENVS, RESIDENT_WAIT_MAX_MS, CONTAINERS_CAPABILITY, WORKER_DIRS, WORKER_SPECS, DEPLOY_ORDER, CONFIG_DOCUMENT_KEY, hasImage, ANSI_SEQUENCE;
5072
+ var UNSET_ENV, SANDBOX_CONTAINER_CLASS, SANDBOX_BEARER_ENV, R2_CAPABILITY, RESIDENT_BEARER_ENVS, RESIDENT_WAIT_MAX_MS, CONTAINERS_CAPABILITY, WORKER_DIRS, WORKER_SPECS, DEPLOY_ORDER, CONFIG_DOCUMENT_KEY, hasImage, ANSI_SEQUENCE;
5043
5073
  var init_plan = __esm({
5044
5074
  "../../src/deploy/plan.ts"() {
5045
5075
  "use strict";
@@ -5051,6 +5081,10 @@ var init_plan = __esm({
5051
5081
  UNSET_ENV = ["CLOUDFLARE_ACCOUNT_ID"];
5052
5082
  SANDBOX_CONTAINER_CLASS = "SwitchboardSandbox";
5053
5083
  SANDBOX_BEARER_ENV = "SANDBOX_TOKEN";
5084
+ R2_CAPABILITY = {
5085
+ command: ["wrangler", "r2", "bucket", "list"],
5086
+ needs: "Workers R2 Storage: Edit"
5087
+ };
5054
5088
  RESIDENT_BEARER_ENVS = ["RESIDENT_ADMIN_TOKEN", "RESIDENT_OPERATOR_TOKEN", "RESIDENT_READ_TOKEN"];
5055
5089
  RESIDENT_WAIT_MAX_MS = 30 * 6e4;
5056
5090
  CONTAINERS_CAPABILITY = {
@@ -5123,10 +5157,7 @@ var init_plan = __esm({
5123
5157
  // A container image like the bot's — checked here too, since an --affected
5124
5158
  // release can select the resident without the bot — plus the BACKUP_BUCKET
5125
5159
  // R2 binding wrangler validates on deploy.
5126
- capabilities: [
5127
- CONTAINERS_CAPABILITY,
5128
- { command: ["wrangler", "r2", "bucket", "list"], needs: "Workers R2 Storage: Edit" }
5129
- ],
5160
+ capabilities: [CONTAINERS_CAPABILITY, R2_CAPABILITY],
5130
5161
  waitMaxMs: RESIDENT_WAIT_MAX_MS,
5131
5162
  why: "per-repo DOs \u2014 preflight refuses while a resident has a run in flight or is provisioning (a refresh or restore mid-cycle only warns: it resumes after the swap)"
5132
5163
  },
@@ -5680,6 +5711,7 @@ function templateView(profile, kind, published) {
5680
5711
  ...hasImage2(kind) ? { image: containerImage(kind, profile, published) } : {},
5681
5712
  urls: { publicBaseUrl: urls.publicBaseUrl, stateWorkerUrl: urls.stateWorkerUrl },
5682
5713
  access: profile.access,
5714
+ artifacts: profile.artifacts,
5683
5715
  bot: { script: profile.workers.bot.script }
5684
5716
  };
5685
5717
  }
@@ -7811,7 +7843,8 @@ var init_runEvents = __esm({
7811
7843
  "review_not_posted",
7812
7844
  "compacted",
7813
7845
  "harness_error",
7814
- "tool_refused"
7846
+ "tool_refused",
7847
+ "stuck_loop"
7815
7848
  ];
7816
7849
  ACTOR_ID_CHARS = "A-Za-z0-9:@._-";
7817
7850
  ACTOR_ID_MAX = 128;
@@ -8108,6 +8141,13 @@ var init_host3 = __esm({
8108
8141
  });
8109
8142
 
8110
8143
  // ../../src/execution/binaryRead.ts
8144
+ function statCommandFor(resolvedPath) {
8145
+ return `stat -c %s -- ${resolvedPath}`;
8146
+ }
8147
+ function parseByteSize(stdout) {
8148
+ const m = /^\s*(\d{1,15})\s*$/.exec(stdout);
8149
+ return m ? Number(m[1]) : null;
8150
+ }
8111
8151
  function tooLargeMessage(path, bytes) {
8112
8152
  const size = bytes === void 0 ? "" : ` (${bytes} bytes)`;
8113
8153
  return `${path}${size} is over the ${MAX_READ_BYTES}-byte cap of a binary read`;
@@ -8731,9 +8771,9 @@ function sendDeadlineMs(budgetMs) {
8731
8771
  return budgetMs + EXEC_CALL_MARGIN_MS;
8732
8772
  }
8733
8773
  function noAnswerMessage(route3, budgetMs) {
8734
- const secs = (ms) => Math.round(ms / 1e3);
8774
+ const secs2 = (ms) => Math.round(ms / 1e3);
8735
8775
  const exec = route3 === "/exec";
8736
- return `sandbox worker ${route3} gave no answer within ${secs(sendDeadlineMs(budgetMs))}s (${exec ? "command budget" : "budget"} ${secs(budgetMs)}s + ${secs(EXEC_CALL_MARGIN_MS)}s margin) \u2014 the sandbox may be gone; ${exec ? "the command may still be running in it" : "the operation may still have run in it"}`;
8776
+ return `sandbox worker ${route3} gave no answer within ${secs2(sendDeadlineMs(budgetMs))}s (${exec ? "command budget" : "budget"} ${secs2(budgetMs)}s + ${secs2(EXEC_CALL_MARGIN_MS)}s margin) \u2014 the sandbox may be gone; ${exec ? "the command may still be running in it" : "the operation may still have run in it"}`;
8737
8777
  }
8738
8778
  function waitForSlot(ms, waitedMs, signal) {
8739
8779
  return new Promise((resolve12, reject) => {
@@ -8896,6 +8936,9 @@ ${parts}`);
8896
8936
  function residentText(text) {
8897
8937
  return redactAndCap(stripAnsi(text), RESIDENT_TEXT_CAP);
8898
8938
  }
8939
+ function residentErrorText(text) {
8940
+ return redactAndCap(stripAnsi(text), RESIDENT_ERROR_CAP);
8941
+ }
8899
8942
  function sanitizeResidentBody(data) {
8900
8943
  return walk(data, SANITIZE_DEPTH);
8901
8944
  }
@@ -8905,7 +8948,7 @@ function walk(value, depth) {
8905
8948
  const src = value;
8906
8949
  const out = {};
8907
8950
  for (const [key, v] of Object.entries(src)) {
8908
- out[key] = SANITIZED_FIELDS.includes(key) && typeof v === "string" ? residentText(v) : walk(v, depth - 1);
8951
+ out[key] = SANITIZED_FIELDS.includes(key) && typeof v === "string" ? key === "error" ? residentErrorText(v) : residentText(v) : walk(v, depth - 1);
8909
8952
  }
8910
8953
  if (typeof src.stderr === "string" && src.stderr === src.error && typeof out.error === "string") {
8911
8954
  out.stderr = out.error;
@@ -8915,12 +8958,13 @@ function walk(value, depth) {
8915
8958
  function residentState(value) {
8916
8959
  return typeof value === "string" && PROBE_STATES.has(value) ? value : "unknown";
8917
8960
  }
8918
- var RESIDENT_TEXT_CAP, SANITIZED_FIELDS, SANITIZE_DEPTH, PROBE_STATES;
8961
+ var RESIDENT_TEXT_CAP, RESIDENT_ERROR_CAP, SANITIZED_FIELDS, SANITIZE_DEPTH, PROBE_STATES;
8919
8962
  var init_residentText = __esm({
8920
8963
  "../../src/execution/residentText.ts"() {
8921
8964
  "use strict";
8922
8965
  init_redact();
8923
8966
  RESIDENT_TEXT_CAP = 300;
8967
+ RESIDENT_ERROR_CAP = 1e3;
8924
8968
  SANITIZED_FIELDS = ["error", "reason", "summary"];
8925
8969
  SANITIZE_DEPTH = 4;
8926
8970
  PROBE_STATES = /* @__PURE__ */ new Set([
@@ -10928,7 +10972,8 @@ function buildAnthropicParams(req) {
10928
10972
  ...req.system ? { system: [{ type: "text", text: req.system, cache_control: cache2 }] } : {},
10929
10973
  messages,
10930
10974
  ...effort2 ? { output_config: { effort: effort2 } } : {},
10931
- ...tools.length > 0 ? { tools } : {}
10975
+ ...tools.length > 0 ? { tools } : {},
10976
+ ...req.toolChoice ? { tool_choice: { type: "tool", name: req.toolChoice.name } } : {}
10932
10977
  };
10933
10978
  }
10934
10979
  function effortFor(model, effort2) {
@@ -13288,7 +13333,7 @@ function analyzeRunFriction(events, opts = {}) {
13288
13333
  }
13289
13334
  if (ev.type === "tool_call") {
13290
13335
  toolCalls++;
13291
- if (failedCalls.has(`${ev.tool} ${ev.summary}`)) {
13336
+ if (failedCalls.has(callSignature(ev.tool, ev.summary))) {
13292
13337
  findings.push({
13293
13338
  category: "retry",
13294
13339
  severity: "low",
@@ -13310,7 +13355,7 @@ function analyzeRunFriction(events, opts = {}) {
13310
13355
  const durationMs = span ? durationOfSpan(span) : void 0;
13311
13356
  const timed = (f) => durationMs !== void 0 ? { ...f, durationMs } : f;
13312
13357
  const interval = span && durationMs !== void 0 ? { interval: { start: span.startedAt, end: span.startedAt + durationMs } } : {};
13313
- if (!ev.ok) failedCalls.add(`${ev.tool} ${callSummary}`);
13358
+ if (!ev.ok) failedCalls.add(callSignature(ev.tool, callSummary));
13314
13359
  if (ev.infra) {
13315
13360
  findings.push(
13316
13361
  timed({
@@ -13503,7 +13548,7 @@ function formatFrictionReport(d) {
13503
13548
  }
13504
13549
  return lines.join("\n");
13505
13550
  }
13506
- var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED;
13551
+ var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
13507
13552
  var init_runFriction = __esm({
13508
13553
  "../../src/core/runFriction.ts"() {
13509
13554
  "use strict";
@@ -13546,6 +13591,7 @@ var init_runFriction = __esm({
13546
13591
  DEFAULT_SLOW_MODEL_TURN_MS = 6e4;
13547
13592
  INSTALL_SEGMENT = /(?:^|&&|;|\|\|?)\s*(?:sudo\s+)?(?:corepack\s+)?(?:(?:npm|pnpm|bun)\s+(?:install|i|ci|add)(?=\s|$)|yarn(?:\s+(?:install|add)(?=\s|$)|\s*$)|npx\s+playwright\s+install(?=\s|$)|(?:python3?\s+-m\s+)?pip3?\s+install(?=\s|$)|poetry\s+install(?=\s|$)|uv\s+(?:sync|pip\s+install)(?=\s|$)|apt(?:-get)?\s+install(?=\s|$)|apk\s+add(?=\s|$)|brew\s+install(?=\s|$)|bundle\s+install(?=\s|$)|gem\s+install(?=\s|$)|cargo\s+(?:fetch|build)(?=\s|$)|go\s+mod\s+download(?=\s|$)|git\s+clone(?=\s|$)|make\s+(?:deps|install|setup)(?=\s|$))/;
13548
13593
  QUOTED = /"(?:[^"\\]|\\.)*"|'[^']*'/g;
13594
+ callSignature = (tool, summary) => `${tool} ${summary}`;
13549
13595
  }
13550
13596
  });
13551
13597
 
@@ -13694,6 +13740,7 @@ function isRunRecord(v) {
13694
13740
  if (r.profile !== void 0 && !isRunProfileRecord(r.profile)) return false;
13695
13741
  if (r.parentRunId !== void 0 && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
13696
13742
  return false;
13743
+ if (r.seed !== void 0 && !RUN_SEEDS.includes(r.seed)) return false;
13697
13744
  if (r.parentInstanceId === void 0 !== (r.idempotencyKey === void 0)) return false;
13698
13745
  if (r.parentInstanceId !== void 0 && (typeof r.parentInstanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.parentInstanceId)))
13699
13746
  return false;
@@ -13825,7 +13872,7 @@ function dropSpanPairsFromTheMiddle(events, eventBudget) {
13825
13872
  }
13826
13873
  return events.filter((_, k) => !dropped.has(k));
13827
13874
  }
13828
- var RUN_STATUSES, RUN_ID_PATTERN, REVIEW_HEAD_PATTERN, MACHINE_CLASSES_IN_RECORD, IDENTITIES_IN_RECORD, BOUNDARY_SCOPES_IN_RECORD, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
13875
+ var RUN_STATUSES, RUN_ID_PATTERN, REVIEW_HEAD_PATTERN, RUN_SEEDS, MACHINE_CLASSES_IN_RECORD, IDENTITIES_IN_RECORD, BOUNDARY_SCOPES_IN_RECORD, RUN_LIST_DEFAULT_LIMIT, RUN_LIST_MAX_LIMIT, RUN_EVENTS_DEFAULT_PAGE, RUN_EVENTS_MAX_PAGE, CHANNEL_VISIBILITIES2, KIB, MIB, GIB2, DEFAULT_RETENTION_POLICY, RETENTION_BOUNDS, MAX_RECORD_BYTES, MAX_EVENT_BYTES, ELLIPSIS, encoder;
13829
13876
  var init_runRecord = __esm({
13830
13877
  "../../src/core/runRecord.ts"() {
13831
13878
  "use strict";
@@ -13837,6 +13884,7 @@ var init_runRecord = __esm({
13837
13884
  RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
13838
13885
  RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
13839
13886
  REVIEW_HEAD_PATTERN = /^[0-9a-f]{7,40}$/;
13887
+ RUN_SEEDS = ["channel", "parent"];
13840
13888
  MACHINE_CLASSES_IN_RECORD = {
13841
13889
  none: true,
13842
13890
  blank: true,
@@ -19896,7 +19944,7 @@ function createRunsService(deps) {
19896
19944
  const analyze = deps.analyze ?? ((events, opts) => analyzeRunFriction(events, opts));
19897
19945
  const clock3 = deps.clock ?? systemClock;
19898
19946
  const warn = deps.warn ?? ((m) => console.warn(m));
19899
- const describe9 = (err2) => err2 instanceof Error ? err2.message : String(err2);
19947
+ const describe11 = (err2) => err2 instanceof Error ? err2.message : String(err2);
19900
19948
  let listing;
19901
19949
  const liveRows = () => {
19902
19950
  if (!ledger) return null;
@@ -19916,7 +19964,7 @@ function createRunsService(deps) {
19916
19964
  try {
19917
19965
  rows = await pending2;
19918
19966
  } catch (err2) {
19919
- warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${describe9(err2)}`);
19967
+ warn(`[runs] run ledger list failed \u2014 showing this process's runs only: ${describe11(err2)}`);
19920
19968
  return [];
19921
19969
  }
19922
19970
  const foreign = rows.filter((row) => !registry.getById(row.runId));
@@ -19926,7 +19974,7 @@ function createRunsService(deps) {
19926
19974
  try {
19927
19975
  events = await ledger.readEvents(row.runId);
19928
19976
  } catch (err2) {
19929
- warn(`[runs] run ledger events read failed for ${row.runId}: ${describe9(err2)}`);
19977
+ warn(`[runs] run ledger events read failed for ${row.runId}: ${describe11(err2)}`);
19930
19978
  }
19931
19979
  return ledgerView(row, events);
19932
19980
  })
@@ -19940,7 +19988,7 @@ function createRunsService(deps) {
19940
19988
  if (!row) return null;
19941
19989
  return { row, events: await ledger.readEvents(id) };
19942
19990
  } catch (err2) {
19943
- warn(`[runs] run ledger read failed for ${id}: ${describe9(err2)}`);
19991
+ warn(`[runs] run ledger read failed for ${id}: ${describe11(err2)}`);
19944
19992
  return null;
19945
19993
  }
19946
19994
  };
@@ -19958,7 +20006,7 @@ function createRunsService(deps) {
19958
20006
  row = await storeSummary(id);
19959
20007
  } catch (err2) {
19960
20008
  warn(
19961
- `[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${describe9(err2)}`
20009
+ `[runs] history store read failed for ${id} \u2014 serving the registry row without its record: ${describe11(err2)}`
19962
20010
  );
19963
20011
  return {};
19964
20012
  }
@@ -20127,7 +20175,7 @@ function createRunsService(deps) {
20127
20175
  const r = await ledger.requestStop(id, mode);
20128
20176
  if (r.ok) return { ok: true, value: { id, mode, state: "stopping" } };
20129
20177
  } catch (err2) {
20130
- warn(`[runs] run ledger stop failed for ${id}: ${describe9(err2)}`);
20178
+ warn(`[runs] run ledger stop failed for ${id}: ${describe11(err2)}`);
20131
20179
  }
20132
20180
  }
20133
20181
  return await storeSummary(id) ? conflict : notFound;
@@ -21502,6 +21550,11 @@ function liveViewLink(id, token2) {
21502
21550
  if (!base) return void 0;
21503
21551
  return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}?t=${encodeURIComponent(token2)}`;
21504
21552
  }
21553
+ function runPageLink(id) {
21554
+ const base = process.env.PUBLIC_BASE_URL?.trim();
21555
+ if (!base) return void 0;
21556
+ return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
21557
+ }
21505
21558
  function stripPlatformPrefix(id) {
21506
21559
  const i = id.indexOf(":");
21507
21560
  return i === -1 ? id : id.slice(i + 1);
@@ -21748,7 +21801,11 @@ function durableInboxMessage(msg, text, at, from) {
21748
21801
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
21749
21802
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
21750
21803
  ...msg.channelName !== void 0 ? { channelName: msg.channelName } : {},
21751
- ...from !== void 0 ? { fromRunId: from.runId } : {}
21804
+ ...from !== void 0 ? { fromRunId: from.runId } : {},
21805
+ // Staged references are metadata (record 0033) — a few hundred bytes each —
21806
+ // so they ride the base row and survive the restart whatever the
21807
+ // attachments below do.
21808
+ ...msg.staged && msg.staged.length > 0 ? { staged: msg.staged.map(stagedRow) } : {}
21752
21809
  };
21753
21810
  const images = msg.images ?? [];
21754
21811
  const documents = msg.documents ?? [];
@@ -21761,6 +21818,17 @@ function durableInboxMessage(msg, text, at, from) {
21761
21818
  if (Buffer.byteLength(JSON.stringify(withAttachments), "utf8") <= DURABLE_INBOX_MAX_BYTES) return withAttachments;
21762
21819
  return { ...base, attachmentsDropped: { images: images.length, documents: documents.length } };
21763
21820
  }
21821
+ function stagedFromInbox(v) {
21822
+ if (!Array.isArray(v)) return void 0;
21823
+ const out = v.flatMap((e) => {
21824
+ if (typeof e !== "object" || e === null) return [];
21825
+ const r = e;
21826
+ if (typeof r.name !== "string" || typeof r.size !== "number" || typeof r.type !== "string" || typeof r.url !== "string" || typeof r.messageId !== "string")
21827
+ return [];
21828
+ return [{ name: r.name, size: r.size, type: r.type, url: r.url, messageId: r.messageId }];
21829
+ });
21830
+ return out.length > 0 ? out : void 0;
21831
+ }
21764
21832
  function messageFromInbox(stored, fallbackAt) {
21765
21833
  const m = stored;
21766
21834
  const str7 = (k) => typeof m[k] === "string" ? m[k] : void 0;
@@ -21777,6 +21845,7 @@ function messageFromInbox(stored, fallbackAt) {
21777
21845
  const at = typeof m.at === "number" && Number.isFinite(m.at) ? m.at : fallbackAt;
21778
21846
  const images = attachmentsFromInbox(m.images);
21779
21847
  const documents = attachmentsFromInbox(m.documents);
21848
+ const staged = stagedFromInbox(m.staged);
21780
21849
  const note = droppedNote(m.attachmentsDropped);
21781
21850
  const msg = {
21782
21851
  channelId,
@@ -21789,7 +21858,8 @@ ${note}` : text,
21789
21858
  ...sourceUrl !== void 0 ? { sourceUrl } : {},
21790
21859
  ...channelName !== void 0 ? { channelName } : {},
21791
21860
  ...images ? { images } : {},
21792
- ...documents ? { documents } : {}
21861
+ ...documents ? { documents } : {},
21862
+ ...staged ? { staged } : {}
21793
21863
  };
21794
21864
  return { msg, at, ...fromRunId !== void 0 ? { from: { runId: fromRunId } } : {} };
21795
21865
  }
@@ -21810,7 +21880,7 @@ function droppedNote(dropped) {
21810
21880
  if (n2 <= 0) return void 0;
21811
21881
  return `(${n2} attachment${n2 === 1 ? "" : "s"} from this reply could not be carried across the bot's restart and ${n2 === 1 ? "is" : "are"} not attached.)`;
21812
21882
  }
21813
- var DURABLE_INBOX_MAX_BYTES, attachmentRow;
21883
+ var DURABLE_INBOX_MAX_BYTES, attachmentRow, stagedRow;
21814
21884
  var init_inboxMessage = __esm({
21815
21885
  "../../src/core/runLedger/inboxMessage.ts"() {
21816
21886
  "use strict";
@@ -21820,6 +21890,7 @@ var init_inboxMessage = __esm({
21820
21890
  data: a.data,
21821
21891
  ...a.name !== void 0 ? { name: a.name } : {}
21822
21892
  });
21893
+ stagedRow = (s) => ({ name: s.name, size: s.size, type: s.type, url: s.url, messageId: s.messageId });
21823
21894
  }
21824
21895
  });
21825
21896
 
@@ -21851,13 +21922,15 @@ function mergeFollowUps(inputs) {
21851
21922
  const last = inputs[inputs.length - 1];
21852
21923
  const images = inputs.flatMap((i) => i.images ?? []);
21853
21924
  const documents = inputs.flatMap((i) => i.documents ?? []);
21925
+ const staged = inputs.flatMap((i) => i.staged ?? []);
21854
21926
  return {
21855
21927
  text: inputs.map((i) => i.text).join("\n\n"),
21856
21928
  userId: last.userId,
21857
21929
  ...last.userName !== void 0 ? { userName: last.userName } : {},
21858
21930
  ...last.sourceUrl !== void 0 ? { sourceUrl: last.sourceUrl } : {},
21859
21931
  ...images.length > 0 ? { images } : {},
21860
- ...documents.length > 0 ? { documents } : {}
21932
+ ...documents.length > 0 ? { documents } : {},
21933
+ ...staged.length > 0 ? { staged } : {}
21861
21934
  };
21862
21935
  }
21863
21936
  var FollowUpInbox, ThreadAdmission, elapsed, linkSuffix, followUpHeader, supersededHeader;
@@ -21969,6 +22042,7 @@ function interruptedRunRecord(summary, snap, finishedAt) {
21969
22042
  channelVisibility: summary.channelVisibility ?? "unknown",
21970
22043
  repo: summary.repo,
21971
22044
  ...summary.parentRunId !== void 0 ? { parentRunId: summary.parentRunId } : {},
22045
+ ...summary.seed !== void 0 ? { seed: summary.seed } : {},
21972
22046
  ...summary.parentInstanceId !== void 0 && summary.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: summary.parentInstanceId, idempotencyKey: summary.idempotencyKey } } : {},
21973
22047
  finishedAt,
21974
22048
  status: "interrupted",
@@ -22004,6 +22078,7 @@ function reclaimedRunRecord(input) {
22004
22078
  // included); a row from before profiles existed leaves the record without one.
22005
22079
  ...row.meta.profile && row.meta.agent ? { profile: { preset: row.meta.agent, ...row.meta.profile } } : {},
22006
22080
  ...row.meta.parentRunId !== void 0 ? { parentRunId: row.meta.parentRunId } : {},
22081
+ ...row.meta.seed !== void 0 ? { seed: row.meta.seed } : {},
22007
22082
  // A coordinator's child keeps its instance and key on the close (item 48),
22008
22083
  // so the state Worker's finish still sends the parent its event.
22009
22084
  ...row.meta.parentInstanceId !== void 0 && row.meta.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: row.meta.parentInstanceId, idempotencyKey: row.meta.idempotencyKey } } : {},
@@ -22072,7 +22147,8 @@ function assembleRunRecord(input) {
22072
22147
  ...input.reviewPost !== void 0 ? { reviewPost: redactReviewPost(input.reviewPost) } : {},
22073
22148
  ...input.profile !== void 0 ? { profile: input.profile } : {},
22074
22149
  ...input.parentRunId !== void 0 ? { parentRunId: input.parentRunId } : {},
22075
- ...coordinatorFields(input.coordinator)
22150
+ ...coordinatorFields(input.coordinator),
22151
+ ...input.seed !== void 0 ? { seed: input.seed } : {}
22076
22152
  });
22077
22153
  return fitted.eventCount !== fitted.storedEventCount ? { ...fitted, truncated: true } : fitted;
22078
22154
  }
@@ -22080,7 +22156,20 @@ function profileRecordOf(agent, profile) {
22080
22156
  return { preset: agent.name, ...profile };
22081
22157
  }
22082
22158
  function writeTombstone(deps, ctx) {
22083
- const { msg, agent, profile, resolved, repoCtx, channelVisibility, run: run2, registry, resume, parentRunId, coordinator } = ctx;
22159
+ const {
22160
+ msg,
22161
+ agent,
22162
+ profile,
22163
+ resolved,
22164
+ repoCtx,
22165
+ channelVisibility,
22166
+ run: run2,
22167
+ registry,
22168
+ resume,
22169
+ parentRunId,
22170
+ coordinator,
22171
+ seed
22172
+ } = ctx;
22084
22173
  if (!resume) {
22085
22174
  const startSnap = registry.snapshot(run2.id, run2.token);
22086
22175
  if (startSnap) {
@@ -22096,6 +22185,7 @@ function writeTombstone(deps, ctx) {
22096
22185
  profile: profileRecordOf(agent, profile),
22097
22186
  ...parentRunId !== void 0 ? { parentRunId } : {},
22098
22187
  ...coordinator !== void 0 ? { coordinator } : {},
22188
+ ...seed !== void 0 ? { seed } : {},
22099
22189
  finishedAt: startSnap.startedAt,
22100
22190
  status: "interrupted",
22101
22191
  diagnosis: analyzeRunFriction(startSnap.events, {
@@ -22130,7 +22220,8 @@ function registerFinishRecord(deps, ctx) {
22130
22220
  dispositions,
22131
22221
  reviewPost,
22132
22222
  parentRunId,
22133
- coordinator
22223
+ coordinator,
22224
+ seed
22134
22225
  } = ctx;
22135
22226
  ending.register({
22136
22227
  runId: run2.id,
@@ -22155,7 +22246,8 @@ function registerFinishRecord(deps, ctx) {
22155
22246
  ...dispositions !== void 0 ? { dispositions } : {},
22156
22247
  ...reviewPost !== void 0 ? { reviewPost } : {},
22157
22248
  ...parentRunId !== void 0 ? { parentRunId } : {},
22158
- ...coordinator !== void 0 ? { coordinator } : {}
22249
+ ...coordinator !== void 0 ? { coordinator } : {},
22250
+ ...seed !== void 0 ? { seed } : {}
22159
22251
  }),
22160
22252
  { span: root, ...ledgerRun ? { via: ledgerRun.sink } : {} }
22161
22253
  )
@@ -22188,6 +22280,7 @@ function followUpOf(msg, text, at, opts) {
22188
22280
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
22189
22281
  ...msg.images !== void 0 ? { images: msg.images } : {},
22190
22282
  ...msg.documents !== void 0 ? { documents: msg.documents } : {},
22283
+ ...msg.staged !== void 0 ? { staged: msg.staged } : {},
22191
22284
  at,
22192
22285
  ...opts.ledgerSeq !== void 0 ? { ledgerSeq: opts.ledgerSeq } : {},
22193
22286
  ...opts.from !== void 0 ? { from: opts.from } : {},
@@ -23353,6 +23446,7 @@ function summaryOf(run2) {
23353
23446
  ...m?.parentRunId !== void 0 ? { parentRunId: m.parentRunId } : {},
23354
23447
  ...m?.parentInstanceId !== void 0 ? { parentInstanceId: m.parentInstanceId } : {},
23355
23448
  ...m?.idempotencyKey !== void 0 ? { idempotencyKey: m.idempotencyKey } : {},
23449
+ ...m?.seed !== void 0 ? { seed: m.seed } : {},
23356
23450
  finished: run2.finished,
23357
23451
  startedAt: run2.startedAt,
23358
23452
  ...run2.finishedAt !== void 0 ? { finishedAt: run2.finishedAt } : {},
@@ -24254,6 +24348,21 @@ var init_awaitChildren = __esm({
24254
24348
  }
24255
24349
  });
24256
24350
 
24351
+ // ../../src/core/dispatch/textTurns.ts
24352
+ function textTurnsOf(messages) {
24353
+ const turns = [];
24354
+ for (const m of messages) {
24355
+ const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n\n");
24356
+ if (text.length > 0) turns.push({ role: m.role, text });
24357
+ }
24358
+ return turns;
24359
+ }
24360
+ var init_textTurns = __esm({
24361
+ "../../src/core/dispatch/textTurns.ts"() {
24362
+ "use strict";
24363
+ }
24364
+ });
24365
+
24257
24366
  // ../../src/core/dispatch/spawn.ts
24258
24367
  function maxChildrenOf(cfg) {
24259
24368
  return cfg?.maxChildren ?? DEFAULT_MAX_CHILDREN;
@@ -24290,6 +24399,7 @@ function watchedChild(io, on) {
24290
24399
  };
24291
24400
  if (io.attach) watched2.attach = (file) => io.attach(file);
24292
24401
  if (io.attachFile) watched2.attachFile = (file) => io.attachFile(file);
24402
+ if (io.uploadTicket) watched2.uploadTicket = (file) => io.uploadTicket(file);
24293
24403
  if (io.runFinished) watched2.runFinished = (receipt) => io.runFinished(receipt);
24294
24404
  if (io.openThread) watched2.openThread = (lead) => io.openThread(lead);
24295
24405
  return watched2;
@@ -24302,6 +24412,12 @@ async function spawnChild(deps, parent, request) {
24302
24412
  `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`
24303
24413
  );
24304
24414
  }
24415
+ if (AGENTS[request.preset]?.identity === "write") {
24416
+ return refused5(
24417
+ "spawn_identity",
24418
+ `\`${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}\``
24419
+ );
24420
+ }
24305
24421
  const minutesLeft = Math.floor(parent.remainingMs / 6e4);
24306
24422
  if (minutesLeft < MIN_BOUNDARY_MINUTES) {
24307
24423
  return refused5(
@@ -24354,8 +24470,10 @@ async function spawnChild(deps, parent, request) {
24354
24470
  lastReply = text;
24355
24471
  }
24356
24472
  });
24473
+ const seed = parent.conversation ? textTurnsOf(parent.conversation) : void 0;
24357
24474
  const settled = deps.dispatch(deps.core, child, io, {
24358
- parent: { runId: parent.runId, depth: parent.depth + 1, remainingMs: parent.remainingMs }
24475
+ parent: { runId: parent.runId, depth: parent.depth + 1, remainingMs: parent.remainingMs },
24476
+ ...seed ? { seed } : {}
24359
24477
  }).then(
24360
24478
  (outcome) => ({ kind: "ended", outcome }),
24361
24479
  (err2) => ({ kind: "threw", err: err2 })
@@ -24396,8 +24514,8 @@ function spawnCapabilityFor(deps, parent) {
24396
24514
  };
24397
24515
  let previous = Promise.resolve();
24398
24516
  return {
24399
- spawn: (request, remainingMs) => {
24400
- const turn = previous.then(() => spawnChild(withHook, { ...parent, remainingMs }, request));
24517
+ spawn: (request, at) => {
24518
+ const turn = previous.then(() => spawnChild(withHook, { ...parent, ...at }, request));
24401
24519
  previous = turn.catch(() => {
24402
24520
  });
24403
24521
  return turn;
@@ -24441,11 +24559,13 @@ var DEFAULT_MAX_CHILDREN, MAX_SPAWN_DEPTH, describe2, nullSpawnCapability;
24441
24559
  var init_spawn = __esm({
24442
24560
  "../../src/core/dispatch/spawn.ts"() {
24443
24561
  "use strict";
24562
+ init_registry();
24444
24563
  init_validate();
24445
24564
  init_actor();
24446
24565
  init_runsService();
24447
24566
  init_admission();
24448
24567
  init_awaitChildren();
24568
+ init_textTurns();
24449
24569
  DEFAULT_MAX_CHILDREN = 3;
24450
24570
  MAX_SPAWN_DEPTH = 1;
24451
24571
  describe2 = (err2) => err2 instanceof Error ? err2.message : String(err2);
@@ -24476,6 +24596,46 @@ function renderPresetTable(presets) {
24476
24596
  );
24477
24597
  return ["| name | what it does | machine | credential | budget |", "|---|---|---|---|---|", ...rows].join("\n");
24478
24598
  }
24599
+ function routeMaxOutputTokens(compound) {
24600
+ const PART_JSON_OVERHEAD = 40;
24601
+ const ANSWER_JSON_OVERHEAD = 80;
24602
+ const chars = ANSWER_JSON_OVERHEAD + ROUTE_REASON_CAP + (compound ? compound.maxParts * (ROUTE_PART_TEXT_CAP + PART_JSON_OVERHEAD) : 0);
24603
+ return Math.max(ROUTE_MIN_OUTPUT_TOKENS, Math.ceil(chars / 3));
24604
+ }
24605
+ function routeTool(presets, compound) {
24606
+ const names = presets.map((p) => p.name);
24607
+ const table = presets.map((p) => `${p.name}: ${oneLine(p.description)}`).join("; ");
24608
+ const properties = {
24609
+ preset: {
24610
+ type: "string",
24611
+ enum: compound ? [...names, COMPOUND_PRESET] : names,
24612
+ description: `the least capable preset whose description covers the request \u2014 ${table}${compound ? `; ${COMPOUND_PRESET}: only for a compound request, with parts` : ""}`
24613
+ },
24614
+ reason: { type: "string", description: "one line, under 100 characters: why this preset" },
24615
+ ...compound ? {
24616
+ parts: {
24617
+ type: "array",
24618
+ description: `only when the request has two or more INDEPENDENT asks on different subjects, each rewritten so it stands alone, with preset "${COMPOUND_PRESET}". A single ask with several steps is one request on one preset: omit parts and name that preset. When one ask is in doubt, omit parts.`,
24619
+ minItems: 2,
24620
+ maxItems: compound.maxParts,
24621
+ items: {
24622
+ type: "object",
24623
+ additionalProperties: false,
24624
+ required: ["preset", "text"],
24625
+ properties: {
24626
+ preset: { type: "string", enum: names, description: "the preset this part runs on" },
24627
+ text: { type: "string", description: "the part as a request of its own" }
24628
+ }
24629
+ }
24630
+ }
24631
+ } : {}
24632
+ };
24633
+ return {
24634
+ name: ROUTE_TOOL_NAME,
24635
+ description: "Route the request: name the preset it runs on and why.",
24636
+ inputSchema: { type: "object", additionalProperties: false, required: ["preset", "reason"], properties }
24637
+ };
24638
+ }
24479
24639
  function quoteRequest(text) {
24480
24640
  const bent = text.replace(/<(\/?)request>/gi, "\u2039$1request\u203A");
24481
24641
  if (bent.length <= ROUTE_TEXT_CAP) return bent;
@@ -24509,7 +24669,7 @@ function buildRoutePrompt(input) {
24509
24669
  quoteRequest(input.text),
24510
24670
  "</request>"
24511
24671
  ].join("\n");
24512
- return { system, user };
24672
+ return { system, user, tool: routeTool(input.presets, input.compound) };
24513
24673
  }
24514
24674
  function compoundRules(offer) {
24515
24675
  return [
@@ -24520,7 +24680,7 @@ function compoundRules(offer) {
24520
24680
  }
24521
24681
  function imperativeRule(writers) {
24522
24682
  const names = writers.map((w) => `\`${w}\``).join(" or ");
24523
- return `Short imperatives: one terse order to change something or to make a failure go away \u2014 "fix it", "make it pass", "make the tests green", "add X", "rename Y", "bump Z" \u2014 is a request to change code even when it names no file, repository or cause: the channel or thread it arrives in is bound to a repository, and the preset that implements changes finds the failure itself. For it, answer ${names}. A question or a read-only ask about the same failure \u2014 "why did ci fail?", "check whether ci is red", "tell me why the build failed", "list the failing tests" \u2014 changes nothing: answer a read-only preset that covers it, never ${names}. An ask to look at, check or judge a pull request is a review, not an order to change it.`;
24683
+ return `Short imperatives: one terse order to change something or to make a failure go away \u2014 "fix it", "make it pass", "make the tests green", "add X", "rename Y", "bump Z" \u2014 is a request to change code even when it names no file, repository or cause: the channel or thread it arrives in is bound to a repository, and the preset that implements changes finds the failure itself. For it, answer ${names}. A question or a read-only ask about the same failure \u2014 "why did ci fail?", "check whether ci is red", "tell me why the build failed", "list the failing tests" \u2014 changes nothing: answer a read-only preset that covers it, never ${names}. An ask to look at, check or judge a pull request \u2014 named by a link or a number, or the thread's own ("this PR") \u2014 is a review, not an order to change it; a question about a failure with no pull request in view is not a review.`;
24524
24684
  }
24525
24685
  function tidyReason(reason) {
24526
24686
  const line3 = oneLine(redactAndCap(reason, ROUTE_REASON_CAP));
@@ -24537,8 +24697,10 @@ function parseRouteAnswer(raw, allowed2, compound) {
24537
24697
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
24538
24698
  return { preset: void 0, reason: `not a single JSON object: ${tidyReason(trimmed)}` };
24539
24699
  const { preset, reason, parts } = parsed;
24540
- if (typeof preset !== "string") return { preset: void 0, reason: "missing preset in the router's answer" };
24541
- if (typeof reason !== "string") return { preset: void 0, reason: "missing reason in the router's answer" };
24700
+ if (typeof preset !== "string")
24701
+ return { preset: void 0, reason: `missing preset in the router's answer: ${tidyReason(trimmed)}` };
24702
+ if (typeof reason !== "string")
24703
+ return { preset: void 0, reason: `missing reason in the router's answer: ${tidyReason(trimmed)}` };
24542
24704
  if (preset === COMPOUND_PRESET) return parseCompound(parts, tidyReason(reason), allowed2, compound);
24543
24705
  if (!allowed2.includes(preset))
24544
24706
  return { preset: void 0, reason: `router said "${tidyReason(preset)}", not a preset the requester may run` };
@@ -24576,7 +24738,7 @@ async function route(input, model, opts = {}) {
24576
24738
  let raw;
24577
24739
  try {
24578
24740
  raw = await model(prompt, {
24579
- maxTokens: ROUTE_MAX_OUTPUT_TOKENS,
24741
+ maxTokens: routeMaxOutputTokens(input.compound),
24580
24742
  signal: AbortSignal.timeout(opts.timeoutMs ?? ROUTE_TIMEOUT_MS)
24581
24743
  });
24582
24744
  } catch (err2) {
@@ -24591,15 +24753,22 @@ async function route(input, model, opts = {}) {
24591
24753
  input.compound
24592
24754
  );
24593
24755
  }
24594
- function providerRouteModel(provider, model) {
24595
- return async (prompt, opts) => {
24756
+ function providerRouteModel(provider, model, opts = {}) {
24757
+ const forced = (opts.answer ?? "tool") === "tool";
24758
+ return async (prompt, call2) => {
24596
24759
  const result = await provider.complete({
24597
24760
  model,
24598
24761
  system: prompt.system,
24599
24762
  messages: [{ role: "user", content: [{ type: "text", text: prompt.user }] }],
24600
- maxTokens: opts.maxTokens,
24601
- signal: opts.signal
24763
+ maxTokens: call2.maxTokens,
24764
+ signal: call2.signal,
24765
+ ...forced ? { tools: [prompt.tool], toolChoice: { type: "tool", name: prompt.tool.name } } : {}
24602
24766
  });
24767
+ if (result.stopReason === "max_tokens") throw new Error(`answer cut at the output cap (${call2.maxTokens} tokens)`);
24768
+ const answer = result.content.find(
24769
+ (p) => p.type === "tool_use" && p.name === prompt.tool.name
24770
+ );
24771
+ if (answer) return JSON.stringify(answer.input);
24603
24772
  return result.content.filter((p) => p.type === "text").map((p) => p.text).join("");
24604
24773
  };
24605
24774
  }
@@ -24617,7 +24786,9 @@ async function routeRequest(deps, ctx) {
24617
24786
  if (deps.routeModel) model = deps.routeModel;
24618
24787
  else {
24619
24788
  const ref = parseModelRef(modelRef2);
24620
- model = providerRouteModel(deps.providers.get(ref.provider), ref.model);
24789
+ model = providerRouteModel(deps.providers.get(ref.provider), ref.model, {
24790
+ ...cfg.routing?.answer ? { answer: cfg.routing.answer } : {}
24791
+ });
24621
24792
  }
24622
24793
  } catch (err2) {
24623
24794
  console.log(`[route] ${msg.threadKey} not routed: ${err2 instanceof Error ? err2.message : String(err2)}`);
@@ -24680,7 +24851,7 @@ function compoundBrief(text, parts) {
24680
24851
  ...parts.map((p, i) => `${i + 1}. \`${p.preset}\`: ${p.text}`)
24681
24852
  ].join("\n");
24682
24853
  }
24683
- var ROUTE_TEXT_CAP, ROUTE_REASON_CAP, ROUTE_MAX_OUTPUT_TOKENS, ROUTE_TIMEOUT_MS, ROUTE_PART_TEXT_CAP, ROUTE_PART_LINE_CAP, COMPOUND_BRIEF_HEADING, ROUTED_CARD_FOOTER;
24854
+ var ROUTE_TEXT_CAP, ROUTE_REASON_CAP, ROUTE_MIN_OUTPUT_TOKENS, ROUTE_TOOL_NAME, ROUTE_TIMEOUT_MS, ROUTE_PART_TEXT_CAP, ROUTE_PART_LINE_CAP, COMPOUND_BRIEF_HEADING, ROUTED_CARD_FOOTER;
24684
24855
  var init_route = __esm({
24685
24856
  "../../src/core/dispatch/route.ts"() {
24686
24857
  "use strict";
@@ -24691,7 +24862,8 @@ var init_route = __esm({
24691
24862
  init_spawn();
24692
24863
  ROUTE_TEXT_CAP = 2e3;
24693
24864
  ROUTE_REASON_CAP = 120;
24694
- ROUTE_MAX_OUTPUT_TOKENS = 200;
24865
+ ROUTE_MIN_OUTPUT_TOKENS = 200;
24866
+ ROUTE_TOOL_NAME = "route";
24695
24867
  ROUTE_TIMEOUT_MS = 8e3;
24696
24868
  ROUTE_PART_TEXT_CAP = 1e3;
24697
24869
  ROUTE_PART_LINE_CAP = 100;
@@ -25723,19 +25895,162 @@ ${skill.body}${footer}`;
25723
25895
  }
25724
25896
  });
25725
25897
 
25898
+ // ../../src/artifacts/contentType.ts
25899
+ function contentTypeFor(name) {
25900
+ const dot = name.lastIndexOf(".");
25901
+ if (dot < 0 || dot === name.length - 1) return OCTET_STREAM;
25902
+ return BY_EXTENSION[name.slice(dot + 1).toLowerCase()] ?? OCTET_STREAM;
25903
+ }
25904
+ var BY_EXTENSION, OCTET_STREAM, INLINE_IMAGE_TYPES;
25905
+ var init_contentType = __esm({
25906
+ "../../src/artifacts/contentType.ts"() {
25907
+ "use strict";
25908
+ BY_EXTENSION = {
25909
+ png: "image/png",
25910
+ jpg: "image/jpeg",
25911
+ jpeg: "image/jpeg",
25912
+ gif: "image/gif",
25913
+ webp: "image/webp",
25914
+ svg: "image/svg+xml",
25915
+ pdf: "application/pdf",
25916
+ mp4: "video/mp4",
25917
+ webm: "video/webm",
25918
+ mov: "video/quicktime",
25919
+ mp3: "audio/mpeg",
25920
+ wav: "audio/wav",
25921
+ txt: "text/plain",
25922
+ md: "text/markdown",
25923
+ csv: "text/csv",
25924
+ json: "application/json",
25925
+ html: "text/html",
25926
+ zip: "application/zip",
25927
+ gz: "application/gzip",
25928
+ tar: "application/x-tar"
25929
+ };
25930
+ OCTET_STREAM = "application/octet-stream";
25931
+ INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
25932
+ }
25933
+ });
25934
+
25935
+ // ../../src/artifacts/keys.ts
25936
+ function safeBasename(name) {
25937
+ const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
25938
+ const last = segments2[segments2.length - 1] ?? "";
25939
+ const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
25940
+ if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
25941
+ return reduced;
25942
+ }
25943
+ function threadKeySafe(threadKey) {
25944
+ return threadKey.replace(/:/g, "-").replace(SAFE, "_");
25945
+ }
25946
+ function outboundKey(runId3, seq, name) {
25947
+ return `runs/${runId3}/out/${seq}-${safeBasename(name)}`;
25948
+ }
25949
+ function inboundKey(threadKey, messageTs, index, name) {
25950
+ return `threads/${threadKeySafe(threadKey)}/in/${messageTs}/${index}-${safeBasename(name)}`;
25951
+ }
25952
+ var SAFE;
25953
+ var init_keys = __esm({
25954
+ "../../src/artifacts/keys.ts"() {
25955
+ "use strict";
25956
+ SAFE = /[^A-Za-z0-9._-]+/g;
25957
+ }
25958
+ });
25959
+
25726
25960
  // ../../src/tools/attach.ts
25961
+ function transferBudgetMs(size) {
25962
+ return Math.ceil(size / TRANSFER_FLOOR_BYTES_PER_MS) + TRANSFER_SETUP_MS;
25963
+ }
25727
25964
  function fileNameOf2(path) {
25728
25965
  const segments2 = path.split("/").filter(Boolean);
25729
25966
  return segments2[segments2.length - 1] ?? path;
25730
25967
  }
25731
- var attachFileTool;
25968
+ function putCommandFor(path, contentType, url) {
25969
+ return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} ${shellQuote(url)}`;
25970
+ }
25971
+ function postCommandFor(path, url) {
25972
+ return `curl -fsS --data-binary @${shellQuote(path)} ${shellQuote(url)}`;
25973
+ }
25974
+ async function attachThroughStore(path, name, lead, artifacts, ctx) {
25975
+ const exec = (command2, timeoutMs2) => {
25976
+ const opts = { timeoutMs: timeoutMs2, ...ctx.signal ? { signal: ctx.signal } : {} };
25977
+ return ctx.executor.exec(command2, opts);
25978
+ };
25979
+ const statOut = await exec(statCommandFor(shellQuote(path)), STAT_TIMEOUT_MS);
25980
+ if (parseExitPrefix(statOut).failed) return `error: could not read ${path}: ${statOut.trim()}`;
25981
+ const size = parseByteSize(statOut);
25982
+ if (size === null) return `error: could not measure ${path}: ${statOut.trim()}`;
25983
+ if (size === 0) return `error: ${path} is empty \u2014 nothing to attach`;
25984
+ if (size > MAX_ARTIFACT_BYTES) {
25985
+ return `error: ${path} is ${size} bytes; attach_file takes files up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) \u2014 link to the file instead`;
25986
+ }
25987
+ let timeoutMs = BASH_TIMEOUT_MAX_MS;
25988
+ if (ctx.remainingMs) {
25989
+ const budget = bashBudgetWithinRun(BASH_TIMEOUT_MAX_MS, ctx.remainingMs());
25990
+ if (budget.kind === "exhausted") return `error: ${budget.note}`;
25991
+ if (budget.kind === "clipped") timeoutMs = budget.timeoutMs;
25992
+ }
25993
+ const needed = transferBudgetMs(size);
25994
+ if (needed > timeoutMs) {
25995
+ return `error: ${name} is ${size} bytes and needs about ${secs(needed)}s to move at ${TRANSFER_FLOOR_BYTES_PER_MS} KB/s; the run has ${secs(timeoutMs)}s of command budget left \u2014 write up what you have and link to the file instead`;
25996
+ }
25997
+ const contentType = contentTypeFor(name);
25998
+ const key = outboundKey(artifacts.runId, artifacts.nextSeq(), name);
25999
+ const putUrl = await artifacts.store.presignPut(key, contentType);
26000
+ const putOut = await exec(putCommandFor(path, contentType, putUrl), timeoutMs);
26001
+ if (parseExitPrefix(putOut).failed) {
26002
+ return `error: the upload of ${name} to the artifact store failed: ${putOut.trim()}`;
26003
+ }
26004
+ const head = await artifacts.store.head(key);
26005
+ if (!head)
26006
+ return `error: the artifact store holds nothing under ${key} after the upload of ${name} \u2014 nothing was posted`;
26007
+ if (head.size !== size) {
26008
+ return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured \u2014 nothing was posted`;
26009
+ }
26010
+ ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
26011
+ if (!ctx.uploadTicket) {
26012
+ const where = artifacts.runUrl ? ` \u2014 ${artifacts.runUrl}` : "";
26013
+ await artifacts.reply(`${lead}
26014
+ \u{1F4CE} ${name} (${size} bytes) is on the run page${where}`);
26015
+ return `attached ${name} (${size} bytes) to the run page; this conversation's channel takes no file uploads, so the lead and the page's link were posted instead`;
26016
+ }
26017
+ const kept = "the file is kept on the run page";
26018
+ let ticket;
26019
+ try {
26020
+ ticket = await ctx.uploadTicket({ name, size });
26021
+ } catch (err2) {
26022
+ return `error: the channel refused an upload ticket for ${name}: ${describe3(err2)}; ${kept}`;
26023
+ }
26024
+ const postOut = await exec(postCommandFor(path, ticket.url), timeoutMs);
26025
+ if (parseExitPrefix(postOut).failed) {
26026
+ return `error: the upload of ${name} to the channel failed: ${postOut.trim()}; ${kept}`;
26027
+ }
26028
+ try {
26029
+ await ticket.complete(lead);
26030
+ } catch (err2) {
26031
+ return `error: the channel refused to complete the upload of ${name}: ${describe3(err2)}; ${kept}`;
26032
+ }
26033
+ return `attached ${name} (${size} bytes) to the conversation and the run page`;
26034
+ }
26035
+ var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe3, secs, attachFileTool;
25732
26036
  var init_attach = __esm({
25733
26037
  "../../src/tools/attach.ts"() {
25734
26038
  "use strict";
26039
+ init_contentType();
26040
+ init_keys();
26041
+ init_runEvents();
26042
+ init_bashTimeout();
25735
26043
  init_binaryRead();
26044
+ init_shellQuote();
26045
+ MAX_ARTIFACT_BYTES = 1073741824;
26046
+ TRANSFER_FLOOR_BYTES_PER_MS = 1024;
26047
+ TRANSFER_SETUP_MS = 1e4;
26048
+ STAT_TIMEOUT_MS = 3e4;
26049
+ describe3 = (err2) => err2 instanceof Error ? err2.message : String(err2);
26050
+ secs = (ms) => Math.max(0, Math.round(ms / 1e3));
25736
26051
  attachFileTool = {
25737
26052
  name: "attach_file",
25738
- description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only, up to ${MAX_READ_BYTES} bytes.`,
26053
+ description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only: up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) where the artifact store is configured, ${MAX_READ_BYTES} bytes otherwise \u2014 the result says which.`,
25739
26054
  inputSchema: {
25740
26055
  type: "object",
25741
26056
  properties: {
@@ -25752,6 +26067,7 @@ var init_attach = __esm({
25752
26067
  if (!path) return "error: path is required";
25753
26068
  const name = fileNameOf2(path);
25754
26069
  const lead = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : name;
26070
+ if (ctx.artifacts) return attachThroughStore(path, name, lead, ctx.artifacts, ctx);
25755
26071
  if (!ctx.attach) {
25756
26072
  return "attach_file is not available here: this conversation's channel takes no file uploads \u2014 link to the file instead";
25757
26073
  }
@@ -25763,13 +26079,13 @@ var init_attach = __esm({
25763
26079
  try {
25764
26080
  bytes = await readBytes(path);
25765
26081
  } catch (err2) {
25766
- return `error: could not read ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`;
26082
+ return `error: could not read ${path}: ${describe3(err2)}`;
25767
26083
  }
25768
26084
  if (bytes.byteLength === 0) return `error: ${path} is empty \u2014 nothing to attach`;
25769
26085
  try {
25770
26086
  await ctx.attach({ name, bytes, lead });
25771
26087
  } catch (err2) {
25772
- return `error: the channel refused the upload of ${name}: ${err2 instanceof Error ? err2.message : String(err2)}`;
26088
+ return `error: the channel refused the upload of ${name}: ${describe3(err2)}`;
25773
26089
  }
25774
26090
  return `attached ${name} (${bytes.byteLength} bytes) to the conversation`;
25775
26091
  }
@@ -25902,7 +26218,7 @@ async function nextTick(wait, ids, ms, signal) {
25902
26218
  signal?.removeEventListener("abort", end);
25903
26219
  }
25904
26220
  }
25905
- var UNAVAILABLE2, NOT_FOUND, PRESETS, CHILDREN_PAGES_MAX, spawnRunTool, sendToRunTool, AWAIT_IDS_MAX, awaitRunsTool, listRunsTool, getRunStatusTool, RUN_TOOLS;
26221
+ var UNAVAILABLE2, NOT_FOUND, CHILD_PRESETS, WRITE_PRESETS, REPO_PRESETS, CHILDREN_PAGES_MAX, spawnRunTool, sendToRunTool, AWAIT_IDS_MAX, awaitRunsTool, listRunsTool, getRunStatusTool, RUN_TOOLS;
25906
26222
  var init_runs2 = __esm({
25907
26223
  "../../src/tools/runs.ts"() {
25908
26224
  "use strict";
@@ -25917,16 +26233,24 @@ var init_runs2 = __esm({
25917
26233
  init_runsService();
25918
26234
  UNAVAILABLE2 = "run tools are not available in this context.";
25919
26235
  NOT_FOUND = "not_found";
25920
- PRESETS = () => Object.keys(AGENTS);
26236
+ CHILD_PRESETS = () => Object.values(AGENTS).filter((a) => a.name !== "conductor" && a.identity !== "write").map((a) => a.name);
26237
+ WRITE_PRESETS = () => Object.values(AGENTS).filter((a) => a.identity === "write").map((a) => a.name);
26238
+ REPO_PRESETS = () => Object.values(AGENTS).filter((a) => machineNeedsRepo(a.machine)).map((a) => a.name);
25921
26239
  CHILDREN_PAGES_MAX = 25;
25922
26240
  spawnRunTool = {
25923
26241
  name: "spawn_run",
25924
- description: "Start a child run as the person who asked you: an ordinary Switchboard run of the named preset, in a thread of its own in this channel, under their permissions \u2014 what they could start by hand with `agent:<preset>`. `prompt` is everything the child needs (it sees none of this thread); `repo` (owner/name) for a preset that works in a repository (coding, review, explore, ship); `budget` narrows the child's wall clock in whole minutes (at least 2; it is also capped by what is left of yours). Returns the child's run id, its thread and a link, or a refusal by name: a preset or repository the requester may not use, a boundary the child's profile exceeds, the fan-out cap, or a channel that cannot open a thread. A child cannot spawn children.",
26242
+ description: `Start a child run as the person who asked you: an ordinary Switchboard run of the named preset, in a thread of its own in this channel, under their permissions \u2014 what they could start by hand with \`agent:<preset>\`. The child is a reader of this conversation: it starts from this conversation's text so far (every user and assistant turn, never your tool calls or their results) plus \`prompt\`, its one new turn \u2014 so say what it should do. Presets a child can run: ${CHILD_PRESETS().join(", ")}; a preset that writes (${WRITE_PRESETS().join(", ")}) is refused \`spawn_identity\`. \`repo\` (owner/name) for a preset that works in a repository (${REPO_PRESETS().join(", ")}); \`budget\` narrows the child's wall clock in whole minutes (at least 2; it is also capped by what is left of yours). Returns the child's run id, its thread and a link, or a refusal by name: a preset or repository the requester may not use, a boundary the child's profile exceeds, the fan-out cap, or a channel that cannot open a thread. A child cannot spawn children.`,
25925
26243
  inputSchema: {
25926
26244
  type: "object",
25927
26245
  properties: {
25928
- preset: { type: "string", description: `The preset the child runs: one of ${PRESETS().join(", ")}` },
25929
- prompt: { type: "string", description: "The child's whole request, self-contained" },
26246
+ preset: {
26247
+ type: "string",
26248
+ description: `The preset the child runs: one of ${CHILD_PRESETS().join(", ")} (a preset that writes \u2014 ${WRITE_PRESETS().join(", ")} \u2014 is refused spawn_identity)`
26249
+ },
26250
+ prompt: {
26251
+ type: "string",
26252
+ description: "What the child should do \u2014 its one new turn after this conversation's text"
26253
+ },
25930
26254
  repo: { type: "string", description: "owner/name of the repository the child works in, for a repository preset" },
25931
26255
  budget: { type: "integer", description: "The child's wall clock in whole minutes, at least 2 (optional)" }
25932
26256
  },
@@ -25934,9 +26258,10 @@ var init_runs2 = __esm({
25934
26258
  },
25935
26259
  async run(input, ctx) {
25936
26260
  const preset = String(input.preset ?? "");
25937
- if (!Object.hasOwn(AGENTS, preset)) return `error: unknown preset "${preset}" \u2014 one of ${PRESETS().join(", ")}`;
26261
+ if (!Object.hasOwn(AGENTS, preset))
26262
+ return `error: unknown preset "${preset}" \u2014 one of ${CHILD_PRESETS().join(", ")}`;
25938
26263
  const prompt = String(input.prompt ?? "").trim();
25939
- if (!prompt) return "error: prompt is required \u2014 the child's whole request, since it sees none of this thread";
26264
+ if (!prompt) return "error: prompt is required \u2014 what the child should do, after this conversation's text";
25940
26265
  const request = { preset, prompt };
25941
26266
  if (input.repo !== void 0 && String(input.repo).trim()) request.repo = String(input.repo).trim();
25942
26267
  if (input.budget !== void 0) {
@@ -25946,7 +26271,10 @@ var init_runs2 = __esm({
25946
26271
  request.budget = budget;
25947
26272
  }
25948
26273
  const spawn7 = ctx.spawn ?? nullSpawnCapability;
25949
- const out = await spawn7.spawn(request, ctx.remainingMs?.() ?? Number.POSITIVE_INFINITY);
26274
+ const out = await spawn7.spawn(request, {
26275
+ remainingMs: ctx.remainingMs?.() ?? Number.POSITIVE_INFINITY,
26276
+ ...ctx.conversation ? { conversation: ctx.conversation() } : {}
26277
+ });
25950
26278
  if (out.kind === "refused") return `spawn refused (${out.reason}): ${out.message}`;
25951
26279
  return `spawned a ${preset} run: ${out.runId} in thread ${out.threadKey}${out.url ? ` (${out.url})` : ""} \u2014 it runs on its own; await_runs waits for it and returns its reply; get_run_status ${out.runId} answers its progress meanwhile.`;
25952
26280
  }
@@ -26673,14 +27001,34 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26673
27001
  const deadline = now() + (opts.resume ? opts.resume.remainingMs : opts.agent.maxMinutes * 6e4);
26674
27002
  const warnAt = deadline - Math.min(3 * 6e4, opts.agent.maxMinutes * 15e3);
26675
27003
  toolContext.remainingMs = () => deadline - now();
27004
+ toolContext.conversation = () => messages;
26676
27005
  let warned = false;
26677
27006
  let sandboxDead = false;
27007
+ let stuckLoop = false;
27008
+ let stuckSig;
27009
+ let stuckStreak = 0;
27010
+ let stuckNudged = false;
27011
+ const trackStuck = (tu, ok2) => {
27012
+ const sig = callSignature(tu.name, redactAndCap(describeToolCall(tu)));
27013
+ if (ok2) {
27014
+ if (sig === stuckSig) {
27015
+ stuckSig = void 0;
27016
+ stuckStreak = 0;
27017
+ stuckNudged = false;
27018
+ }
27019
+ return;
27020
+ }
27021
+ if (sig !== stuckSig) stuckNudged = false;
27022
+ stuckStreak = sig === stuckSig ? stuckStreak + 1 : 1;
27023
+ stuckSig = sig;
27024
+ };
26678
27025
  let turn = 0;
26679
27026
  const wouldStep = (turns, iteration) => turns < opts.agent.maxTurns && iteration < opts.agent.maxTurns * 2 && now() < deadline && !control?.requested;
26680
27027
  const pendingFollowUps = () => (opts.inbox?.size ?? 0) > 0;
26681
27028
  let inboxConsumedSeq = opts.resume?.inboxConsumedSeq ?? 0;
26682
- const drainFollowUps = (superseded = false) => {
27029
+ const drainFollowUps = async (superseded = false) => {
26683
27030
  const inputs = opts.inbox?.drain() ?? [];
27031
+ const stagedLine = opts.stageFollowUps ? await opts.stageFollowUps(inputs) : "";
26684
27032
  for (const input of inputs) {
26685
27033
  if (input.ledgerSeq !== void 0 && input.ledgerSeq > inboxConsumedSeq) inboxConsumedSeq = input.ledgerSeq;
26686
27034
  const source2 = {
@@ -26691,7 +27039,10 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26691
27039
  emit({ type: "input", text: redactSecrets(input.text), ...Object.keys(source2).length > 0 ? { source: source2 } : {} });
26692
27040
  note("follow_up", `follow-up folded in: ${redactSecrets(followUpSnippet(input))}`);
26693
27041
  }
26694
- const parts = [{ type: "text", text: followUpPrompt(inputs, { superseded }) }];
27042
+ const prompt = followUpPrompt(inputs, { superseded });
27043
+ const parts = [{ type: "text", text: stagedLine ? `${prompt}
27044
+
27045
+ ${stagedLine}` : prompt }];
26695
27046
  for (const input of inputs) {
26696
27047
  for (const img of input.images ?? []) parts.push({ type: "image", mediaType: img.mediaType, data: img.data });
26697
27048
  for (const doc of input.documents ?? [])
@@ -26708,12 +27059,15 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26708
27059
  const body = async (callSpan) => {
26709
27060
  if (announceIt) announce(tu, callSpan?.id);
26710
27061
  const spanId = callSpan ? { spanId: callSpan.id } : {};
26711
- const settle2 = (ok2, extra = {}) => callSpan?.end(ok2 ? "ok" : "error", {
26712
- callId: tu.id,
26713
- ok: ok2,
26714
- ...extra.exitCode !== void 0 ? { exitCode: extra.exitCode } : {},
26715
- ...extra.infra ? { infra: true } : {}
26716
- });
27062
+ const settle2 = (ok2, extra = {}) => {
27063
+ trackStuck(tu, ok2);
27064
+ callSpan?.end(ok2 ? "ok" : "error", {
27065
+ callId: tu.id,
27066
+ ok: ok2,
27067
+ ...extra.exitCode !== void 0 ? { exitCode: extra.exitCode } : {},
27068
+ ...extra.infra ? { infra: true } : {}
27069
+ });
27070
+ };
26717
27071
  const tool = toolsByName.get(tu.name);
26718
27072
  if (!tool) {
26719
27073
  emit({
@@ -26897,7 +27251,7 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26897
27251
  turn++;
26898
27252
  if (text2) emit({ type: "assistant", text: redactSecrets(text2) });
26899
27253
  messages.push({ role: "assistant", content: result.content });
26900
- messages.push({ role: "user", content: drainFollowUps(true) });
27254
+ messages.push({ role: "user", content: await drainFollowUps(true) });
26901
27255
  continue;
26902
27256
  }
26903
27257
  return text2 || "_(no response)_";
@@ -26922,6 +27276,10 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26922
27276
  reportedUpTo = messages.length;
26923
27277
  }
26924
27278
  const results = await dispatchToolUses(toolUses);
27279
+ if (!stuckNudged && stuckStreak >= STUCK_NUDGE_AT) {
27280
+ stuckNudged = true;
27281
+ results.push({ type: "text", text: stuckLoopNudge() });
27282
+ }
26925
27283
  if (!warned && now() >= warnAt) {
26926
27284
  warned = true;
26927
27285
  const minutesLeft = Math.max(1, Math.round((deadline - now()) / 6e4));
@@ -26929,12 +27287,16 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26929
27287
  results.push({ type: "text", text: wrapUpInstruction(minutesLeft) });
26930
27288
  }
26931
27289
  const dead = execTracker.consecutiveInfraFailures >= MAX_CONSECUTIVE_INFRA_FAILURES;
26932
- if (pendingFollowUps() && !dead && wouldStep(turn, iteration + 1)) results.push(...drainFollowUps());
27290
+ if (pendingFollowUps() && !dead && wouldStep(turn, iteration + 1)) results.push(...await drainFollowUps());
26933
27291
  messages.push({ role: "user", content: results });
26934
27292
  if (dead) {
26935
27293
  sandboxDead = true;
26936
27294
  break;
26937
27295
  }
27296
+ if (stuckStreak >= STUCK_WRITE_UP_AT) {
27297
+ stuckLoop = true;
27298
+ break;
27299
+ }
26938
27300
  }
26939
27301
  narrateHeld();
26940
27302
  if (control?.requested === "soft") {
@@ -26946,6 +27308,11 @@ async function runLoop(opts, now, note, emit, agentSpan) {
26946
27308
  note("sandbox_dead", `${diagnosis} \u2014 aborting instead of retrying into a dead sandbox`);
26947
27309
  return await finishSandboxDead(complete2, opts, messages, system, diagnosis);
26948
27310
  }
27311
+ if (stuckLoop) {
27312
+ note("stuck_loop", stuckLoopNote());
27313
+ const text2 = await runFinale(complete2, opts, messages, system, stuckLoopInstruction());
27314
+ return stuckLoopAnswer(text2);
27315
+ }
26949
27316
  const wasTimeout = now() >= deadline;
26950
27317
  const elapsedMs = opts.agent.maxMinutes * 6e4 - (deadline - now());
26951
27318
  const pace = turnGuardPace(turn, elapsedMs);
@@ -27047,7 +27414,7 @@ function withSpanId(e, spanId) {
27047
27414
  return e;
27048
27415
  }
27049
27416
  }
27050
- var MAX_CONSECUTIVE_INFRA_FAILURES, HardStopError, HARD_STOP_MESSAGE, wrapUpNote, wrapUpInstruction, WRITE_UP_REQUEST, timeBudgetInstruction, turnGuardInstruction, SOFT_STOP_INSTRUCTION, turnGuardPace, timeBudgetNote, turnGuardNote, softStopNote, hardStopNote, timeBudgetAnswer, turnGuardAnswer, softStopAnswer, FinaleTimeoutError, FINALE_TIMEOUT_MS;
27417
+ var MAX_CONSECUTIVE_INFRA_FAILURES, HardStopError, HARD_STOP_MESSAGE, wrapUpNote, wrapUpInstruction, WRITE_UP_REQUEST, timeBudgetInstruction, turnGuardInstruction, SOFT_STOP_INSTRUCTION, turnGuardPace, timeBudgetNote, turnGuardNote, softStopNote, hardStopNote, timeBudgetAnswer, turnGuardAnswer, STUCK_NUDGE_AT, STUCK_WRITE_UP_AT, stuckLoopNudge, stuckLoopNote, stuckLoopInstruction, stuckLoopAnswer, softStopAnswer, FinaleTimeoutError, FINALE_TIMEOUT_MS;
27051
27418
  var init_runner = __esm({
27052
27419
  "../../src/runner.ts"() {
27053
27420
  "use strict";
@@ -27058,6 +27425,7 @@ var init_runner = __esm({
27058
27425
  init_tracingExecutor();
27059
27426
  init_workspace();
27060
27427
  init_formatDuration();
27428
+ init_runFriction();
27061
27429
  MAX_CONSECUTIVE_INFRA_FAILURES = 2;
27062
27430
  HardStopError = class extends Error {
27063
27431
  constructor() {
@@ -27083,6 +27451,14 @@ ${text}` : `Stopped at the ${maxMinutes}-minute budget without finishing. Partia
27083
27451
  turnGuardAnswer = (text, pace) => text ? `\u26A0\uFE0F _Stopped after ${pace} \u2014 that pace looks like a loop; findings so far:_
27084
27452
 
27085
27453
  ${text}` : `Stopped after ${pace} \u2014 that pace looks like a loop \u2014 without finishing. Partial work may exist in the workspace \u2014 look for a retry loop in the run's events before trying again.`;
27454
+ STUCK_NUDGE_AT = 3;
27455
+ STUCK_WRITE_UP_AT = 6;
27456
+ stuckLoopNudge = () => `\u26A0\uFE0F That exact tool call has now failed ${STUCK_NUDGE_AT} times identically. Change approach \u2014 different arguments, a different tool, a smaller step \u2014 or write up what you have.`;
27457
+ stuckLoopNote = () => `stuck loop: the same call failed ${STUCK_WRITE_UP_AT} times identically \u2014 writing up findings so far`;
27458
+ stuckLoopInstruction = () => `The same tool call has failed ${STUCK_WRITE_UP_AT} times identically; the run is stuck and can make no more tool calls. ${WRITE_UP_REQUEST}`;
27459
+ stuckLoopAnswer = (text) => text ? `\u26A0\uFE0F _Stopped after the same call failed ${STUCK_WRITE_UP_AT} times identically \u2014 findings so far:_
27460
+
27461
+ ${text}` : `Stopped after the same call failed ${STUCK_WRITE_UP_AT} times identically, without finishing. Partial work may exist in the workspace \u2014 look at the failing call in the run's events before trying again.`;
27086
27462
  softStopAnswer = (text) => text ? `\u23F9 _Stopped early by an operator (soft stop) \u2014 findings so far:_
27087
27463
 
27088
27464
  ${text}` : "\u23F9 Stopped early by an operator (soft stop) before any findings were written. Partial work may exist in the workspace.";
@@ -27925,11 +28301,107 @@ var init_messages = __esm({
27925
28301
  init_runEvents();
27926
28302
  init_runRecord();
27927
28303
  init_reply();
28304
+ init_textTurns();
27928
28305
  CONTEXT_MAX_ITEMS = 20;
27929
28306
  CONTEXT_MAX_BYTES = 256 * 1024;
27930
28307
  }
27931
28308
  });
27932
28309
 
28310
+ // ../../src/core/dispatch/staging.ts
28311
+ function stagedBasename(index, name) {
28312
+ return `${index}-${safeBasename(name)}`;
28313
+ }
28314
+ function pullCommandFor(url, basename4) {
28315
+ return `mkdir -p ${ATTACHMENTS_DIR} && curl -fsS -o ${shellQuote(`${ATTACHMENTS_DIR}/${basename4}`)} ${shellQuote(url)}`;
28316
+ }
28317
+ function excludeCommand() {
28318
+ const entry = shellQuote(`${ATTACHMENTS_DIR}/`);
28319
+ return `mkdir -p .git/info && { grep -qxF ${entry} .git/info/exclude 2>/dev/null || echo ${entry} >> .git/info/exclude; }`;
28320
+ }
28321
+ function formatSize(bytes) {
28322
+ if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
28323
+ if (bytes >= 1e6) return `${Math.round(bytes / 1e6)} MB`;
28324
+ if (bytes >= 1e3) return `${Math.round(bytes / 1e3)} KB`;
28325
+ return `${bytes} B`;
28326
+ }
28327
+ function attachmentsLine(outcomes) {
28328
+ const landed = outcomes.filter((o) => o.error === void 0);
28329
+ const failed = outcomes.filter((o) => o.error !== void 0);
28330
+ const parts = [];
28331
+ if (landed.length > 0) {
28332
+ parts.push(
28333
+ `Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map((o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type})`).join(", ")}`
28334
+ );
28335
+ }
28336
+ for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
28337
+ return parts.join(". ");
28338
+ }
28339
+ function noWorkspaceLine(files) {
28340
+ return files.map(
28341
+ (f) => `this agent has no workspace for ${f.name} (${formatSize(f.size)}, ${f.type}); ask \`agent:coding\` to work with it`
28342
+ ).join(". ");
28343
+ }
28344
+ function stagingIndex() {
28345
+ let n2 = 0;
28346
+ return () => ++n2;
28347
+ }
28348
+ async function copyStaged(files, deps) {
28349
+ return Promise.all(
28350
+ files.map(async (file) => {
28351
+ const index = deps.nextIndex();
28352
+ const basename4 = stagedBasename(index, file.name);
28353
+ const key = inboundKey(deps.threadKey, file.messageId, index, file.name);
28354
+ try {
28355
+ await deps.store.copyFromUrl({ url: file.url, size: file.size, key });
28356
+ deps.publish?.({
28357
+ type: "artifact",
28358
+ direction: "in",
28359
+ key,
28360
+ name: file.name,
28361
+ size: file.size,
28362
+ contentType: file.type
28363
+ });
28364
+ return { file, basename: basename4, key };
28365
+ } catch (err2) {
28366
+ return { file, basename: basename4, key, error: `the copy into the store failed: ${describe4(err2)}` };
28367
+ }
28368
+ })
28369
+ );
28370
+ }
28371
+ async function pullStaged(outcomes, deps) {
28372
+ const landed = outcomes.filter((o) => o.error === void 0);
28373
+ if (landed.length === 0) return [...outcomes];
28374
+ const timeoutMs = deps.timeoutMs ?? BASH_TIMEOUT_MAX_MS;
28375
+ const opts = { timeoutMs, ...deps.signal ? { signal: deps.signal } : {} };
28376
+ if (deps.resident) await deps.executor.exec(excludeCommand(), opts);
28377
+ const pulled = /* @__PURE__ */ new Map();
28378
+ for (const o of landed) {
28379
+ const url = await deps.store.presignGet(o.key);
28380
+ const out = await deps.executor.exec(pullCommandFor(url, o.basename), opts);
28381
+ pulled.set(o.key, parseExitPrefix(out).failed ? `the pull into the workspace failed: ${out.trim()}` : void 0);
28382
+ }
28383
+ return outcomes.map((o) => {
28384
+ const error = o.error ?? pulled.get(o.key);
28385
+ return error === void 0 ? { file: o.file, basename: o.basename, key: o.key } : { ...o, error };
28386
+ });
28387
+ }
28388
+ async function stageIntoWorkspace(files, deps) {
28389
+ const outcomes = await pullStaged(await copyStaged(files, deps), deps);
28390
+ return { line: attachmentsLine(outcomes), outcomes };
28391
+ }
28392
+ var ATTACHMENTS_DIR, describe4;
28393
+ var init_staging = __esm({
28394
+ "../../src/core/dispatch/staging.ts"() {
28395
+ "use strict";
28396
+ init_keys();
28397
+ init_bashTimeout();
28398
+ init_shellQuote();
28399
+ init_runEvents();
28400
+ ATTACHMENTS_DIR = "attachments";
28401
+ describe4 = (err2) => err2 instanceof Error ? err2.message : String(err2);
28402
+ }
28403
+ });
28404
+
27933
28405
  // ../../src/core/modelProxy/runBearers.ts
27934
28406
  import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
27935
28407
  function token(runId3, secret2) {
@@ -28525,6 +28997,8 @@ async function registerRun(deps, ctx) {
28525
28997
  admitted,
28526
28998
  parentRunId,
28527
28999
  coordinator,
29000
+ seed,
29001
+ seedTurns,
28528
29002
  agentSource,
28529
29003
  route: route3
28530
29004
  } = ctx;
@@ -28557,7 +29031,8 @@ async function registerRun(deps, ctx) {
28557
29031
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
28558
29032
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
28559
29033
  ...parentRunId !== void 0 ? { parentRunId } : {},
28560
- ...coordinatorFields(coordinator)
29034
+ ...coordinatorFields(coordinator),
29035
+ ...seed !== void 0 ? { seed } : {}
28561
29036
  },
28562
29037
  // Under the run's id, at the card's start (the reservation's, or the
28563
29038
  // carried row's) — a resume replays its events, a restart starts them
@@ -28606,7 +29081,7 @@ async function registerRun(deps, ctx) {
28606
29081
  if (!resume) publishMeta(repoCtx);
28607
29082
  if (!resume && route3) registry.publish(run2.id, { type: "route", ...route3, at: clock3() });
28608
29083
  if (!resume && deps.config.config.runHistory?.includeContext !== false) {
28609
- for (const text of contextMessageTexts(history, humanize)) publishText("context", text);
29084
+ for (const text of contextMessageTexts(seedTurns ?? history, humanize)) publishText("context", text);
28610
29085
  }
28611
29086
  return { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta };
28612
29087
  }
@@ -28628,7 +29103,8 @@ async function reserveRun(deps, ctx) {
28628
29103
  admitted,
28629
29104
  root,
28630
29105
  parentRunId,
28631
- coordinator
29106
+ coordinator,
29107
+ seed
28632
29108
  } = ctx;
28633
29109
  if (!resume && !restart) {
28634
29110
  const requestRow = durableInboxMessage(msg, msg.text, receivedAt);
@@ -28656,6 +29132,7 @@ async function reserveRun(deps, ctx) {
28656
29132
  profile,
28657
29133
  ...parentRunId !== void 0 ? { parentRunId } : {},
28658
29134
  ...coordinatorFields(coordinator),
29135
+ ...seed !== void 0 ? { seed } : {},
28659
29136
  request: requestRow
28660
29137
  },
28661
29138
  card: card.handle ?? null,
@@ -28885,7 +29362,8 @@ async function claimRun(deps, ctx) {
28885
29362
  clock: clock3,
28886
29363
  root,
28887
29364
  parentRunId,
28888
- coordinator
29365
+ coordinator,
29366
+ seed
28889
29367
  } = ctx;
28890
29368
  const { resident, binding } = selection;
28891
29369
  let ledgerRun = ctx.ledgerRun;
@@ -28915,6 +29393,7 @@ async function claimRun(deps, ctx) {
28915
29393
  profile,
28916
29394
  ...parentRunId !== void 0 ? { parentRunId } : {},
28917
29395
  ...coordinatorFields(coordinator),
29396
+ ...seed !== void 0 ? { seed } : {},
28918
29397
  selection: resident === true ? "resident" : "sandbox",
28919
29398
  ...binding?.workspace !== void 0 ? { workspace: binding.workspace } : {},
28920
29399
  ...requestRow !== void 0 ? { request: requestRow } : {}
@@ -29113,6 +29592,157 @@ var init_container = __esm({
29113
29592
  }
29114
29593
  });
29115
29594
 
29595
+ // ../../src/core/harness/pi/extensionSource.ts
29596
+ var BLOCKED_AT_DOOR_PREFIX, BLOCKED_UNAVAILABLE_PREFIX, PI_EXTENSION_SOURCE;
29597
+ var init_extensionSource = __esm({
29598
+ "../../src/core/harness/pi/extensionSource.ts"() {
29599
+ "use strict";
29600
+ BLOCKED_AT_DOOR_PREFIX = "authorization refused at the door: ";
29601
+ BLOCKED_UNAVAILABLE_PREFIX = "authorization unavailable: ";
29602
+ PI_EXTENSION_SOURCE = `// Switchboard's pi harness extension. Written into the run's directory by the
29603
+ // bot before pi starts; loaded with \`-e\`. Imports nothing.
29604
+
29605
+ const BEARER_ENV = "SWITCHBOARD_RUN_BEARER";
29606
+ const URL_ENV = "SWITCHBOARD_HARNESS_URL";
29607
+ /** How long the tool_call hook keeps asking an unreachable bot before it blocks. */
29608
+ const AUTHORIZE_WAIT_MS = 90_000;
29609
+ const AUTHORIZE_RETRY_MS = 2_000;
29610
+
29611
+ function settings() {
29612
+ const url = process.env[URL_ENV];
29613
+ const bearer = process.env[BEARER_ENV];
29614
+ if (!url || !bearer) throw new Error("switchboard harness: " + URL_ENV + " and " + BEARER_ENV + " must be set");
29615
+ return { base: url.replace(/\\/$/, ""), bearer };
29616
+ }
29617
+
29618
+ /** The bot answered, and said no: the status rides along so a caller can tell
29619
+ * a verdict from a bot it could not reach. */
29620
+ class HarnessAnswerError extends Error {
29621
+ constructor(message, status) {
29622
+ super(message);
29623
+ this.status = status;
29624
+ }
29625
+ }
29626
+
29627
+ async function call(method, path, body, signal) {
29628
+ const { base, bearer } = settings();
29629
+ const res = await fetch(base + path, {
29630
+ method,
29631
+ headers: { authorization: "Bearer " + bearer, "content-type": "application/json", accept: "application/json" },
29632
+ body: body === undefined ? undefined : JSON.stringify(body),
29633
+ signal,
29634
+ });
29635
+ const text = await res.text();
29636
+ let json;
29637
+ try {
29638
+ json = JSON.parse(text);
29639
+ } catch {
29640
+ throw new Error("switchboard harness: " + method + " " + path + " answered " + res.status + " with a body that is not JSON");
29641
+ }
29642
+ if (!res.ok) {
29643
+ throw new HarnessAnswerError(
29644
+ "switchboard harness: " + method + " " + path + " answered " + res.status + ": " + (json.error || text),
29645
+ res.status,
29646
+ );
29647
+ }
29648
+ return json;
29649
+ }
29650
+
29651
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29652
+
29653
+ /** The bot's verdict on one tool call. A verdict answers at once, and so does
29654
+ * a refusal at the door \u2014 a 4xx: the bearer wrong, expired or revoked, the
29655
+ * run not on the harness \u2014 since nothing later changes it. A bot that cannot
29656
+ * be reached or is failing (a network error, a 5xx, a body that is not JSON)
29657
+ * is asked again every two seconds until the wait is up, then the call is
29658
+ * blocked naming why. */
29659
+ async function authorize(event) {
29660
+ const started = Date.now();
29661
+ let lastError = "";
29662
+ for (;;) {
29663
+ try {
29664
+ return await call("POST", "/harness/authorize", {
29665
+ toolCallId: event.toolCallId,
29666
+ tool: event.toolName,
29667
+ input: event.input,
29668
+ });
29669
+ } catch (err) {
29670
+ lastError = err instanceof Error ? err.message : String(err);
29671
+ const status = err instanceof HarnessAnswerError ? err.status : undefined;
29672
+ if (status !== undefined && status >= 400 && status < 500) {
29673
+ return { allow: false, reason: "${BLOCKED_AT_DOOR_PREFIX}" + lastError };
29674
+ }
29675
+ if (Date.now() - started >= AUTHORIZE_WAIT_MS) {
29676
+ return { allow: false, reason: "${BLOCKED_UNAVAILABLE_PREFIX}the bot did not answer for 90 s (" + lastError + ")" };
29677
+ }
29678
+ await sleep(AUTHORIZE_RETRY_MS);
29679
+ }
29680
+ }
29681
+ }
29682
+
29683
+ function relayTool(def) {
29684
+ return {
29685
+ name: def.name,
29686
+ label: def.name,
29687
+ description: def.description,
29688
+ parameters: def.inputSchema,
29689
+ async execute(toolCallId, params, signal) {
29690
+ const answer = await call("POST", "/harness/tool", { toolCallId, tool: def.name, input: params }, signal);
29691
+ if (answer.isError) throw new Error(answer.content.map((c) => (c.type === "text" ? c.text : "")).join("\\n"));
29692
+ return { content: answer.content, details: {} };
29693
+ },
29694
+ };
29695
+ }
29696
+
29697
+ export default async function switchboardHarness(pi) {
29698
+ const { tools } = await call("GET", "/harness/tools");
29699
+ for (const def of tools) pi.registerTool(relayTool(def));
29700
+ pi.on("tool_call", async (event) => {
29701
+ const verdict = await authorize(event);
29702
+ if (!verdict.allow) return { block: true, reason: verdict.reason };
29703
+ return undefined;
29704
+ });
29705
+ }
29706
+ `;
29707
+ }
29708
+ });
29709
+
29710
+ // ../../src/core/harness/pi/protocol.ts
29711
+ function parsePiLine(line3) {
29712
+ try {
29713
+ const value = JSON.parse(line3);
29714
+ if (typeof value === "object" && value !== null && typeof value.type === "string") {
29715
+ return value;
29716
+ }
29717
+ } catch {
29718
+ }
29719
+ return void 0;
29720
+ }
29721
+ function piAnsweredWithoutRunning(event) {
29722
+ if (event.type !== "tool_execution_end" || event.isError !== true) return void 0;
29723
+ const tool = typeof event.toolName === "string" ? event.toolName : "";
29724
+ const result = event.result;
29725
+ const content = Array.isArray(result?.content) ? result.content : [];
29726
+ const text = content.filter(
29727
+ (p) => typeof p === "object" && p !== null && p.type === "text" && typeof p.text === "string"
29728
+ ).map((p) => p.text).join("\n");
29729
+ const validation = `Validation failed for tool "${tool}":
29730
+ `;
29731
+ if (text.startsWith(validation)) {
29732
+ const problems = text.slice(validation.length).split("\n\n")[0].split("\n").map((line3) => line3.replace(/^\s*-\s*/, "").trim()).filter((line3) => line3.length > 0);
29733
+ return `its arguments failed pi's validation (${problems.join("; ")})`;
29734
+ }
29735
+ if (text.startsWith(`Tool call "${tool}" was not executed: the response hit the output token limit`))
29736
+ return "its arguments were cut by the output token limit";
29737
+ if (text === `Tool ${tool} not found`) return "the tool is not on pi's list";
29738
+ return void 0;
29739
+ }
29740
+ var init_protocol = __esm({
29741
+ "../../src/core/harness/pi/protocol.ts"() {
29742
+ "use strict";
29743
+ }
29744
+ });
29745
+
29116
29746
  // ../../src/core/harness/pi/bridge.ts
29117
29747
  function describePiToolCall(tool, args) {
29118
29748
  const input = isRecord3(args) ? args : void 0;
@@ -29140,6 +29770,8 @@ var init_bridge2 = __esm({
29140
29770
  "use strict";
29141
29771
  init_formatDuration();
29142
29772
  init_runEvents();
29773
+ init_extensionSource();
29774
+ init_protocol();
29143
29775
  PI_EVENT_DISPOSITION = {
29144
29776
  response: "structure",
29145
29777
  agent_start: "structure",
@@ -29182,8 +29814,23 @@ var init_bridge2 = __esm({
29182
29814
  /** Text beside a bookkeeping-only call, held until the next message decides (run-loop item 15). */
29183
29815
  heldAnswer;
29184
29816
  assistantStartedAt;
29817
+ /** The calls under way: their span, their tool, and whether their end is
29818
+ * judged against the gate (harness-pi item 7). */
29185
29819
  openTools = /* @__PURE__ */ new Map();
29820
+ /** The calls the gate saw — the extension asked `/harness/authorize` for
29821
+ * them (`gateSaw`) — not yet ended. */
29822
+ vetted = /* @__PURE__ */ new Set();
29823
+ /** Whether a call starting now is judged against the gate when it ends.
29824
+ * The harness turns it off while catching up on a re-attach: those calls
29825
+ * were vetted by the bot generation that died, and this one never heard. */
29826
+ judgeGate = true;
29186
29827
  summarizationRetries = 0;
29828
+ /** The gate saw this call: the extension's `tool_call` hook asked the bot
29829
+ * for it, whatever the answer. Called from the authorize route, before pi
29830
+ * runs the tool — so it always precedes the call's `tool_execution_end`. */
29831
+ gateSaw(callId) {
29832
+ this.vetted.add(callId);
29833
+ }
29187
29834
  /** The run's answer as it stands: the last text-only assistant message, else a held bookkeeping text. */
29188
29835
  answer() {
29189
29836
  return this.answerText ?? this.heldAnswer;
@@ -29222,7 +29869,7 @@ var init_bridge2 = __esm({
29222
29869
  this.onToolStart(event);
29223
29870
  break;
29224
29871
  case "tool_execution_end":
29225
- this.onToolEnd(event);
29872
+ this.onToolEnd(event, out);
29226
29873
  break;
29227
29874
  case "compaction_end":
29228
29875
  this.onCompactionEnd(event);
@@ -29285,7 +29932,7 @@ var init_bridge2 = __esm({
29285
29932
  const tool = str5(event.toolName);
29286
29933
  const callId = str5(event.toolCallId);
29287
29934
  const span = this.deps.agentSpan?.start(`tool.${tool}`);
29288
- this.openTools.set(callId, { span, tool });
29935
+ this.openTools.set(callId, { span, tool, judged: this.judgeGate });
29289
29936
  this.toolCalls++;
29290
29937
  const input = isRecord3(event.args) ? event.args : void 0;
29291
29938
  const command2 = tool === "bash" && typeof input?.command === "string" ? { command: redactAndCap(input.command, COMMAND_CAP) } : {};
@@ -29298,7 +29945,7 @@ var init_bridge2 = __esm({
29298
29945
  ...span ? { spanId: span.id } : {}
29299
29946
  });
29300
29947
  }
29301
- onToolEnd(event) {
29948
+ onToolEnd(event, out) {
29302
29949
  const callId = str5(event.toolCallId);
29303
29950
  const open = this.openTools.get(callId);
29304
29951
  this.openTools.delete(callId);
@@ -29321,6 +29968,23 @@ var init_bridge2 = __esm({
29321
29968
  ok: ok2,
29322
29969
  ...exit.exitCode !== void 0 ? { exitCode: exit.exitCode } : {}
29323
29970
  });
29971
+ const seen = this.vetted.delete(callId);
29972
+ if (!seen && open?.judged) {
29973
+ const rejection = piAnsweredWithoutRunning(event);
29974
+ if (rejection !== void 0) {
29975
+ this.note(
29976
+ "harness_error",
29977
+ `pi answered the ${tool} call ${callId} itself, before the gate: ${redactAndCap(rejection, 300)}; nothing ran`
29978
+ );
29979
+ } else if (isError && (text.startsWith(BLOCKED_AT_DOOR_PREFIX) || text.startsWith(BLOCKED_UNAVAILABLE_PREFIX))) {
29980
+ this.note(
29981
+ "harness_error",
29982
+ `the extension blocked the ${tool} call ${callId} without the gate's verdict: ${redactAndCap(text, 300)}; nothing ran`
29983
+ );
29984
+ } else {
29985
+ out.gateBypassed = { callId, tool };
29986
+ }
29987
+ }
29324
29988
  }
29325
29989
  onCompactionEnd(event) {
29326
29990
  const result = isRecord3(event.result) ? event.result : void 0;
@@ -29360,6 +30024,7 @@ var init_bridge2 = __esm({
29360
30024
  this.emit({ type: "tool_result", tool: open.tool, ok: false, callId, summary: redactAndCap(reason) });
29361
30025
  }
29362
30026
  this.openTools.clear();
30027
+ this.vetted.clear();
29363
30028
  }
29364
30029
  };
29365
30030
  }
@@ -29536,120 +30201,10 @@ var init_mirror = __esm({
29536
30201
  }
29537
30202
  });
29538
30203
 
29539
- // ../../src/core/harness/pi/extensionSource.ts
29540
- var PI_EXTENSION_SOURCE;
29541
- var init_extensionSource = __esm({
29542
- "../../src/core/harness/pi/extensionSource.ts"() {
29543
- "use strict";
29544
- PI_EXTENSION_SOURCE = `// Switchboard's pi harness extension. Written into the run's directory by the
29545
- // bot before pi starts; loaded with \`-e\`. Imports nothing.
29546
-
29547
- const BEARER_ENV = "SWITCHBOARD_RUN_BEARER";
29548
- const URL_ENV = "SWITCHBOARD_HARNESS_URL";
29549
- /** How long the tool_call hook keeps asking an unreachable bot before it blocks. */
29550
- const AUTHORIZE_WAIT_MS = 90_000;
29551
- const AUTHORIZE_RETRY_MS = 2_000;
29552
-
29553
- function settings() {
29554
- const url = process.env[URL_ENV];
29555
- const bearer = process.env[BEARER_ENV];
29556
- if (!url || !bearer) throw new Error("switchboard harness: " + URL_ENV + " and " + BEARER_ENV + " must be set");
29557
- return { base: url.replace(/\\/$/, ""), bearer };
29558
- }
29559
-
29560
- /** The bot answered, and said no: the status rides along so a caller can tell
29561
- * a verdict from a bot it could not reach. */
29562
- class HarnessAnswerError extends Error {
29563
- constructor(message, status) {
29564
- super(message);
29565
- this.status = status;
29566
- }
29567
- }
29568
-
29569
- async function call(method, path, body, signal) {
29570
- const { base, bearer } = settings();
29571
- const res = await fetch(base + path, {
29572
- method,
29573
- headers: { authorization: "Bearer " + bearer, "content-type": "application/json", accept: "application/json" },
29574
- body: body === undefined ? undefined : JSON.stringify(body),
29575
- signal,
29576
- });
29577
- const text = await res.text();
29578
- let json;
29579
- try {
29580
- json = JSON.parse(text);
29581
- } catch {
29582
- throw new Error("switchboard harness: " + method + " " + path + " answered " + res.status + " with a body that is not JSON");
29583
- }
29584
- if (!res.ok) {
29585
- throw new HarnessAnswerError(
29586
- "switchboard harness: " + method + " " + path + " answered " + res.status + ": " + (json.error || text),
29587
- res.status,
29588
- );
29589
- }
29590
- return json;
29591
- }
29592
-
29593
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29594
-
29595
- /** The bot's verdict on one tool call. A verdict answers at once, and so does
29596
- * a refusal at the door \u2014 a 4xx: the bearer wrong, expired or revoked, the
29597
- * run not on the harness \u2014 since nothing later changes it. A bot that cannot
29598
- * be reached or is failing (a network error, a 5xx, a body that is not JSON)
29599
- * is asked again every two seconds until the wait is up, then the call is
29600
- * blocked naming why. */
29601
- async function authorize(event) {
29602
- const started = Date.now();
29603
- let lastError = "";
29604
- for (;;) {
29605
- try {
29606
- return await call("POST", "/harness/authorize", {
29607
- toolCallId: event.toolCallId,
29608
- tool: event.toolName,
29609
- input: event.input,
29610
- });
29611
- } catch (err) {
29612
- lastError = err instanceof Error ? err.message : String(err);
29613
- const status = err instanceof HarnessAnswerError ? err.status : undefined;
29614
- if (status !== undefined && status >= 400 && status < 500) {
29615
- return { allow: false, reason: "authorization refused at the door: " + lastError };
29616
- }
29617
- if (Date.now() - started >= AUTHORIZE_WAIT_MS) {
29618
- return { allow: false, reason: "authorization unavailable: the bot did not answer for 90 s (" + lastError + ")" };
29619
- }
29620
- await sleep(AUTHORIZE_RETRY_MS);
29621
- }
29622
- }
29623
- }
29624
-
29625
- function relayTool(def) {
29626
- return {
29627
- name: def.name,
29628
- label: def.name,
29629
- description: def.description,
29630
- parameters: def.inputSchema,
29631
- async execute(toolCallId, params, signal) {
29632
- const answer = await call("POST", "/harness/tool", { toolCallId, tool: def.name, input: params }, signal);
29633
- if (answer.isError) throw new Error(answer.content.map((c) => (c.type === "text" ? c.text : "")).join("\\n"));
29634
- return { content: answer.content, details: {} };
29635
- },
29636
- };
29637
- }
29638
-
29639
- export default async function switchboardHarness(pi) {
29640
- const { tools } = await call("GET", "/harness/tools");
29641
- for (const def of tools) pi.registerTool(relayTool(def));
29642
- pi.on("tool_call", async (event) => {
29643
- const verdict = await authorize(event);
29644
- if (!verdict.allow) return { block: true, reason: verdict.reason };
29645
- return undefined;
29646
- });
29647
- }
29648
- `;
29649
- }
29650
- });
29651
-
29652
30204
  // ../../src/core/harness/pi/process.ts
30205
+ function piBuiltinToolsFor(identity2) {
30206
+ return identity2 === "write" ? PI_BUILTIN_TOOLS : PI_READ_TOOLS;
30207
+ }
29653
30208
  function piThinkingLevel(effort2) {
29654
30209
  return effort2;
29655
30210
  }
@@ -29679,7 +30234,7 @@ function piLaunchArgs(spec) {
29679
30234
  "-e",
29680
30235
  spec.paths.extension,
29681
30236
  "--tools",
29682
- [...PI_BUILTIN_TOOLS, ...spec.relayTools].join(","),
30237
+ [...piBuiltinToolsFor(spec.identity), ...spec.relayTools].join(","),
29683
30238
  "--provider",
29684
30239
  PROXY_PROVIDER,
29685
30240
  "--model",
@@ -29698,6 +30253,13 @@ function piLaunchEnv(spec, bearer) {
29698
30253
  PI_TELEMETRY: "0"
29699
30254
  };
29700
30255
  }
30256
+ function takesAdaptiveThinking(modelId) {
30257
+ const version2 = /(?:^|-)(\d{1,2})(?:-(\d{1,2}))?(?=-|$)/.exec(modelId);
30258
+ if (!version2) return true;
30259
+ const major = Number(version2[1]);
30260
+ const minor = version2[2] === void 0 ? 0 : Number(version2[2]);
30261
+ return major > 4 || major === 4 && minor >= 6;
30262
+ }
29701
30263
  function piModelsJson(spec) {
29702
30264
  const anthropic = spec.model.providerType === "anthropic";
29703
30265
  const base = spec.harnessUrl.replace(/\/$/, "");
@@ -29713,6 +30275,7 @@ function piModelsJson(spec) {
29713
30275
  id: spec.model.id,
29714
30276
  name: spec.model.id,
29715
30277
  reasoning: true,
30278
+ ...anthropic ? { compat: { forceAdaptiveThinking: takesAdaptiveThinking(spec.model.id) } } : {},
29716
30279
  input: ["text", "image"],
29717
30280
  contextWindow: 2e5,
29718
30281
  maxTokens: spec.model.maxTokens,
@@ -29726,17 +30289,18 @@ function piModelsJson(spec) {
29726
30289
  2
29727
30290
  ) + "\n";
29728
30291
  }
29729
- function harnessPromptNote(relayTools) {
30292
+ function harnessPromptNote(relayTools, identity2) {
30293
+ const write = identity2 === "write";
29730
30294
  return [
29731
- "HARNESS NOTE: this run's workspace tools are pi's own \u2014 `read`, `bash`, `edit`, `write`, `grep`, `find` and `ls`, all running in the worktree.",
29732
- "Where these instructions say `read_file` use `read`; where they say `write_file` use `write` (a whole file) or `edit` (a targeted change).",
30295
+ write ? "HARNESS NOTE: this run's workspace tools are pi's own \u2014 `read`, `bash`, `edit`, `write`, `grep`, `find` and `ls`, all running in the worktree." : "HARNESS NOTE: this run's workspace tools are pi's own \u2014 `read`, `bash`, `grep`, `find` and `ls`, all running in the worktree; it has no `edit` and no `write`: the run is read-only, and a call to either is refused.",
30296
+ write ? "Where these instructions say `read_file` use `read`; where they say `write_file` use `write` (a whole file) or `edit` (a targeted change)." : "Where these instructions say `read_file` use `read`.",
29733
30297
  relayTools.length > 0 ? `Every other tool named above is available under its own name: ${relayTools.map((t) => `\`${t}\``).join(", ")}.` : "No other tools are available in this run."
29734
30298
  ].join(" ");
29735
30299
  }
29736
30300
  function piSystemPrompt(spec) {
29737
30301
  return `${spec.system.trimEnd()}
29738
30302
 
29739
- ${harnessPromptNote(spec.relayTools)}
30303
+ ${harnessPromptNote(spec.relayTools, spec.identity)}
29740
30304
  `;
29741
30305
  }
29742
30306
  function piSettingsJson() {
@@ -29750,7 +30314,7 @@ function piLaunchFiles(spec) {
29750
30314
  { path: spec.paths.extension, content: PI_EXTENSION_SOURCE }
29751
30315
  ];
29752
30316
  }
29753
- var RUN_BEARER_ENV, HARNESS_URL_ENV, PROXY_PROVIDER, PI_BUILTIN_TOOLS;
30317
+ var RUN_BEARER_ENV, HARNESS_URL_ENV, PROXY_PROVIDER, PI_BUILTIN_TOOLS, PI_READ_TOOLS;
29754
30318
  var init_process = __esm({
29755
30319
  "../../src/core/harness/pi/process.ts"() {
29756
30320
  "use strict";
@@ -29759,23 +30323,7 @@ var init_process = __esm({
29759
30323
  HARNESS_URL_ENV = "SWITCHBOARD_HARNESS_URL";
29760
30324
  PROXY_PROVIDER = "switchboard";
29761
30325
  PI_BUILTIN_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
29762
- }
29763
- });
29764
-
29765
- // ../../src/core/harness/pi/protocol.ts
29766
- function parsePiLine(line3) {
29767
- try {
29768
- const value = JSON.parse(line3);
29769
- if (typeof value === "object" && value !== null && typeof value.type === "string") {
29770
- return value;
29771
- }
29772
- } catch {
29773
- }
29774
- return void 0;
29775
- }
29776
- var init_protocol = __esm({
29777
- "../../src/core/harness/pi/protocol.ts"() {
29778
- "use strict";
30326
+ PI_READ_TOOLS = ["read", "bash", "grep", "find", "ls"];
29779
30327
  }
29780
30328
  });
29781
30329
 
@@ -29923,6 +30471,7 @@ async function runPiHarness(deps, run2) {
29923
30471
  let writeUp;
29924
30472
  let writeUpAt;
29925
30473
  let hardStopped = false;
30474
+ let bypass;
29926
30475
  const toolsBlocked = () => {
29927
30476
  if (!writeUp) return void 0;
29928
30477
  if (writeUp.kind === "time")
@@ -29931,14 +30480,16 @@ async function runPiHarness(deps, run2) {
29931
30480
  return "the run has hit its turn guard: no more tool calls \u2014 write your final answer now";
29932
30481
  return "an operator asked this run to stop: no more tool calls \u2014 write your final answer now";
29933
30482
  };
30483
+ const rules = { ...run2.rules, identity: run2.agent.identity };
29934
30484
  const live = {
29935
30485
  runId: run2.runId,
29936
30486
  tools: run2.tools,
29937
30487
  toolContext: run2.toolContext,
29938
30488
  ...run2.backend ? { backend: run2.backend } : {},
29939
- rules: run2.rules,
30489
+ rules,
29940
30490
  emit,
29941
30491
  toolSpan: (callId) => bridge.openSpan(callId),
30492
+ gateSaw: (callId) => bridge.gateSaw(callId),
29942
30493
  toolsBlocked
29943
30494
  };
29944
30495
  const forget = deps.registry.register(live);
@@ -29948,6 +30499,7 @@ async function runPiHarness(deps, run2) {
29948
30499
  model: { id: run2.model.id, providerType: run2.model.providerType, maxTokens: run2.agent.maxTokens },
29949
30500
  harnessUrl: deps.harnessUrl,
29950
30501
  ...run2.effort ? { effort: run2.effort } : {},
30502
+ identity: run2.agent.identity,
29951
30503
  system: run2.system,
29952
30504
  relayTools: run2.tools.map((t) => t.name)
29953
30505
  };
@@ -30108,8 +30660,15 @@ async function runPiHarness(deps, run2) {
30108
30660
  if (next.done) break;
30109
30661
  const event = parsePiLine(next.value);
30110
30662
  if (!event) continue;
30663
+ bridge.judgeGate = !catchingUp;
30111
30664
  const obs = bridge.observe(event);
30112
30665
  for (const reply of obs.replies) transport.send(reply);
30666
+ if (obs.gateBypassed) {
30667
+ bypass = new GateBypassed(obs.gateBypassed.tool, obs.gateBypassed.callId);
30668
+ note("harness_error", `${bypass.message} \u2014 the run is stopped`);
30669
+ transport.send({ type: "abort" });
30670
+ break;
30671
+ }
30113
30672
  if (obs.response) {
30114
30673
  const r = obs.response;
30115
30674
  if (r.id === "state" && r.success === true && typeof r.data?.sessionFile === "string") {
@@ -30140,6 +30699,7 @@ async function runPiHarness(deps, run2) {
30140
30699
  note("stopped", hardStopNote(), "hard");
30141
30700
  return HARD_STOP_MESSAGE;
30142
30701
  }
30702
+ if (bypass) throw bypass;
30143
30703
  if (!settled) {
30144
30704
  const tail = await container.tail(paths.errLog, 2e3);
30145
30705
  throw new Error(`pi exited before the run settled${tail.trim() ? `: ${redactAndCap(tail.trim(), 400)}` : ""}`);
@@ -30152,15 +30712,17 @@ async function runPiHarness(deps, run2) {
30152
30712
  return text || "_(no response)_";
30153
30713
  } finally {
30154
30714
  transport?.close();
30155
- bridge.closeOpenSpans(hardStopped ? "the run was hard-stopped" : "the run ended");
30715
+ bridge.closeOpenSpans(
30716
+ hardStopped ? "the run was hard-stopped" : bypass ? "the run was stopped: a tool call bypassed the gate" : "the run ended"
30717
+ );
30156
30718
  save();
30157
30719
  forget();
30158
30720
  if (pid !== void 0) await container.kill(pid).catch(() => {
30159
30721
  });
30160
- agentSpan?.end(hardStopped ? "error" : "ok");
30722
+ agentSpan?.end(hardStopped || bypass ? "error" : "ok");
30161
30723
  }
30162
30724
  }
30163
- var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused;
30725
+ var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CONTINUE_PROMPT, PromptRefused, GateBypassed;
30164
30726
  var init_harness = __esm({
30165
30727
  "../../src/core/harness/pi/harness.ts"() {
30166
30728
  "use strict";
@@ -30181,6 +30743,12 @@ var init_harness = __esm({
30181
30743
  this.name = "PromptRefused";
30182
30744
  }
30183
30745
  };
30746
+ GateBypassed = class extends Error {
30747
+ constructor(tool, callId) {
30748
+ super(`the gate was bypassed: pi ran ${tool} (call ${callId}) without asking the bot`);
30749
+ this.name = "GateBypassed";
30750
+ }
30751
+ };
30184
30752
  }
30185
30753
  });
30186
30754
 
@@ -30648,8 +31216,8 @@ var init_descriptionTurn = __esm({
30648
31216
  // ../../src/core/statusCardLabel.ts
30649
31217
  function quietSuffix(quietMs, inFlightTool) {
30650
31218
  if (quietMs <= QUIET_SUFFIX_AFTER_MS) return "";
30651
- const secs = Math.round(quietMs / 1e3);
30652
- return inFlightTool ? ` \u2014 running ${inFlightTool} (${secs}s)` : ` \u2014 thinking (${secs}s since last tool)`;
31219
+ const secs2 = Math.round(quietMs / 1e3);
31220
+ return inFlightTool ? ` \u2014 running ${inFlightTool} (${secs2}s)` : ` \u2014 thinking (${secs2}s since last tool)`;
30653
31221
  }
30654
31222
  function inFlightToolAfter(current, e) {
30655
31223
  if (e.type === "tool_call") return e.tool;
@@ -30701,6 +31269,7 @@ async function runLoop2(deps, ctx) {
30701
31269
  wait,
30702
31270
  parentRunId,
30703
31271
  coordinator,
31272
+ seed,
30704
31273
  fixRound
30705
31274
  } = ctx;
30706
31275
  const agent = budgetedAgent(ctx.agent, profile);
@@ -30822,10 +31391,23 @@ async function runLoop2(deps, ctx) {
30822
31391
  let runDiagnosis;
30823
31392
  const releaseWorkspace = (span) => round2.release({ hardStopped: run2.control.requested === "hard", ...span ? { span } : {} });
30824
31393
  const attachFile = io.attachFile?.bind(io);
31394
+ const uploadTicket = io.uploadTicket?.bind(io);
31395
+ const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
31396
+ let artifactSeq = 0;
31397
+ const runUrl = runPageLink(run2.id);
31398
+ const artifacts = deps.artifacts ? {
31399
+ store: deps.artifacts,
31400
+ runId: run2.id,
31401
+ nextSeq: () => ++artifactSeq,
31402
+ ...runUrl ? { runUrl } : {},
31403
+ reply: (text) => io.reply(text)
31404
+ } : void 0;
30825
31405
  const toolContext = {
30826
31406
  executor,
30827
31407
  reportProgress,
30828
31408
  ...attachFile ? { attach: attachFile } : {},
31409
+ ...artifacts ? { artifacts } : {},
31410
+ ...uploadTicket ? { uploadTicket } : {},
30829
31411
  web: webCapability(),
30830
31412
  skills: deps.skills,
30831
31413
  github: githubCapabilityFor(deps, msg.userId),
@@ -30927,6 +31509,23 @@ async function runLoop2(deps, ctx) {
30927
31509
  // operator stop from /runs
30928
31510
  inbox: admitted.inbox,
30929
31511
  // thread follow-ups steered into this run (thread-admission item 2)
31512
+ // A steered follow-up's staged files (record 0033): copied into the
31513
+ // store and pulled into this workspace before the model reads the turn.
31514
+ ...deps.artifacts ? {
31515
+ stageFollowUps: async (inputs) => {
31516
+ const files = inputs.flatMap((i) => i.staged ?? []);
31517
+ if (files.length === 0) return "";
31518
+ const staged = await stageIntoWorkspace(files, {
31519
+ store: deps.artifacts,
31520
+ threadKey: msg.threadKey,
31521
+ publish: (e) => registry.publish(run2.id, e),
31522
+ nextIndex: nextStagedIndex,
31523
+ executor,
31524
+ resident: round2.selection.resident !== void 0
31525
+ });
31526
+ return staged.line;
31527
+ }
31528
+ } : {},
30930
31529
  // The step record before each step's tools (run-history item 35).
30931
31530
  ...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun) } : {},
30932
31531
  // A resume re-enters the loop from the plan (run-history item 37).
@@ -31150,7 +31749,8 @@ async function runLoop2(deps, ctx) {
31150
31749
  ...dispositions !== void 0 ? { dispositions } : {},
31151
31750
  ...reviewPost !== void 0 ? { reviewPost } : {},
31152
31751
  ...parentRunId !== void 0 ? { parentRunId } : {},
31153
- ...coordinator !== void 0 ? { coordinator } : {}
31752
+ ...coordinator !== void 0 ? { coordinator } : {},
31753
+ ...seed !== void 0 ? { seed } : {}
31154
31754
  });
31155
31755
  if (runFailed)
31156
31756
  await root.span(
@@ -31209,6 +31809,7 @@ var init_runLoop = __esm({
31209
31809
  init_secrets2();
31210
31810
  init_record();
31211
31811
  init_reply();
31812
+ init_staging();
31212
31813
  init_run2();
31213
31814
  }
31214
31815
  });
@@ -31675,7 +32276,7 @@ async function plan(deps, input) {
31675
32276
  try {
31676
32277
  planId = planIdOf(request.planPath);
31677
32278
  } catch (err2) {
31678
- return { ok: false, reply: `\u{1F6AB} ${describe3(err2)}` };
32279
+ return { ok: false, reply: `\u{1F6AB} ${describe5(err2)}` };
31679
32280
  }
31680
32281
  let text;
31681
32282
  try {
@@ -31683,7 +32284,7 @@ async function plan(deps, input) {
31683
32284
  } catch (err2) {
31684
32285
  return {
31685
32286
  ok: false,
31686
- reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${describe3(err2)}`
32287
+ reply: `\u{1F6AB} The plan \`${request.planPath}\` could not be read at \`${base}\` in ${entry.repo}: ${describe5(err2)}`
31687
32288
  };
31688
32289
  }
31689
32290
  const graph = parsePlanGraph(text, planId);
@@ -31696,7 +32297,7 @@ async function plan(deps, input) {
31696
32297
  try {
31697
32298
  selected = openPlanCursor(graph, request.units).order;
31698
32299
  } catch (err2) {
31699
- return { ok: false, reply: `\u{1F6AB} ${describe3(err2)}` };
32300
+ return { ok: false, reply: `\u{1F6AB} ${describe5(err2)}` };
31700
32301
  }
31701
32302
  return { ok: true, planned: { kind: "plan", planId, path: request.planPath, base, graph, selected, identity: identity2 } };
31702
32303
  }
@@ -31812,7 +32413,7 @@ async function start(deps, input, instance, units, where, write) {
31812
32413
  try {
31813
32414
  answer = await deps.create(instance.id);
31814
32415
  } catch (err2) {
31815
- answer = { kind: "unanswered", reason: describe3(err2) };
32416
+ answer = { kind: "unanswered", reason: describe5(err2) };
31816
32417
  }
31817
32418
  switch (answer.kind) {
31818
32419
  case "created": {
@@ -31834,7 +32435,7 @@ async function start(deps, input, instance, units, where, write) {
31834
32435
  );
31835
32436
  }
31836
32437
  }
31837
- var refused3, describe3, RUNNING, ENDED;
32438
+ var refused3, describe5, RUNNING, ENDED;
31838
32439
  var init_handOff = __esm({
31839
32440
  "../../src/core/coordinator/handOff.ts"() {
31840
32441
  "use strict";
@@ -31842,7 +32443,7 @@ var init_handOff = __esm({
31842
32443
  init_coordinator();
31843
32444
  init_briefs();
31844
32445
  refused3 = (reply) => ({ status: "aborted", reply });
31845
- describe3 = (err2) => err2 instanceof Error ? err2.message : String(err2);
32446
+ describe5 = (err2) => err2 instanceof Error ? err2.message : String(err2);
31846
32447
  RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
31847
32448
  ENDED = /* @__PURE__ */ new Set(["complete", "errored", "terminated"]);
31848
32449
  }
@@ -32533,22 +33134,22 @@ function createRunEnding(deps) {
32533
33134
  try {
32534
33135
  deps.registry.seal(id, replyOk === void 0 ? {} : { replyOk });
32535
33136
  } catch (err2) {
32536
- log(`[ending] seal ${id}: ${describe4(err2)}`);
33137
+ log(`[ending] seal ${id}: ${describe6(err2)}`);
32537
33138
  }
32538
33139
  try {
32539
33140
  afterSeal?.();
32540
33141
  } catch (err2) {
32541
- log(`[ending] after seal ${id}: ${describe4(err2)}`);
33142
+ log(`[ending] after seal ${id}: ${describe6(err2)}`);
32542
33143
  }
32543
33144
  }
32544
33145
  for (const entry of pending2.splice(0)) {
32545
33146
  try {
32546
33147
  const result = deps.registry.seal(entry.runId);
32547
33148
  void Promise.resolve(entry.write(result, entry.failedAfterFinish)).catch(
32548
- (err2) => log(`[ending] record ${entry.runId}: ${describe4(err2)}`)
33149
+ (err2) => log(`[ending] record ${entry.runId}: ${describe6(err2)}`)
32549
33150
  );
32550
33151
  } catch (err2) {
32551
- log(`[ending] record ${entry.runId}: ${describe4(err2)}`);
33152
+ log(`[ending] record ${entry.runId}: ${describe6(err2)}`);
32552
33153
  }
32553
33154
  }
32554
33155
  };
@@ -32558,7 +33159,7 @@ function createRunEnding(deps) {
32558
33159
  try {
32559
33160
  deps.onFinished?.(runId3);
32560
33161
  } catch (err2) {
32561
- log(`[ending] finished ${runId3}: ${describe4(err2)}`);
33162
+ log(`[ending] finished ${runId3}: ${describe6(err2)}`);
32562
33163
  }
32563
33164
  },
32564
33165
  register(entry) {
@@ -32586,7 +33187,7 @@ function createRunEnding(deps) {
32586
33187
  }
32587
33188
  };
32588
33189
  }
32589
- function describe4(err2) {
33190
+ function describe6(err2) {
32590
33191
  return err2 instanceof Error ? err2.message : String(err2);
32591
33192
  }
32592
33193
  var init_runEnding = __esm({
@@ -32715,6 +33316,7 @@ async function dispatch(deps, msg, io, opts = {}) {
32715
33316
  if (outcome.kind === "redispatch")
32716
33317
  return dispatch(deps, msg, io, {
32717
33318
  ...opts.parent ? { parent: opts.parent } : {},
33319
+ ...opts.seed ? { seed: opts.seed } : {},
32718
33320
  ...opts.coordinator ? { coordinator: opts.coordinator } : {}
32719
33321
  });
32720
33322
  if (outcome.kind !== "proceed") return ended;
@@ -32784,7 +33386,8 @@ async function dispatch(deps, msg, io, opts = {}) {
32784
33386
  }
32785
33387
  const contractBlock = opts.contract ? renderContract(opts.contract, { maxChars: DEFAULT_CONTRACT_MAX_CHARS }).text : void 0;
32786
33388
  const requestText = route3?.parts ? compoundBrief(directives.text, route3.parts) : directives.text;
32787
- const built = buildMessages(history, requestText, msg.images, msg.documents);
33389
+ const seed = opts.seed ? "parent" : "channel";
33390
+ const built = buildMessages(opts.seed ?? history, requestText, msg.images, msg.documents);
32788
33391
  const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(built, contractBlock) : built;
32789
33392
  const headPreflight = await authorizePrHead({
32790
33393
  msg,
@@ -32821,11 +33424,21 @@ async function dispatch(deps, msg, io, opts = {}) {
32821
33424
  admitted,
32822
33425
  parentRunId,
32823
33426
  coordinator,
33427
+ seed,
33428
+ ...opts.seed ? { seedTurns: opts.seed } : {},
32824
33429
  agentSource,
32825
33430
  ...routeEvent ? { route: routeEvent } : {}
32826
33431
  });
32827
33432
  const { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta } = registration;
32828
33433
  registered = run2;
33434
+ const staged = !resume && deps.artifacts && msg.staged && msg.staged.length > 0 ? msg.staged : [];
33435
+ const nextStagedIndex = stagingIndex();
33436
+ const stagedCopies = staged.length > 0 && agent.machine !== "none" ? copyStaged(staged, {
33437
+ store: deps.artifacts,
33438
+ threadKey: msg.threadKey,
33439
+ nextIndex: nextStagedIndex,
33440
+ publish: (e) => registry.publish(runId3, e)
33441
+ }) : void 0;
32829
33442
  const reservation = await reserveRun(deps, {
32830
33443
  msg,
32831
33444
  agent,
@@ -32843,7 +33456,8 @@ async function dispatch(deps, msg, io, opts = {}) {
32843
33456
  admitted,
32844
33457
  root,
32845
33458
  parentRunId,
32846
- coordinator
33459
+ coordinator,
33460
+ seed
32847
33461
  });
32848
33462
  if (reservation) {
32849
33463
  reserved = reservation.reserved;
@@ -32873,6 +33487,22 @@ async function dispatch(deps, msg, io, opts = {}) {
32873
33487
  });
32874
33488
  return ended;
32875
33489
  }
33490
+ if (staged.length > 0) {
33491
+ const line3 = stagedCopies ? attachmentsLine(
33492
+ await pullStaged(await stagedCopies, {
33493
+ store: deps.artifacts,
33494
+ executor,
33495
+ resident: resident !== void 0
33496
+ })
33497
+ ) : noWorkspaceLine(staged);
33498
+ const request = messages[messages.length - 1];
33499
+ if (request && request.role === "user" && line3) {
33500
+ request.content = Array.isArray(request.content) ? [...request.content, { type: "text", text: line3 }] : [
33501
+ { type: "text", text: request.content },
33502
+ { type: "text", text: line3 }
33503
+ ];
33504
+ }
33505
+ }
32876
33506
  const bearer = mintRunBearer(deps, { runId: runId3, agent, profile, resolved, registry, root, clock: clock3 });
32877
33507
  const headGate = await authorizeAttachedHead(deps, {
32878
33508
  msg,
@@ -32937,7 +33567,8 @@ async function dispatch(deps, msg, io, opts = {}) {
32937
33567
  registry,
32938
33568
  resume,
32939
33569
  parentRunId,
32940
- coordinator
33570
+ coordinator,
33571
+ seed
32941
33572
  });
32942
33573
  ledgerRun = await claimRun(deps, {
32943
33574
  msg,
@@ -32960,7 +33591,8 @@ async function dispatch(deps, msg, io, opts = {}) {
32960
33591
  clock: clock3,
32961
33592
  root,
32962
33593
  parentRunId,
32963
- coordinator
33594
+ coordinator,
33595
+ seed
32964
33596
  });
32965
33597
  const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
32966
33598
  { core: deps, dispatch, registry, clock: clock3 },
@@ -32980,6 +33612,7 @@ async function dispatch(deps, msg, io, opts = {}) {
32980
33612
  agent,
32981
33613
  profile,
32982
33614
  resolved,
33615
+ stagingIndex: nextStagedIndex,
32983
33616
  provider,
32984
33617
  model,
32985
33618
  messages,
@@ -33013,6 +33646,7 @@ async function dispatch(deps, msg, io, opts = {}) {
33013
33646
  wait,
33014
33647
  parentRunId,
33015
33648
  coordinator,
33649
+ seed,
33016
33650
  ...opts.fixRound ? { fixRound: opts.fixRound } : {},
33017
33651
  ...bearer !== void 0 ? { bearer } : {}
33018
33652
  });
@@ -33128,6 +33762,7 @@ var init_dispatcher = __esm({
33128
33762
  init_route();
33129
33763
  init_authorize2();
33130
33764
  init_messages();
33765
+ init_staging();
33131
33766
  init_provision();
33132
33767
  init_run2();
33133
33768
  init_runLoop();
@@ -33146,7 +33781,7 @@ var init_dispatcher = __esm({
33146
33781
  });
33147
33782
 
33148
33783
  // ../../src/core/runHistoryWriter.ts
33149
- function describe5(err2) {
33784
+ function describe7(err2) {
33150
33785
  return err2 instanceof Error ? err2.message : String(err2);
33151
33786
  }
33152
33787
  function createRunHistoryWriter(opts) {
@@ -33163,7 +33798,7 @@ function createRunHistoryWriter(opts) {
33163
33798
  try {
33164
33799
  opts.onPersisted?.(id);
33165
33800
  } catch (err2) {
33166
- opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe5(err2)}`);
33801
+ opts.warn(`[run-history] onPersisted hook failed for ${id}: ${describe7(err2)}`);
33167
33802
  }
33168
33803
  };
33169
33804
  const attemptAll = async (record2, flag2, sink, span) => {
@@ -33186,12 +33821,12 @@ function createRunHistoryWriter(opts) {
33186
33821
  }
33187
33822
  if (err2 instanceof PermanentStoreError) {
33188
33823
  failures++;
33189
- opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe5(err2)}`);
33824
+ opts.warn(`[run-history] ${record2.id} not persisted (permanent, not retried): ${describe7(err2)}`);
33190
33825
  return;
33191
33826
  }
33192
33827
  if (attempt >= attempts) {
33193
33828
  failures++;
33194
- opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe5(err2)}`);
33829
+ opts.warn(`[run-history] ${record2.id} not persisted after ${attempts} attempts: ${describe7(err2)}`);
33195
33830
  return;
33196
33831
  }
33197
33832
  await sleep2(jittered(RUN_HISTORY_RETRY_DELAYS_MS[attempt - 1]));
@@ -33211,7 +33846,7 @@ function createRunHistoryWriter(opts) {
33211
33846
  }
33212
33847
  const p = attemptAll(record2, flag2, writeOpts?.via ?? opts.store, writeOpts?.span).catch((err2) => {
33213
33848
  failures++;
33214
- opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe5(err2)}`);
33849
+ opts.warn(`[run-history] ${record2.id} writer failed unexpectedly: ${describe7(err2)}`);
33215
33850
  }).finally(() => {
33216
33851
  if (flag2) provisionalFlags.delete(flag2);
33217
33852
  void inFlight.delete(p);
@@ -33605,7 +34240,7 @@ function createLedgerWriteThrough(opts) {
33605
34240
  return "untracked";
33606
34241
  }
33607
34242
  if (err2 instanceof PermanentStoreError || attempt >= claimAttempts) {
33608
- warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe6(err2)}`);
34243
+ warn(`[ledger] ${req.threadKey} not tracked: claim failed after ${attempt} attempt(s): ${describe8(err2)}`);
33609
34244
  return "untracked";
33610
34245
  }
33611
34246
  await sleep2(RETRY_MS[Math.min(attempt, RETRY_MS.length) - 1]);
@@ -33729,7 +34364,7 @@ function createLedgerWriteThrough(opts) {
33729
34364
  if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
33730
34365
  else this.seeded = true;
33731
34366
  } catch (err2) {
33732
- this.detach(`seed failed: ${describe6(err2)}`);
34367
+ this.detach(`seed failed: ${describe8(err2)}`);
33733
34368
  }
33734
34369
  }
33735
34370
  async step(report2) {
@@ -33752,7 +34387,7 @@ function createLedgerWriteThrough(opts) {
33752
34387
  return;
33753
34388
  } catch (err2) {
33754
34389
  if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
33755
- this.detach(`step ${record2.step} failed: ${describe6(err2)}`);
34390
+ this.detach(`step ${record2.step} failed: ${describe8(err2)}`);
33756
34391
  return;
33757
34392
  }
33758
34393
  await sleep2(RETRY_MS[0]);
@@ -33785,7 +34420,7 @@ function createLedgerWriteThrough(opts) {
33785
34420
  } catch (err2) {
33786
34421
  if (err2 instanceof RouteMissingError || err2 instanceof PermanentStoreError || attempt >= 2) {
33787
34422
  this.stateDirty = true;
33788
- warn(`[ledger] ${this.threadKey} state not written: ${describe6(err2)}`);
34423
+ warn(`[ledger] ${this.threadKey} state not written: ${describe8(err2)}`);
33789
34424
  return;
33790
34425
  }
33791
34426
  await sleep2(RETRY_MS[0]);
@@ -33803,7 +34438,7 @@ function createLedgerWriteThrough(opts) {
33803
34438
  this.detach(`finishing refused (fenced)`);
33804
34439
  return "fenced";
33805
34440
  } catch (err2) {
33806
- warn(`[ledger] ${this.threadKey} finishing failed: ${describe6(err2)}`);
34441
+ warn(`[ledger] ${this.threadKey} finishing failed: ${describe8(err2)}`);
33807
34442
  return "unavailable";
33808
34443
  }
33809
34444
  }
@@ -33818,7 +34453,7 @@ function createLedgerWriteThrough(opts) {
33818
34453
  warn(`[ledger] ${this.threadKey} abandon refused (fenced) \u2014 the row is another generation's`);
33819
34454
  } catch (err2) {
33820
34455
  warn(
33821
- `[ledger] ${this.threadKey} abandon failed: ${describe6(err2)} \u2014 the sweep will take the row once its lease lapses`
34456
+ `[ledger] ${this.threadKey} abandon failed: ${describe8(err2)} \u2014 the sweep will take the row once its lease lapses`
33822
34457
  );
33823
34458
  }
33824
34459
  }
@@ -33839,7 +34474,7 @@ function createLedgerWriteThrough(opts) {
33839
34474
  this.onStop(result.stop);
33840
34475
  }
33841
34476
  } catch (err2) {
33842
- warn(`[ledger] ${this.threadKey} heartbeat failed: ${describe6(err2)}`);
34477
+ warn(`[ledger] ${this.threadKey} heartbeat failed: ${describe8(err2)}`);
33843
34478
  }
33844
34479
  }
33845
34480
  stopHeartbeat() {
@@ -33906,7 +34541,7 @@ function createLedgerWriteThrough(opts) {
33906
34541
  warn(`[ledger] inbox push refused for run ${runId3}: no live row \u2014 the follow-up rides in memory only`);
33907
34542
  return void 0;
33908
34543
  } catch (err2) {
33909
- warn(`[ledger] inbox push failed for run ${runId3}: ${describe6(err2)} \u2014 the follow-up rides in memory only`);
34544
+ warn(`[ledger] inbox push failed for run ${runId3}: ${describe8(err2)} \u2014 the follow-up rides in memory only`);
33910
34545
  return void 0;
33911
34546
  }
33912
34547
  },
@@ -33915,7 +34550,7 @@ function createLedgerWriteThrough(opts) {
33915
34550
  return await ledger.readInbox(runId3, afterSeq);
33916
34551
  } catch (err2) {
33917
34552
  warn(
33918
- err2 instanceof RouteMissingError ? `[ledger] state Worker has no inbox read route \u2014 run ${runId3} resumes with the reclaim's inbox snapshot only` : `[ledger] inbox read failed for run ${runId3}: ${describe6(err2)} \u2014 resuming with the reclaim's snapshot only`
34553
+ err2 instanceof RouteMissingError ? `[ledger] state Worker has no inbox read route \u2014 run ${runId3} resumes with the reclaim's inbox snapshot only` : `[ledger] inbox read failed for run ${runId3}: ${describe8(err2)} \u2014 resuming with the reclaim's snapshot only`
33919
34554
  );
33920
34555
  return [];
33921
34556
  }
@@ -33932,12 +34567,12 @@ function createLedgerWriteThrough(opts) {
33932
34567
  for (const r of candidates) if (markedSet.has(r.runId)) r.markHandedOff();
33933
34568
  return { marked };
33934
34569
  } catch (err2) {
33935
- return { marked: [], failed: describe6(err2) };
34570
+ return { marked: [], failed: describe8(err2) };
33936
34571
  }
33937
34572
  }
33938
34573
  };
33939
34574
  }
33940
- var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS, describe6;
34575
+ var NullLedgerWriteThrough, NullLedgerRun, RETRY_MS, describe8;
33941
34576
  var init_writeThrough = __esm({
33942
34577
  "../../src/core/runLedger/writeThrough.ts"() {
33943
34578
  "use strict";
@@ -34000,7 +34635,7 @@ var init_writeThrough = __esm({
34000
34635
  }
34001
34636
  };
34002
34637
  RETRY_MS = [200, 800];
34003
- describe6 = (err2) => err2 instanceof Error ? err2.message : String(err2);
34638
+ describe8 = (err2) => err2 instanceof Error ? err2.message : String(err2);
34004
34639
  }
34005
34640
  });
34006
34641
 
@@ -34092,6 +34727,24 @@ var init_residentFleet = __esm({
34092
34727
  });
34093
34728
 
34094
34729
  // ../../src/providers/openaiCompat.ts
34730
+ function buildOpenAIBody(req) {
34731
+ const messages = [];
34732
+ if (req.system) messages.push({ role: "system", content: req.system });
34733
+ for (const m of req.messages) messages.push(...toOAIMessages(m));
34734
+ const body = {
34735
+ model: req.model,
34736
+ max_tokens: req.maxTokens,
34737
+ messages
34738
+ };
34739
+ if (req.tools && req.tools.length > 0) {
34740
+ body.tools = req.tools.map((t) => ({
34741
+ type: "function",
34742
+ function: { name: t.name, description: t.description, parameters: t.inputSchema }
34743
+ }));
34744
+ if (req.toolChoice) body.tool_choice = { type: "function", function: { name: req.toolChoice.name } };
34745
+ }
34746
+ return body;
34747
+ }
34095
34748
  function toOAIMessages(m) {
34096
34749
  if (m.role === "assistant") {
34097
34750
  const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
@@ -34178,20 +34831,7 @@ var init_openaiCompat = __esm({
34178
34831
  this.apiKey = cfg.apiKeyEnv ? processSecrets.named(cfg.apiKeyEnv) : void 0;
34179
34832
  }
34180
34833
  async complete(req) {
34181
- const messages = [];
34182
- if (req.system) messages.push({ role: "system", content: req.system });
34183
- for (const m of req.messages) messages.push(...toOAIMessages(m));
34184
- const body = {
34185
- model: req.model,
34186
- max_tokens: req.maxTokens,
34187
- messages
34188
- };
34189
- if (req.tools && req.tools.length > 0) {
34190
- body.tools = req.tools.map((t) => ({
34191
- type: "function",
34192
- function: { name: t.name, description: t.description, parameters: t.inputSchema }
34193
- }));
34194
- }
34834
+ const body = buildOpenAIBody(req);
34195
34835
  const res = await fetch(`${this.baseUrl}/chat/completions`, {
34196
34836
  method: "POST",
34197
34837
  headers: {
@@ -34683,6 +35323,7 @@ function classifyDocument(mimetype, name) {
34683
35323
  async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
34684
35324
  const images = [];
34685
35325
  const skipped = [];
35326
+ const skippedFiles = [];
34686
35327
  let bytes = 0;
34687
35328
  const candidates = [];
34688
35329
  for (const f of files ?? []) {
@@ -34690,6 +35331,7 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
34690
35331
  const url = f.url_private_download ?? f.url_private;
34691
35332
  if (!url || !f.mimetype || !IMAGE_TYPES2.has(f.mimetype) || (f.size ?? 0) > MAX_IMAGE_BYTES || candidates.length >= maxImages) {
34692
35333
  skipped.push(label);
35334
+ skippedFiles.push(f);
34693
35335
  continue;
34694
35336
  }
34695
35337
  candidates.push({ f, label, url, mediaType: f.mimetype });
@@ -34703,12 +35345,13 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
34703
35345
  const buf2 = downloads[i];
34704
35346
  if (!buf2 || buf2.byteLength > MAX_IMAGE_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
34705
35347
  skipped.push(label);
35348
+ skippedFiles.push(f);
34706
35349
  return;
34707
35350
  }
34708
35351
  bytes += buf2.byteLength;
34709
35352
  images.push({ mediaType, data: buf2.toString("base64"), name: f.name });
34710
35353
  });
34711
- return { images, skipped, bytes };
35354
+ return { images, skipped, skippedFiles, bytes };
34712
35355
  }
34713
35356
  async function downloadSlackFile(url, label, isLoginPage) {
34714
35357
  const token2 = processSecrets.get("SLACK_BOT_TOKEN");
@@ -34727,6 +35370,7 @@ async function downloadSlackFile(url, label, isLoginPage) {
34727
35370
  async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
34728
35371
  const documents = [];
34729
35372
  const skipped = [];
35373
+ const skippedFiles = [];
34730
35374
  let bytes = 0;
34731
35375
  const candidates = [];
34732
35376
  for (const f of files ?? []) {
@@ -34735,6 +35379,7 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
34735
35379
  const kind = classifyDocument(f.mimetype, f.name);
34736
35380
  if (!url || !kind || (f.size ?? 0) > MAX_DOCUMENT_BYTES || candidates.length >= maxDocs) {
34737
35381
  skipped.push(label);
35382
+ skippedFiles.push(f);
34738
35383
  continue;
34739
35384
  }
34740
35385
  candidates.push({ f, label, url, kind });
@@ -34748,6 +35393,7 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
34748
35393
  const buf2 = downloads[i];
34749
35394
  if (!buf2 || buf2.byteLength > MAX_DOCUMENT_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
34750
35395
  skipped.push(label);
35396
+ skippedFiles.push(f);
34751
35397
  return;
34752
35398
  }
34753
35399
  bytes += buf2.byteLength;
@@ -34757,9 +35403,43 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
34757
35403
  name: f.name
34758
35404
  });
34759
35405
  });
34760
- return { documents, skipped, bytes };
35406
+ return { documents, skipped, skippedFiles, bytes };
35407
+ }
35408
+ function stagedFiles(unsupported, opts) {
35409
+ if (!opts.staging) return { staged: [], skipped: unsupported.map(fileLabel) };
35410
+ const staged = [];
35411
+ const skipped = [];
35412
+ let total = 0;
35413
+ for (const f of unsupported) {
35414
+ const label = fileLabel(f);
35415
+ const url = f.url_private_download ?? f.url_private;
35416
+ const size = f.size ?? 0;
35417
+ if (isSecretFile(f.name)) {
35418
+ skipped.push(`${label} \u2014 looks like a credential or key file`);
35419
+ continue;
35420
+ }
35421
+ if (!url || !f.name || size <= 0) {
35422
+ skipped.push(`${label} \u2014 Slack gave no download URL, name or size`);
35423
+ continue;
35424
+ }
35425
+ if (size > MAX_STAGED_FILE_BYTES) {
35426
+ skipped.push(`${label} \u2014 ${size} bytes is over the ${MAX_STAGED_FILE_BYTES}-byte per-file ceiling`);
35427
+ continue;
35428
+ }
35429
+ if (staged.length >= MAX_STAGED_PER_MESSAGE) {
35430
+ skipped.push(`${label} \u2014 more than ${MAX_STAGED_PER_MESSAGE} files on one message`);
35431
+ continue;
35432
+ }
35433
+ if (total + size > opts.maxBytesPerMessage) {
35434
+ skipped.push(`${label} \u2014 the message's files exceed the ${opts.maxBytesPerMessage}-byte budget`);
35435
+ continue;
35436
+ }
35437
+ total += size;
35438
+ staged.push({ name: f.name, size, type: f.mimetype ?? "application/octet-stream", url, messageId: opts.messageId });
35439
+ }
35440
+ return { staged, skipped };
34761
35441
  }
34762
- var IMAGE_TYPES2, MAX_IMAGE_BYTES, MAX_IMAGES_PER_MESSAGE, MAX_HISTORY_IMAGES, MAX_HISTORY_IMAGE_BYTES, PDF_TYPE2, TEXT_MIME_TYPES, GENERIC_MIME_TYPES, TEXT_EXTENSIONS, SECRET_FILE_EXTENSIONS, SECRET_FILE_PREFIXES, MAX_DOCUMENT_BYTES, MAX_DOCS_PER_MESSAGE, MAX_HISTORY_DOCS, MAX_HISTORY_DOCUMENT_BYTES, fileLabel;
35442
+ var IMAGE_TYPES2, MAX_IMAGE_BYTES, MAX_IMAGES_PER_MESSAGE, MAX_HISTORY_IMAGES, MAX_HISTORY_IMAGE_BYTES, PDF_TYPE2, TEXT_MIME_TYPES, GENERIC_MIME_TYPES, TEXT_EXTENSIONS, SECRET_FILE_EXTENSIONS, SECRET_FILE_PREFIXES, MAX_DOCUMENT_BYTES, MAX_DOCS_PER_MESSAGE, MAX_HISTORY_DOCS, MAX_HISTORY_DOCUMENT_BYTES, fileLabel, MAX_STAGED_FILE_BYTES, MAX_STAGED_PER_MESSAGE;
34763
35443
  var init_attachments = __esm({
34764
35444
  "../../src/channels/slack/attachments.ts"() {
34765
35445
  "use strict";
@@ -34842,6 +35522,8 @@ var init_attachments = __esm({
34842
35522
  MAX_HISTORY_DOCS = 20;
34843
35523
  MAX_HISTORY_DOCUMENT_BYTES = 32 * 1024 * 1024;
34844
35524
  fileLabel = (f) => `${f.name ?? f.id ?? "file"} (${f.mimetype ?? "unknown type"})`;
35525
+ MAX_STAGED_FILE_BYTES = 1073741824;
35526
+ MAX_STAGED_PER_MESSAGE = 10;
34845
35527
  }
34846
35528
  });
34847
35529
 
@@ -35480,7 +36162,13 @@ async function handle(deps, { client, statusClient }, ev) {
35480
36162
  const receivedAt = systemClock();
35481
36163
  const trace = startRequestRoot(deps, { channel: "slack", receivedAt, originAt: tsMs(ev.ts) });
35482
36164
  try {
35483
- const received = await trace.root.span("slack.receive", (span) => receiveSlackMessage(client, ev, span));
36165
+ const received = await trace.root.span(
36166
+ "slack.receive",
36167
+ (span) => receiveSlackMessage(client, ev, span, {
36168
+ staging: deps.artifacts !== void 0,
36169
+ maxBytesPerMessage: deps.config.config.artifacts?.inbound?.maxBytesPerMessage ?? ARTIFACT_DEFAULTS.maxBytesPerMessage
36170
+ })
36171
+ );
35484
36172
  if (!received) {
35485
36173
  trace.root.end("ok", { status: "refused" });
35486
36174
  return;
@@ -35495,7 +36183,7 @@ async function handle(deps, { client, statusClient }, ev) {
35495
36183
  if (!trace.root.ended) trace.root.end("error", { status: "failed" });
35496
36184
  }
35497
36185
  }
35498
- async function receiveSlackMessage(client, ev, span) {
36186
+ async function receiveSlackMessage(client, ev, span, policy) {
35499
36187
  const drop = await dedupeDelivery(client, ev);
35500
36188
  if (drop) {
35501
36189
  console.log(`[redelivery] ${ev.channel}:${ev.ts} dropped: ${drop}`);
@@ -35509,12 +36197,13 @@ async function receiveSlackMessage(client, ev, span) {
35509
36197
  const delayNote = ev.caughtUp ? new SlackIO(client, ev).reply(catchUpDelayNote(ev.ts, systemClock())).catch((err2) => {
35510
36198
  console.error(`[catch-up] ${ev.channel}:${ev.ts} delay note failed: ${err2.message}`);
35511
36199
  }) : Promise.resolve();
35512
- const [{ images, skipped: skippedImages }, { documents, skipped: skippedDocs }] = await Promise.all([
36200
+ const [{ images, skippedFiles: imagePassSkipped }, { documents, skippedFiles: documentPassSkipped }] = await Promise.all([
35513
36201
  fetchImages(ev.files, MAX_IMAGES_PER_MESSAGE),
35514
36202
  fetchDocuments(ev.files, MAX_DOCS_PER_MESSAGE),
35515
36203
  delayNote
35516
36204
  ]);
35517
- const skipped = skippedImages.filter((s) => skippedDocs.includes(s));
36205
+ const unsupported = imagePassSkipped.filter((f) => documentPassSkipped.includes(f));
36206
+ const { staged, skipped } = stagedFiles(unsupported, { ...policy, messageId: ev.ts });
35518
36207
  const [channelName, userName, team] = await Promise.all([
35519
36208
  resolveChannelName(client, ev.channel),
35520
36209
  resolveUserName(client, ev.user),
@@ -35532,7 +36221,8 @@ async function receiveSlackMessage(client, ev, span) {
35532
36221
  userName,
35533
36222
  sourceUrl: team ? slackPermalink(team, ev.channel, ev.ts, ev.threadTs) : void 0,
35534
36223
  images: images.length > 0 ? images : void 0,
35535
- documents: documents.length > 0 ? documents : void 0
36224
+ documents: documents.length > 0 ? documents : void 0,
36225
+ ...staged.length > 0 ? { staged } : {}
35536
36226
  };
35537
36227
  }
35538
36228
  function resumeSlackIO(client, run2, opts = {}) {
@@ -35599,6 +36289,7 @@ var init_slack = __esm({
35599
36289
  init_statusCard();
35600
36290
  init_slackCatchUp();
35601
36291
  init_secrets2();
36292
+ init_config();
35602
36293
  init_slackCatchUpStatus();
35603
36294
  init_slackSocketStatus();
35604
36295
  init_slackTriggers();
@@ -35664,6 +36355,30 @@ ${file.text}`));
35664
36355
  initial_comment: mdToMrkdwn(file.lead)
35665
36356
  });
35666
36357
  }
36358
+ /** The external-upload flow (docs/reference/specs/slack-channel.md item 10,
36359
+ * record 0033): `files.getUploadURLExternal` mints a URL for exactly
36360
+ * `size` bytes under `name`; the run's container POSTs the file there; then
36361
+ * `complete` shares it into this thread with the lead as the comment
36362
+ * (`files.completeUploadExternal`). The bot never sees the bytes. A ticket
36363
+ * Slack answers without a URL or id is refused by name. */
36364
+ async uploadTicket(file) {
36365
+ const minted = await this.client.files.getUploadURLExternal({ filename: file.name, length: file.size });
36366
+ const url = minted.upload_url;
36367
+ const id = minted.file_id;
36368
+ if (!url || !id)
36369
+ throw new Error(`files.getUploadURLExternal answered without an upload_url and file_id for ${file.name}`);
36370
+ return {
36371
+ url,
36372
+ complete: async (lead) => {
36373
+ await this.client.files.completeUploadExternal({
36374
+ files: [{ id, title: file.name }],
36375
+ channel_id: this.ev.channel,
36376
+ thread_ts: this.ev.threadTs,
36377
+ initial_comment: mdToMrkdwn(lead)
36378
+ });
36379
+ }
36380
+ };
36381
+ }
35667
36382
  async post(mrkdwn) {
35668
36383
  for (const chunk of chunkText(mrkdwn, SLACK_MSG_LIMIT)) {
35669
36384
  await this.client.chat.postMessage({
@@ -36385,31 +37100,6 @@ var init_mcp3 = __esm({
36385
37100
  }
36386
37101
  });
36387
37102
 
36388
- // ../../src/artifacts/contentType.ts
36389
- var INLINE_IMAGE_TYPES;
36390
- var init_contentType = __esm({
36391
- "../../src/artifacts/contentType.ts"() {
36392
- "use strict";
36393
- INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
36394
- }
36395
- });
36396
-
36397
- // ../../src/artifacts/keys.ts
36398
- function safeBasename(name) {
36399
- const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
36400
- const last = segments2[segments2.length - 1] ?? "";
36401
- const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
36402
- if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
36403
- return reduced;
36404
- }
36405
- var SAFE;
36406
- var init_keys = __esm({
36407
- "../../src/artifacts/keys.ts"() {
36408
- "use strict";
36409
- SAFE = /[^A-Za-z0-9._-]+/g;
36410
- }
36411
- });
36412
-
36413
37103
  // ../../src/channels/scheduledPanel.ts
36414
37104
  function buildScheduledRows(schedules, firings, liveRuns, now) {
36415
37105
  const byName = /* @__PURE__ */ new Map();
@@ -37226,7 +37916,7 @@ var init_liveView = __esm({
37226
37916
  // ../../src/channels/webAssets.ts
37227
37917
  import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
37228
37918
  import { join as join13 } from "node:path";
37229
- function contentTypeFor(path) {
37919
+ function contentTypeFor2(path) {
37230
37920
  const dot = path.lastIndexOf(".");
37231
37921
  return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
37232
37922
  }
@@ -37271,7 +37961,7 @@ function loadWebAssets(distDir) {
37271
37961
  return true;
37272
37962
  }
37273
37963
  res.writeHead(200, {
37274
- "content-type": contentTypeFor(path),
37964
+ "content-type": contentTypeFor2(path),
37275
37965
  "content-length": String(body.byteLength),
37276
37966
  "cache-control": "public, max-age=31536000, immutable"
37277
37967
  });
@@ -38491,7 +39181,7 @@ async function reclaimRuns(opts) {
38491
39181
  reclaimed = await ledger.reclaim(gen, now(), LEASE_MS);
38492
39182
  } catch (err2) {
38493
39183
  warn(
38494
- err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${describe7(err2)} \u2014 runs the last generation left stay on the ledger until the next boot`
39184
+ err2 instanceof RouteMissingError ? "[reclaim] state Worker has no run-ledger routes \u2014 nothing to take over" : `[reclaim] reclaim failed: ${describe9(err2)} \u2014 runs the last generation left stay on the ledger until the next boot`
38495
39185
  );
38496
39186
  return outcome;
38497
39187
  }
@@ -38558,8 +39248,8 @@ async function reclaimRuns(opts) {
38558
39248
  `[reclaim] ${row.runId} ${row.threadKey} closed ${status3} (from ${run2.reclaimedFrom}; ${why}; ${events.length} event(s))`
38559
39249
  );
38560
39250
  } catch (err2) {
38561
- outcome.failed.push({ runId: row.runId, error: describe7(err2) });
38562
- warn(`[reclaim] ${row.runId} ${row.threadKey}: ${describe7(err2)} \u2014 left on the ledger for the next boot`);
39251
+ outcome.failed.push({ runId: row.runId, error: describe9(err2) });
39252
+ warn(`[reclaim] ${row.runId} ${row.threadKey}: ${describe9(err2)} \u2014 left on the ledger for the next boot`);
38563
39253
  }
38564
39254
  }
38565
39255
  try {
@@ -38575,7 +39265,7 @@ async function reclaimRuns(opts) {
38575
39265
  });
38576
39266
  }
38577
39267
  } catch (err2) {
38578
- warn(`[reclaim] listing live runs failed: ${describe7(err2)} \u2014 the card sweep runs without the ledger's guard`);
39268
+ warn(`[reclaim] listing live runs failed: ${describe9(err2)} \u2014 the card sweep runs without the ledger's guard`);
38579
39269
  }
38580
39270
  if (reclaimed.length > 0 || outcome.liveElsewhere.length > 0) {
38581
39271
  log(
@@ -38597,7 +39287,7 @@ function startReclaimSweep(opts) {
38597
39287
  await opts.onOutcome(outcome);
38598
39288
  }
38599
39289
  } catch (err2) {
38600
- warn(`[reclaim] sweep failed: ${describe7(err2)}`);
39290
+ warn(`[reclaim] sweep failed: ${describe9(err2)}`);
38601
39291
  } finally {
38602
39292
  running = false;
38603
39293
  }
@@ -38622,7 +39312,7 @@ async function completenessVerdict(ledger, runId3, lastStep) {
38622
39312
  try {
38623
39313
  transcript = await ledger.readTranscript(runId3);
38624
39314
  } catch (err2) {
38625
- return { resumable: false, why: `transcript unreadable (${describe7(err2)})` };
39315
+ return { resumable: false, why: `transcript unreadable (${describe9(err2)})` };
38626
39316
  }
38627
39317
  if (!transcript.complete) return { resumable: false, why: `transcript incomplete: ${transcript.gap}` };
38628
39318
  const verdict = transcriptCompleteness({
@@ -38643,7 +39333,7 @@ async function completenessVerdict(ledger, runId3, lastStep) {
38643
39333
  return { resumable: false, why: verdict.why };
38644
39334
  }
38645
39335
  }
38646
- var TERMINAL, describe7;
39336
+ var TERMINAL, describe9;
38647
39337
  var init_boot = __esm({
38648
39338
  "../../src/core/boot.ts"() {
38649
39339
  "use strict";
@@ -38659,7 +39349,7 @@ var init_boot = __esm({
38659
39349
  "failed",
38660
39350
  "interrupted"
38661
39351
  ]);
38662
- describe7 = (err2) => err2 instanceof Error ? err2.message : String(err2);
39352
+ describe9 = (err2) => err2 instanceof Error ? err2.message : String(err2);
38663
39353
  }
38664
39354
  });
38665
39355
 
@@ -39495,11 +40185,14 @@ var init_adminModelProxy = __esm({
39495
40185
 
39496
40186
  // ../../src/core/harness/pi/toolRules.ts
39497
40187
  import { isAbsolute as isAbsolute5, relative, resolve as resolve11 } from "node:path";
40188
+ function reachFor(identity2) {
40189
+ return identity2 === "write" ? CODING_REACH : READ_REACH;
40190
+ }
39498
40191
  function judgeToolCall(tool, input, ctx) {
39499
40192
  const bundle = PI_TOOL_BUNDLES[tool];
39500
- if (bundle === void 0) return outside(`${tool} is not in any bundle the coding preset reaches`);
39501
- if (!CODING_REACH.has(bundle)) {
39502
- return outside(`${tool} is the \`${bundle}\` bundle; the coding preset's reach does not include it`);
40193
+ if (bundle === void 0) return outside(`${tool} is not in any bundle the ${ctx.identity} identity reaches`);
40194
+ if (!reachFor(ctx.identity).has(bundle)) {
40195
+ return outside(`${tool} is the \`${bundle}\` bundle, outside the ${ctx.identity} identity's reach`);
39503
40196
  }
39504
40197
  const args = isRecord5(input) ? input : {};
39505
40198
  if (tool === "bash") return judgeBash(args.command, ctx);
@@ -39514,6 +40207,13 @@ function judgeBash(command2, ctx) {
39514
40207
  if (MERGE_OR_APPROVE.test(command2)) {
39515
40208
  return refused4("merge/approve \u2014 a coding run never merges or approves a pull request");
39516
40209
  }
40210
+ if (ctx.identity !== "write") {
40211
+ if (ANY_PUSH.test(command2)) return refused4("read-only \u2014 a read-identity run never pushes");
40212
+ if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
40213
+ return refused4("read-only \u2014 a read-identity run never writes to GitHub");
40214
+ }
40215
+ return allowed;
40216
+ }
39517
40217
  for (const match of command2.matchAll(GIT_PUSH)) {
39518
40218
  const verdict = judgePush(match[1], ctx);
39519
40219
  if (verdict.verdict !== "allowed") return verdict;
@@ -39549,7 +40249,7 @@ function judgePath(path, ctx) {
39549
40249
  if (rel.startsWith("..") || isAbsolute5(rel)) return refused4(`path \u2014 \`${path}\` resolves outside the checkout`);
39550
40250
  return allowed;
39551
40251
  }
39552
- var PI_TOOL_BUNDLES, CODING_REACH, allowed, refused4, outside, isRecord5, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_PUSH;
40252
+ var PI_TOOL_BUNDLES, CODING_REACH, READ_REACH, allowed, refused4, outside, isRecord5, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_THEN_PUSH, GIT_PUSH, ANY_PUSH, GH_WRITE_VERB, WRITE_METHOD, GH_API_WRITE, CURL_GITHUB_WRITE;
39553
40253
  var init_toolRules = __esm({
39554
40254
  "../../src/core/harness/pi/toolRules.ts"() {
39555
40255
  "use strict";
@@ -39573,6 +40273,7 @@ var init_toolRules = __esm({
39573
40273
  "github-write",
39574
40274
  "pr"
39575
40275
  ]);
40276
+ READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
39576
40277
  allowed = { verdict: "allowed" };
39577
40278
  refused4 = (reason) => ({ verdict: "refused", reason });
39578
40279
  outside = (reason) => ({ verdict: "outside-profile", reason });
@@ -39581,7 +40282,17 @@ var init_toolRules = __esm({
39581
40282
  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])/;
39582
40283
  CREDENTIAL_VAR = /\$\{?[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b|printenv\s+[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b/;
39583
40284
  MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
39584
- GIT_PUSH = /\bgit\s+push\b([^;&|]*)/g;
40285
+ GIT_THEN_PUSH = String.raw`\bgit(?:\s+(?:-C\s+\S+|--git-dir=\S+|--work-tree=\S+|-c\s+\S+|--no-pager))*\s+push\b`;
40286
+ GIT_PUSH = new RegExp(`${GIT_THEN_PUSH}([^;&|]*)`, "g");
40287
+ ANY_PUSH = new RegExp(GIT_THEN_PUSH);
40288
+ GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
40289
+ WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
40290
+ GH_API_WRITE = new RegExp(
40291
+ `\\bgh\\s+api\\b(?=[^;&|]*(?:\\s(?:-X|--method)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-f|-F|--field|--raw-field|--input)[\\s=]))`
40292
+ );
40293
+ CURL_GITHUB_WRITE = new RegExp(
40294
+ `\\bcurl\\b(?=[^;&|]*api\\.github\\.com)(?=[^;&|]*(?:\\s(?:-X|--request)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-d|--data(?:-\\w+)?|--json|-T|--upload-file)[\\s=]))`
40295
+ );
39585
40296
  }
39586
40297
  });
39587
40298
 
@@ -39590,6 +40301,7 @@ function relayedToolDefinitions(harness) {
39590
40301
  return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
39591
40302
  }
39592
40303
  function authorizeToolCall(harness, ask) {
40304
+ harness.gateSaw(ask.toolCallId);
39593
40305
  const blocked = harness.toolsBlocked();
39594
40306
  const refuse3 = (reason) => {
39595
40307
  harness.emit({
@@ -40109,6 +40821,7 @@ function watched(io, on) {
40109
40821
  };
40110
40822
  if (io.attach) out.attach = (file) => io.attach(file);
40111
40823
  if (io.attachFile) out.attachFile = (file) => io.attachFile(file);
40824
+ if (io.uploadTicket) out.uploadTicket = (file) => io.uploadTicket(file);
40112
40825
  if (io.runFinished) out.runFinished = (receipt) => io.runFinished(receipt);
40113
40826
  if (io.openThread) out.openThread = (lead) => io.openThread(lead);
40114
40827
  return out;
@@ -40140,8 +40853,8 @@ async function spawn6(body, deps) {
40140
40853
  const composed = await composeChild(req.brief, instance, unit.row, briefReaders(deps, instance, io));
40141
40854
  turn = composed;
40142
40855
  } catch (err2) {
40143
- log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe8(err2)}`);
40144
- return json(502, { ok: false, error: "brief_failed", message: describe8(err2), at });
40856
+ log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe10(err2)}`);
40857
+ return json(502, { ok: false, error: "brief_failed", message: describe10(err2), at });
40145
40858
  }
40146
40859
  } else {
40147
40860
  turn = { prompt: req.prompt };
@@ -40192,7 +40905,7 @@ async function spawn6(body, deps) {
40192
40905
  );
40193
40906
  void settled.then((end) => {
40194
40907
  if (end.kind === "threw")
40195
- log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${describe8(end.err)}`);
40908
+ log(`[coordinator] ${instance.id} ${req.step}: the child's dispatch threw: ${describe10(end.err)}`);
40196
40909
  });
40197
40910
  const first = await Promise.race([started.then((id) => ({ kind: "started", id })), settled]);
40198
40911
  if (first.kind === "started" || startedId !== void 0) {
@@ -40200,7 +40913,7 @@ async function spawn6(body, deps) {
40200
40913
  log(`[coordinator] ${instance.id} ${req.step}: spawned ${req.preset} run ${runId3} in ${threadKey}`);
40201
40914
  return json(200, { ok: true, runId: runId3, threadKey, at });
40202
40915
  }
40203
- if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message: describe8(first.err), at });
40916
+ if (first.kind === "threw") return json(502, { ok: false, error: "spawn_failed", message: describe10(first.err), at });
40204
40917
  const refusal = first.outcome.refusal;
40205
40918
  if (refusal === "coordinator_thread_live") {
40206
40919
  const now = await liveOnThread(deps.runs, instance, threadKey);
@@ -40353,7 +41066,7 @@ async function prCheck(body, deps) {
40353
41066
  at
40354
41067
  });
40355
41068
  } catch (err2) {
40356
- return json(502, { ok: false, error: "github_unavailable", message: describe8(err2), at });
41069
+ return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
40357
41070
  }
40358
41071
  }
40359
41072
  async function plan2(body, deps) {
@@ -40412,7 +41125,7 @@ async function unitStart(body, deps) {
40412
41125
  ...opened.thread.sourceUrl !== void 0 ? { sourceUrl: opened.thread.sourceUrl } : {}
40413
41126
  };
40414
41127
  } catch (err2) {
40415
- return json(502, { ok: false, error: "thread_failed", message: describe8(err2), at });
41128
+ return json(502, { ok: false, error: "thread_failed", message: describe10(err2), at });
40416
41129
  }
40417
41130
  }
40418
41131
  }
@@ -40454,7 +41167,7 @@ async function branch(body, deps) {
40454
41167
  await deps.createBranchRef(instance.repo, name, base);
40455
41168
  return json(200, { ok: true, branch: name, base, at });
40456
41169
  } catch (err2) {
40457
- return json(200, { ok: false, reason: describe8(err2), at });
41170
+ return json(200, { ok: false, reason: describe10(err2), at });
40458
41171
  }
40459
41172
  }
40460
41173
  function unitLines(units) {
@@ -40506,7 +41219,7 @@ async function round(body, deps) {
40506
41219
  instance,
40507
41220
  units.map((u) => u.unit === updated.unit ? updated : u)
40508
41221
  ).catch(
40509
- (err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${describe8(err2)}`)
41222
+ (err2) => (deps.log ?? console.warn)(`[coordinator] ${instance.id}: the card could not be redrawn: ${describe10(err2)}`)
40510
41223
  );
40511
41224
  return json(200, { ok: true, at });
40512
41225
  }
@@ -40540,7 +41253,7 @@ async function unitEnd(body, deps) {
40540
41253
  told = true;
40541
41254
  } catch (err2) {
40542
41255
  (deps.log ?? console.warn)(
40543
- `[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${describe8(err2)}`
41256
+ `[coordinator] ${instance.id} ${row.unit}: the report could not be posted: ${describe10(err2)}`
40544
41257
  );
40545
41258
  }
40546
41259
  }
@@ -40554,7 +41267,7 @@ ${ending.report}` + (rendered !== void 0 ? `
40554
41267
  ${rendered}` : "");
40555
41268
  await deps.github.commentIssue(instance.repo, row.issue, comment).catch(
40556
41269
  (err2) => (deps.log ?? console.warn)(
40557
- `[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${describe8(err2)}`
41270
+ `[coordinator] ${instance.id} ${row.unit}: the board comment could not be posted: ${describe10(err2)}`
40558
41271
  )
40559
41272
  );
40560
41273
  }
@@ -40609,7 +41322,7 @@ async function merge(body, deps, subject2) {
40609
41322
  try {
40610
41323
  facts2 = await deps.fetchPrFacts(pr);
40611
41324
  } catch (err2) {
40612
- return json(502, { ok: false, error: "github_unavailable", message: describe8(err2), at });
41325
+ return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
40613
41326
  }
40614
41327
  if (facts2 === void 0)
40615
41328
  return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
@@ -40661,7 +41374,7 @@ async function merge(body, deps, subject2) {
40661
41374
  title: facts2.title ?? `Merge pull request #${pr.number}`
40662
41375
  });
40663
41376
  } catch (err2) {
40664
- return json(502, { ok: false, error: "github_unavailable", message: describe8(err2), at });
41377
+ return json(502, { ok: false, error: "github_unavailable", message: describe10(err2), at });
40665
41378
  }
40666
41379
  if (!merged.ok) {
40667
41380
  log(
@@ -40836,13 +41549,13 @@ function createAdminCoordinatorHandler(deps) {
40836
41549
  }
40837
41550
  write(res, await answerCoordinatorStep(door, read.body, deps));
40838
41551
  } catch (err2) {
40839
- (deps.log ?? console.error)(`[coordinator] ${describe8(err2)}`);
41552
+ (deps.log ?? console.error)(`[coordinator] ${describe10(err2)}`);
40840
41553
  write(res, json(500, { ok: false, error: "internal error" }));
40841
41554
  }
40842
41555
  })();
40843
41556
  };
40844
41557
  }
40845
- var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json, describe8, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
41558
+ var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json, describe10, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
40846
41559
  var init_adminCoordinator = __esm({
40847
41560
  "../../src/channels/adminCoordinator.ts"() {
40848
41561
  "use strict";
@@ -40875,7 +41588,7 @@ var init_adminCoordinator = __esm({
40875
41588
  };
40876
41589
  invalid = (error) => ({ ok: false, error });
40877
41590
  json = (status3, body) => ({ status: status3, body });
40878
- describe8 = (err2) => err2 instanceof Error ? err2.message : String(err2);
41591
+ describe10 = (err2) => err2 instanceof Error ? err2.message : String(err2);
40879
41592
  EVERY_RUN = { kind: "all" };
40880
41593
  REVIEW_POSTED_CHECKS = 3;
40881
41594
  REVIEW_POSTED_RECHECK_MS = 2e3;