@coreplane/switchboard 1.216.2 → 1.218.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/assets/config/config.example.yaml +6 -0
  2. package/dist/assets/deploy/cloudflare/package.json +1 -1
  3. package/dist/assets/deploy/cloudflare/worker.ts +4 -0
  4. package/dist/assets/deploy/cloudflare-docs/package.json +1 -1
  5. package/dist/assets/deploy/cloudflare-memory/package.json +1 -1
  6. package/dist/assets/deploy/cloudflare-memory/worker.ts +96 -6
  7. package/dist/assets/deploy/cloudflare-resident/package.json +1 -1
  8. package/dist/assets/deploy/cloudflare-sandbox/package.json +1 -1
  9. package/dist/assets/deploy/secrets.manifest.json +12 -0
  10. package/dist/assets/package-lock.json +5676 -7966
  11. package/dist/assets/package.json +2 -2
  12. package/dist/assets/source.json +3 -3
  13. package/dist/assets/src/agents/registry.ts +18 -0
  14. package/dist/assets/src/core/runEvents.ts +13 -0
  15. package/dist/assets/src/core/runFriction.ts +4 -2
  16. package/dist/assets/src/core/runLedger/sessionLog.ts +22 -0
  17. package/dist/assets/src/core/runLedger/types.ts +17 -0
  18. package/dist/assets/src/core/runRecord.ts +4 -2
  19. package/dist/assets/src/mcp/registry.ts +33 -5
  20. package/dist/assets/web/dist/.vite/manifest.json +18 -18
  21. package/dist/assets/web/dist/assets/{ResidentDetailPage-CvwjhjlG.js → ResidentDetailPage-DvCt96Dk.js} +1 -1
  22. package/dist/assets/web/dist/assets/{ResidentsIndexPage-DoN6XuNx.js → ResidentsIndexPage-BZaYja1-.js} +1 -1
  23. package/dist/assets/web/dist/assets/{RunRoutePage-FzLNexjF.js → RunRoutePage-DI4vzCUu.js} +4 -4
  24. package/dist/assets/web/dist/assets/{RunsIndexPage-DhSUbF9k.js → RunsIndexPage-Ch9uP4p4.js} +1 -1
  25. package/dist/assets/web/dist/assets/{ScheduledPage-Bq0Yg4nT.js → ScheduledPage-DAkAHMCH.js} +1 -1
  26. package/dist/assets/web/dist/assets/{StatusDot-D8Wwt1KC.js → StatusDot-CfBonGcV.js} +1 -1
  27. package/dist/assets/web/dist/assets/{Tooltip-DpDK7jWZ.js → Tooltip-CBD36E3W.js} +1 -1
  28. package/dist/assets/web/dist/assets/dist-DQ1U73LF.js +1542 -0
  29. package/dist/assets/web/dist/assets/{main-BFkEOy3K.js → main-CdLnG-yl.js} +2 -2
  30. package/dist/cli.js +1066 -343
  31. package/package.json +1 -1
  32. package/dist/assets/web/dist/assets/dist-B7BVkB7x.js +0 -1547
package/dist/cli.js CHANGED
@@ -206,6 +206,9 @@ async function ensureWorkArea(at, version2, workers, deps) {
206
206
  }
207
207
  return { ok: true, copied: plan3.copy, installed: plan3.install };
208
208
  }
209
+ function checkoutInstallHolds(root, dir, exists) {
210
+ return exists(join3(root, dir, "node_modules", "wrangler")) || exists(join3(root, "node_modules", "wrangler"));
211
+ }
209
212
  function imageBuiltOutsideDir(renderedConfig) {
210
213
  const code = renderedConfig.split("\n").filter((l) => !l.trimStart().startsWith("//")).join("\n");
211
214
  for (const m of code.matchAll(/"image"\s*:\s*"([^"]+)"/g)) {
@@ -1796,7 +1799,7 @@ function getAgent(name) {
1796
1799
  }
1797
1800
  return a;
1798
1801
  }
1799
- 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;
1802
+ 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, NOTEPAD, 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;
1800
1803
  var init_registry = __esm({
1801
1804
  "../../src/agents/registry.ts"() {
1802
1805
  "use strict";
@@ -1827,6 +1830,8 @@ SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshot
1827
1830
  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.
1828
1831
  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.
1829
1832
  Text stays in your message; do not attach what you can say.`;
1833
+ NOTEPAD = `YOUR NOTES AND YOUR REACH BACK. This thread's conversation outlives your context window and this run: every turn \u2014 yours, the person's, every tool call and its output, from this run and the runs before it in this thread \u2014 is kept in a log you can search with the \`recall\` tool (words \u2192 the matching turns with their numbers; a turn number \u2192 that turn whole). When something you need is no longer in front of you, recall it instead of redoing the work or guessing.
1834
+ Keep notes with the \`notes\` tool: one short document, replaced whole each time, at most 8 KiB \u2014 decisions and their reasons, the names of things you found (files, tests, commits, the head your tests were green at), what is not yet proven. They are the one thing sure to survive a compaction and to reach the next run in this thread: they ride your system prompt at its start and come back to you right after a compaction. Write them when you decide something worth keeping, not only at the end.`;
1830
1835
  CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
1831
1836
 
1832
1837
  You work inside a dedicated workspace directory with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
@@ -1856,6 +1861,8 @@ ${PR_DESCRIPTION_TEMPLATE}
1856
1861
 
1857
1862
  ${SHOW_FILES}
1858
1863
 
1864
+ ${NOTEPAD}
1865
+
1859
1866
  Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (\u25CB pending items), then update it whenever an item starts (\u2731) or finishes (\u2713). Items are short outcomes ("Clone repo and read the diff", "Run the test suite"), never commands. Mark an item \u2713 only after it has actually happened \u2014 never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
1860
1867
 
1861
1868
  If the request doesn't name a repository and you can't infer it, ask for it instead of guessing.
@@ -1890,6 +1897,8 @@ ${PR_DESCRIPTION_TEMPLATE}
1890
1897
 
1891
1898
  ${SHOW_FILES}
1892
1899
 
1900
+ ${NOTEPAD}
1901
+
1893
1902
  Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (\u25CB pending items), then update it whenever an item starts (\u2731) or finishes (\u2713). Items are short outcomes ("Implement the fix", "Run the test suite"), never commands. Mark an item \u2713 only after it has actually happened \u2014 never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
1894
1903
 
1895
1904
  Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
@@ -1922,6 +1931,8 @@ REVIEW THE PR'S OWN HEAD, NOTHING ELSE: the commit you read must be the PR's hea
1922
1931
 
1923
1932
  ${REVIEW_VERDICT_INSTRUCTION}
1924
1933
 
1934
+ ${NOTEPAD}
1935
+
1925
1936
  Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending), update as items start (\u2731) and finish (\u2713 \u2014 only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
1926
1937
 
1927
1938
  Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
@@ -1948,6 +1959,8 @@ REVIEW THE PR'S OWN HEAD, NOTHING ELSE: the commit you read must be the PR's hea
1948
1959
 
1949
1960
  ${REVIEW_VERDICT_INSTRUCTION}
1950
1961
 
1962
+ ${NOTEPAD}
1963
+
1951
1964
  Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending), update as items start (\u2731) and finish (\u2713 \u2014 only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
1952
1965
 
1953
1966
  Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
@@ -1981,6 +1994,8 @@ READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch,
1981
1994
 
1982
1995
  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.
1983
1996
 
1997
+ ${NOTEPAD}
1998
+
1984
1999
  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.`;
1985
2000
  COMPOUND_PRESET = "conductor";
1986
2001
  WORK_PRESETS = {
@@ -2167,8 +2182,8 @@ var init_effort = __esm({
2167
2182
  });
2168
2183
 
2169
2184
  // ../../src/core/dashboardAuthConfig.ts
2170
- function resolveDashboardAuthMode(configured2, accessConfigured) {
2171
- if (configured2 !== void 0) return configured2;
2185
+ function resolveDashboardAuthMode(configured3, accessConfigured) {
2186
+ if (configured3 !== void 0) return configured3;
2172
2187
  return accessConfigured ? "access" : "none";
2173
2188
  }
2174
2189
  function tokenSubjectOf(actor) {
@@ -2603,6 +2618,11 @@ function splitCredentialKey(key) {
2603
2618
  const name = key.slice(slash + 1);
2604
2619
  return parseMcpScopeKey(scopeKey) && MCP_SERVER_NAME_RE.test(name) ? { scopeKey, name } : void 0;
2605
2620
  }
2621
+ function isHeadersEnv(v) {
2622
+ if (!v || typeof v !== "object" || Array.isArray(v)) return false;
2623
+ const entries = Object.entries(v);
2624
+ return entries.length > 0 && entries.length <= MCP_HEADERS_MAX && entries.every(([name, envVar]) => isStr(name, 64) && isStr(envVar, 128));
2625
+ }
2606
2626
  function isOutcome(v) {
2607
2627
  if (!v || typeof v !== "object" || Array.isArray(v)) return false;
2608
2628
  const o = v;
@@ -2611,7 +2631,7 @@ function isOutcome(v) {
2611
2631
  function isMcpServerEntry(v) {
2612
2632
  if (!v || typeof v !== "object" || Array.isArray(v)) return false;
2613
2633
  const e = v;
2614
- return isStr(e.url, MCP_URL_MAX) && (e.agents === void 0 || Array.isArray(e.agents) && e.agents.length > 0 && e.agents.length <= MCP_AGENTS_MAX && e.agents.every((a) => isStr(a, 32))) && (e.auth === "none" || e.auth === "bearer" || e.auth === "oauth") && (e.tokenEnv === void 0 || isStr(e.tokenEnv, 128)) && (e.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt));
2634
+ return isStr(e.url, MCP_URL_MAX) && (e.agents === void 0 || Array.isArray(e.agents) && e.agents.length > 0 && e.agents.length <= MCP_AGENTS_MAX && e.agents.every((a) => isStr(a, 32))) && (e.auth === "none" || e.auth === "bearer" || e.auth === "oauth") && (e.tokenEnv === void 0 || isStr(e.tokenEnv, 128)) && (e.headersEnv === void 0 || isHeadersEnv(e.headersEnv)) && (e.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt));
2615
2635
  }
2616
2636
  function isSealedCredential(v) {
2617
2637
  if (!v || typeof v !== "object") return false;
@@ -2626,7 +2646,7 @@ function isMcpTicket(v) {
2626
2646
  }
2627
2647
  function serverView(scopeKey, name, entry, opts) {
2628
2648
  const parsed = parseMcpScopeKey(scopeKey);
2629
- const state = entry.auth === "none" ? "connected" : entry.tokenEnv ? "static" : opts.hasCredential ? "connected" : "awaiting_credential";
2649
+ const state = entry.auth === "none" ? entry.headersEnv ? "static" : "connected" : entry.tokenEnv ? "static" : opts.hasCredential ? "connected" : "awaiting_credential";
2630
2650
  return {
2631
2651
  name,
2632
2652
  scope: parsed?.kind ?? "org",
@@ -2648,7 +2668,7 @@ function safeUrl(raw) {
2648
2668
  return "(invalid url)";
2649
2669
  }
2650
2670
  }
2651
- var MCP_TICKET_STATES, MCP_TICKET_TTL_MS, MCP_SERVER_NAME_MAX, MCP_SERVER_NAME_RE, MCP_URL_MAX, MCP_AGENTS_MAX, MCP_SELF_SERVE_AGENTS, MCP_TOKEN_MAX_CHARS, MCP_SERVERS_PER_SCOPE_MAX, isStr, isNum;
2671
+ var MCP_TICKET_STATES, MCP_TICKET_TTL_MS, MCP_SERVER_NAME_MAX, MCP_SERVER_NAME_RE, MCP_URL_MAX, MCP_AGENTS_MAX, MCP_SELF_SERVE_AGENTS, MCP_TOKEN_MAX_CHARS, MCP_SERVERS_PER_SCOPE_MAX, isStr, isNum, MCP_HEADERS_MAX, MCP_HEADER_NAME_RE;
2652
2672
  var init_registry2 = __esm({
2653
2673
  "../../src/mcp/registry.ts"() {
2654
2674
  "use strict";
@@ -2669,6 +2689,8 @@ var init_registry2 = __esm({
2669
2689
  MCP_SERVERS_PER_SCOPE_MAX = 32;
2670
2690
  isStr = (v, max = 4096) => typeof v === "string" && v.length > 0 && v.length <= max;
2671
2691
  isNum = (v) => typeof v === "number" && Number.isFinite(v);
2692
+ MCP_HEADERS_MAX = 8;
2693
+ MCP_HEADER_NAME_RE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$/;
2672
2694
  }
2673
2695
  });
2674
2696
 
@@ -2753,7 +2775,9 @@ function validateMcpServers(layer, source2) {
2753
2775
  `${source2}: ${path}.${name}: server names are slugs (lowercase letters, digits, dashes; \u2264 ${MCP_SERVER_NAME_MAX} chars)`
2754
2776
  );
2755
2777
  if (!isMcpServerEntry(raw))
2756
- throw new Error(`${source2}: ${path}.${name} must be { url, auth: none|bearer|oauth, agents?, tokenEnv? }`);
2778
+ throw new Error(
2779
+ `${source2}: ${path}.${name} must be { url, auth: none|bearer|oauth, agents?, tokenEnv?, headersEnv? }`
2780
+ );
2757
2781
  try {
2758
2782
  assertUrlAllowed(raw.url);
2759
2783
  } catch (err2) {
@@ -2774,6 +2798,14 @@ function validateMcpServers(layer, source2) {
2774
2798
  }
2775
2799
  if (raw.tokenEnv !== void 0 && raw.auth !== "bearer")
2776
2800
  throw new Error(`${source2}: ${path}.${name}.tokenEnv only applies to auth: bearer`);
2801
+ for (const header4 of Object.keys(raw.headersEnv ?? {})) {
2802
+ if (!MCP_HEADER_NAME_RE.test(header4))
2803
+ throw new Error(`${source2}: ${path}.${name}.headersEnv: "${header4}" is not an HTTP header name`);
2804
+ if (header4.toLowerCase() === "authorization")
2805
+ throw new Error(
2806
+ `${source2}: ${path}.${name}.headersEnv: the Authorization header is \`auth\`'s \u2014 use auth: bearer with tokenEnv`
2807
+ );
2808
+ }
2777
2809
  }
2778
2810
  };
2779
2811
  check("defaults.mcpServers", "org", layer.defaults?.mcpServers);
@@ -4989,7 +5021,7 @@ function planDeploy(opts, host, loaded, images) {
4989
5021
  function formatPlan(plan3) {
4990
5022
  const missing = plan3.checks.nodeModulesMissing;
4991
5023
  const checkout = plan3.root.mode === "checkout";
4992
- const nodeModules = missing.length === 0 ? "node_modules present in every dir" : checkout ? `node_modules missing in ${missing.join(", ")} \u2014 the runner will \`npm ci\` there first` : `${missing.join(", ")} not installed under ${WORK_AREA_DIR}/ \u2014 the runner will \`npm ci\` them there first`;
5024
+ const nodeModules = missing.length === 0 ? "node_modules present in every dir" : checkout ? `install missing for ${missing.join(", ")} \u2014 the runner will \`npm ci\` at the checkout's root first` : `${missing.join(", ")} not installed under ${WORK_AREA_DIR}/ \u2014 the runner will \`npm ci\` them there first`;
4993
5025
  const tree = checkout ? `${plan3.checks.cleanTree ? "clean tree" : "tree unchecked"}; ${plan3.checks.atOriginMain ? "HEAD == origin/main" : "any branch (--allow-branch)"}` : `Worker sources are the package's (version ${plan3.root.version ?? "?"}), materialised under ${WORK_AREA_DIR}/ \u2014 no git`;
4994
5026
  const lines = [
4995
5027
  ...plan3.affected ? [formatAffectedText(plan3.affected)] : [],
@@ -6241,7 +6273,7 @@ var init_sandboxLiveGate = __esm({
6241
6273
  // ../../src/deploy/run.ts
6242
6274
  import { spawn as spawn3 } from "node:child_process";
6243
6275
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
6244
- import { dirname as dirname4, isAbsolute as isAbsolute3, join as join5 } from "node:path";
6276
+ import { dirname as dirname4, isAbsolute as isAbsolute3 } from "node:path";
6245
6277
  function run(cmd, args, opts) {
6246
6278
  const env = { ...process.env, ...opts.set ?? {} };
6247
6279
  for (const k of opts.unset ?? []) delete env[k];
@@ -6348,7 +6380,7 @@ function packageModeProblems(plan3) {
6348
6380
  return problems;
6349
6381
  }
6350
6382
  function hasNodeModules(dir) {
6351
- if (OPERATOR_ROOT.mode === "checkout") return existsSync5(join5(OPERATOR_ROOT.root, dir, "node_modules"));
6383
+ if (OPERATOR_ROOT.mode === "checkout") return checkoutInstallHolds(OPERATOR_ROOT.root, dir, existsSync5);
6352
6384
  const state = readWorkAreaState(OPERATOR_ROOT.workArea);
6353
6385
  return state.kind === "stamped" && state.stamp.installed.includes(dir);
6354
6386
  }
@@ -6514,10 +6546,10 @@ async function pushConfigOnHost(opts) {
6514
6546
  return pushConfigDocument(read, { stateWorkerUrl: opts.stateWorkerUrl, key: opts.key, env: process.env });
6515
6547
  }
6516
6548
  async function ensureNodeModules(step, io) {
6517
- const dir = workerDir(step.dir);
6518
6549
  if (hasNodeModules(step.dir)) return true;
6519
- io.log(`[deploy:all] ${step.name}: node_modules missing \u2014 npm ci`);
6520
- const r = await run("npm", ["ci", "--silent"], { cwd: dir });
6550
+ const cwd = OPERATOR_ROOT.mode === "checkout" ? OPERATOR_ROOT.root : workerDir(step.dir);
6551
+ io.log(`[deploy:all] ${step.name}: install missing \u2014 npm ci in ${cwd}`);
6552
+ const r = await run("npm", ["ci", "--silent"], { cwd });
6521
6553
  if (r.code !== 0) io.warn(r.output);
6522
6554
  return r.code === 0;
6523
6555
  }
@@ -7582,6 +7614,159 @@ var init_imagesHost = __esm({
7582
7614
  }
7583
7615
  });
7584
7616
 
7617
+ // ../../src/deploy/artifactsBucket.ts
7618
+ function lifecycleRulesFor(retentionDays) {
7619
+ if (!Number.isInteger(retentionDays) || retentionDays < 1) {
7620
+ throw new Error(`retentionDays must be an integer >= 1, got ${String(retentionDays)}`);
7621
+ }
7622
+ return [
7623
+ {
7624
+ id: EXPIRE_RULE_ID,
7625
+ enabled: true,
7626
+ conditions: { prefix: "" },
7627
+ deleteObjectsTransition: { condition: { type: "Age", maxAge: retentionDays * DAY_SECONDS } }
7628
+ },
7629
+ {
7630
+ id: ABORT_RULE_ID,
7631
+ enabled: true,
7632
+ conditions: { prefix: "" },
7633
+ abortMultipartUploadsTransition: { condition: { type: "Age", maxAge: ABORT_MULTIPART_AFTER_DAYS * DAY_SECONDS } }
7634
+ }
7635
+ ];
7636
+ }
7637
+ function describeRules(rules) {
7638
+ return rules.map((r) => {
7639
+ const days = (s) => `${s / DAY_SECONDS} day${s === DAY_SECONDS ? "" : "s"}`;
7640
+ if (r.deleteObjectsTransition)
7641
+ return `${r.id}: delete every object ${days(r.deleteObjectsTransition.condition.maxAge)} after it was written`;
7642
+ if (r.abortMultipartUploadsTransition)
7643
+ return `${r.id}: abort an incomplete multipart upload after ${days(r.abortMultipartUploadsTransition.condition.maxAge)}`;
7644
+ return `${r.id}: (no transition)`;
7645
+ });
7646
+ }
7647
+ function rulesMatch(readBack, wanted) {
7648
+ const rules = Array.isArray(readBack?.rules) ? readBack.rules : null;
7649
+ if (!rules) return { ok: false, problem: "the read-back carries no `rules` array" };
7650
+ const problems = [];
7651
+ for (const want of wanted) {
7652
+ const got = rules.find((r) => r?.id === want.id);
7653
+ if (!got) {
7654
+ problems.push(`rule ${want.id} is missing`);
7655
+ continue;
7656
+ }
7657
+ if (got.enabled !== true) problems.push(`rule ${want.id} is not enabled`);
7658
+ const wantAge = want.deleteObjectsTransition?.condition.maxAge ?? want.abortMultipartUploadsTransition?.condition.maxAge;
7659
+ const gotAge = got.deleteObjectsTransition?.condition?.maxAge ?? got.abortMultipartUploadsTransition?.condition?.maxAge;
7660
+ if (gotAge !== wantAge) problems.push(`rule ${want.id} has maxAge ${String(gotAge)}, not ${String(wantAge)}`);
7661
+ }
7662
+ return problems.length === 0 ? { ok: true } : { ok: false, problem: problems.join("; ") };
7663
+ }
7664
+ function privacyOf(managed, custom) {
7665
+ const open = [];
7666
+ if (managed.enabled) open.push(`the managed r2.dev domain ${managed.domain} is enabled`);
7667
+ for (const d of custom) if (d.enabled) open.push(`the custom domain ${d.domain} is enabled`);
7668
+ return open.length === 0 ? { private: true, open: [] } : { private: false, open };
7669
+ }
7670
+ function bucketTokenMissingProblem() {
7671
+ return `${API_TOKEN_ENV} is not set \u2014 the bucket's lifecycle rules and domain settings are read and written with a Cloudflare API token holding Workers R2 Storage: Edit (Read suffices for \`artifacts check\`); the bot's ARTIFACTS_R2_* token cannot do this and is never used here`;
7672
+ }
7673
+ function bucketCallProblem(method, url, status3, text) {
7674
+ const words = text.trim().slice(0, 300);
7675
+ const permission = status3 === 401 || status3 === 403 ? ` \u2014 the token lacks the permission (Workers R2 Storage: ${method === "GET" ? "Read" : "Edit"} on this account)` : "";
7676
+ return `${method} ${url} answered HTTP ${status3}${permission}${words ? `: ${words}` : ""}`;
7677
+ }
7678
+ function artifactsBucketHost(options = {}) {
7679
+ const env = options.env ?? process.env;
7680
+ const fetchImpl = options.fetch ?? fetch;
7681
+ const call2 = async (method, path, body) => {
7682
+ const token2 = env[API_TOKEN_ENV]?.trim();
7683
+ if (!token2) return { ok: false, problem: bucketTokenMissingProblem() };
7684
+ const url = `${API}${path}`;
7685
+ let res;
7686
+ try {
7687
+ res = await fetchImpl(url, {
7688
+ method,
7689
+ headers: {
7690
+ authorization: `Bearer ${token2}`,
7691
+ ...body !== void 0 ? { "content-type": "application/json" } : {}
7692
+ },
7693
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
7694
+ });
7695
+ } catch (err2) {
7696
+ return { ok: false, problem: `${method} ${url} failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}` };
7697
+ }
7698
+ const text = await res.text().catch(() => "");
7699
+ if (!res.ok) return { ok: false, problem: bucketCallProblem(method, url, res.status, text) };
7700
+ let envelope;
7701
+ try {
7702
+ envelope = JSON.parse(text);
7703
+ } catch {
7704
+ return { ok: false, problem: `${method} ${url} answered HTTP ${res.status} with a body that is not JSON` };
7705
+ }
7706
+ if (envelope.success !== true) {
7707
+ const errors = Array.isArray(envelope.errors) ? envelope.errors.map((e) => e?.message ?? JSON.stringify(e)).join("; ") : "no error given";
7708
+ return { ok: false, problem: `${method} ${url} answered success: false \u2014 ${errors}` };
7709
+ }
7710
+ return { ok: true, value: envelope.result };
7711
+ };
7712
+ const bucketPath = (account2, bucket, tail) => `/accounts/${encodeURIComponent(account2)}/r2/buckets/${encodeURIComponent(bucket)}/${tail}`;
7713
+ return {
7714
+ async putLifecycle(account2, bucket, rules) {
7715
+ const r = await call2("PUT", bucketPath(account2, bucket, "lifecycle"), { rules });
7716
+ return r.ok ? { ok: true, value: void 0 } : r;
7717
+ },
7718
+ getLifecycle(account2, bucket) {
7719
+ return call2("GET", bucketPath(account2, bucket, "lifecycle"));
7720
+ },
7721
+ async managedDomain(account2, bucket) {
7722
+ const r = await call2("GET", bucketPath(account2, bucket, "domains/managed"));
7723
+ if (!r.ok) return r;
7724
+ const v = r.value;
7725
+ if (!v || typeof v.domain !== "string" || typeof v.enabled !== "boolean") {
7726
+ return {
7727
+ ok: false,
7728
+ problem: `the managed-domain answer has no domain/enabled: ${JSON.stringify(r.value).slice(0, 200)}`
7729
+ };
7730
+ }
7731
+ return { ok: true, value: { domain: v.domain, enabled: v.enabled } };
7732
+ },
7733
+ async customDomains(account2, bucket) {
7734
+ const r = await call2("GET", bucketPath(account2, bucket, "domains/custom"));
7735
+ if (!r.ok) return r;
7736
+ const list = r.value?.domains;
7737
+ if (!Array.isArray(list)) {
7738
+ return {
7739
+ ok: false,
7740
+ problem: `the custom-domains answer has no domains array: ${JSON.stringify(r.value).slice(0, 200)}`
7741
+ };
7742
+ }
7743
+ const domains = [];
7744
+ for (const d of list) {
7745
+ if (typeof d?.domain !== "string" || typeof d.enabled !== "boolean") {
7746
+ return {
7747
+ ok: false,
7748
+ problem: `a custom domain entry has no domain/enabled: ${JSON.stringify(d).slice(0, 200)}`
7749
+ };
7750
+ }
7751
+ domains.push({ domain: d.domain, enabled: d.enabled });
7752
+ }
7753
+ return { ok: true, value: domains };
7754
+ }
7755
+ };
7756
+ }
7757
+ var ABORT_MULTIPART_AFTER_DAYS, DAY_SECONDS, EXPIRE_RULE_ID, ABORT_RULE_ID, API;
7758
+ var init_artifactsBucket = __esm({
7759
+ "../../src/deploy/artifactsBucket.ts"() {
7760
+ "use strict";
7761
+ init_imagesHost();
7762
+ ABORT_MULTIPART_AFTER_DAYS = 1;
7763
+ DAY_SECONDS = 86400;
7764
+ EXPIRE_RULE_ID = "switchboard-artifacts-expire";
7765
+ ABORT_RULE_ID = "switchboard-artifacts-abort-multipart";
7766
+ API = "https://api.cloudflare.com/client/v4";
7767
+ }
7768
+ });
7769
+
7585
7770
  // ../../src/deploy/secrets.ts
7586
7771
  import { z as z3 } from "zod";
7587
7772
  function isOptionalOn(secret2, worker) {
@@ -7671,9 +7856,9 @@ var init_secrets = __esm({
7671
7856
  import { spawn as spawn4 } from "node:child_process";
7672
7857
  import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
7673
7858
  import { homedir as homedir2 } from "node:os";
7674
- import { isAbsolute as isAbsolute4, join as join6, resolve as resolve4 } from "node:path";
7859
+ import { isAbsolute as isAbsolute4, join as join5, resolve as resolve4 } from "node:path";
7675
7860
  function expandDir(path, root = OPERATOR_ROOT.root) {
7676
- if (path === "~" || path.startsWith("~/")) return join6(homedir2(), path.slice(1));
7861
+ if (path === "~" || path.startsWith("~/")) return join5(homedir2(), path.slice(1));
7677
7862
  if (isAbsolute4(path)) return path;
7678
7863
  return resolve4(root, path);
7679
7864
  }
@@ -7717,7 +7902,7 @@ async function opFieldLabels(source2) {
7717
7902
  }
7718
7903
  function wranglerBin(dir, workspaceRoot, exists = existsSync6) {
7719
7904
  for (const base of [dir, workspaceRoot]) {
7720
- const bin = join6(base, "node_modules", ".bin", "wrangler");
7905
+ const bin = join5(base, "node_modules", ".bin", "wrangler");
7721
7906
  if (exists(bin)) return bin;
7722
7907
  }
7723
7908
  return "wrangler";
@@ -7738,7 +7923,7 @@ var init_secretsHost = __esm({
7738
7923
  if (source2.kind === "dir") {
7739
7924
  const dir = expandDir(source2.path);
7740
7925
  if (!existsSync6(dir)) return { ok: false, problem: `secretsSource ${source2.path}: no such directory (${dir})` };
7741
- return { ok: true, present: new Set(names.filter((n2) => existsSync6(join6(dir, n2)))) };
7926
+ return { ok: true, present: new Set(names.filter((n2) => existsSync6(join5(dir, n2)))) };
7742
7927
  }
7743
7928
  const labels = await opFieldLabels(source2);
7744
7929
  if (!labels.ok) return labels;
@@ -7747,7 +7932,7 @@ var init_secretsHost = __esm({
7747
7932
  put: async (source2, dir, name) => {
7748
7933
  let value;
7749
7934
  if (source2.kind === "dir") {
7750
- value = readFileSync7(join6(expandDir(source2.path), name), "utf8");
7935
+ value = readFileSync7(join5(expandDir(source2.path), name), "utf8");
7751
7936
  } else {
7752
7937
  const r2 = await spawnCollect("op", ["read", `op://${source2.vault}/${source2.item}/${name}`]);
7753
7938
  if (r2.code !== 0) return { code: r2.code, output: `op read exited ${r2.code} \u2014 ${r2.stderr.trim()}` };
@@ -7842,6 +8027,7 @@ var init_runEvents = __esm({
7842
8027
  "mcp_unavailable",
7843
8028
  "follow_up",
7844
8029
  "resumed",
8030
+ "seed",
7845
8031
  "description_turn",
7846
8032
  "cold_sandbox",
7847
8033
  "pr_not_opened",
@@ -7960,11 +8146,11 @@ var init_readingDiff = __esm({
7960
8146
  // ../../src/core/meatProcess.ts
7961
8147
  import { spawn as spawn5 } from "node:child_process";
7962
8148
  import { accessSync, constants } from "node:fs";
7963
- import { delimiter, join as join7 } from "node:path";
8149
+ import { delimiter, join as join6 } from "node:path";
7964
8150
  function meatOnPath(env, binary = MEAT_BINARY) {
7965
8151
  return (env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").some((dir) => {
7966
8152
  try {
7967
- accessSync(join7(dir, binary), constants.X_OK);
8153
+ accessSync(join6(dir, binary), constants.X_OK);
7968
8154
  return true;
7969
8155
  } catch {
7970
8156
  return false;
@@ -8044,10 +8230,10 @@ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
8044
8230
  import { randomBytes } from "node:crypto";
8045
8231
  import { createInterface } from "node:readline/promises";
8046
8232
  import { Writable } from "node:stream";
8047
- import { dirname as dirname5, join as join8, resolve as resolve5 } from "node:path";
8233
+ import { dirname as dirname5, join as join7, resolve as resolve5 } from "node:path";
8048
8234
  async function readTemplates(root = PACKAGE_ROOT) {
8049
8235
  const read = async (key) => {
8050
- const path = join8(root, TEMPLATE_PATHS[key]);
8236
+ const path = join7(root, TEMPLATE_PATHS[key]);
8051
8237
  if (!existsSync7(path)) throw new Error(`${TEMPLATE_PATHS[key]}: no such file under ${root}`);
8052
8238
  return readFile(path, "utf8");
8053
8239
  };
@@ -8061,7 +8247,7 @@ function isCheckoutRoot(cwd = process.cwd(), root = PACKAGE_ROOT) {
8061
8247
  }
8062
8248
  }
8063
8249
  async function writePlannedFile(file, cwd = process.cwd()) {
8064
- const path = join8(cwd, file.path);
8250
+ const path = join7(cwd, file.path);
8065
8251
  await mkdir(dirname5(path), { recursive: true });
8066
8252
  const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
8067
8253
  try {
@@ -8096,7 +8282,7 @@ function ttyPrompter(io = { stdin: process.stdin, stdout: process.stdout }) {
8096
8282
  };
8097
8283
  }
8098
8284
  function projectFact(name, root) {
8099
- const facts2 = JSON.parse(readFileSync8(join8(root, "project.json"), "utf8"));
8285
+ const facts2 = JSON.parse(readFileSync8(join7(root, "project.json"), "utf8"));
8100
8286
  const value = facts2[name];
8101
8287
  if (typeof value !== "string") throw new Error(`project.json: no \`${name}\` fact`);
8102
8288
  return value;
@@ -8112,7 +8298,7 @@ function hostSetupIO(cwd = OPERATOR_ROOT.root, startedIn = process.cwd()) {
8112
8298
  templates: () => readTemplates(),
8113
8299
  // Where the files went when that is not where the operator stands — `init` prints it.
8114
8300
  root: () => resolve5(cwd) === resolve5(startedIn) ? void 0 : cwd,
8115
- exists: async (path) => existsSync7(join8(cwd, path)),
8301
+ exists: async (path) => existsSync7(join7(cwd, path)),
8116
8302
  write: (file) => writePlannedFile(file, cwd),
8117
8303
  // A file the operator NAMED (`--github-private-key-file app.pem`) is where the operator stands,
8118
8304
  // not in the installation: `init` run from ~/Downloads reads ~/Downloads/app.pem.
@@ -8168,9 +8354,9 @@ var init_binaryRead = __esm({
8168
8354
 
8169
8355
  // ../../src/secrets.ts
8170
8356
  import { readFileSync as readFileSync9 } from "node:fs";
8171
- import { join as join9 } from "node:path";
8357
+ import { join as join8 } from "node:path";
8172
8358
  function manifestNames() {
8173
- const raw = JSON.parse(readFileSync9(join9(PACKAGE_ROOT, MANIFEST_PATH), "utf8"));
8359
+ const raw = JSON.parse(readFileSync9(join8(PACKAGE_ROOT, MANIFEST_PATH), "utf8"));
8174
8360
  const parsed = parseManifest2(raw);
8175
8361
  if (!parsed.ok) throw new Error(`${MANIFEST_PATH} is invalid \u2014 ${parsed.problems.join("; ")}`);
8176
8362
  return parsed.manifest.secrets.map((s) => s.name);
@@ -11997,8 +12183,110 @@ var init_commandRegistry = __esm({
11997
12183
  }
11998
12184
  });
11999
12185
 
12000
- // ../../src/core/commands/config.ts
12186
+ // ../../src/core/commands/artifacts.ts
12001
12187
  import { z as z5 } from "zod";
12188
+ async function configured2(deps) {
12189
+ const cfg = await deps.artifacts.config();
12190
+ if (!cfg) {
12191
+ throw new CommandError(
12192
+ "unavailable",
12193
+ "artifacts: is not configured \u2014 name the bucket under `artifacts.r2` in config.yaml before managing it (docs/how-to/store-run-artifacts.md)"
12194
+ );
12195
+ }
12196
+ return cfg;
12197
+ }
12198
+ function registerArtifactsCommands(registry) {
12199
+ for (const cmd of artifactsCommands) registry.register(cmd);
12200
+ }
12201
+ var defineCommand2, lifecycleOptions, artifactsLifecycle, artifactsCheck, artifactsCommands;
12202
+ var init_artifacts = __esm({
12203
+ "../../src/core/commands/artifacts.ts"() {
12204
+ "use strict";
12205
+ init_config();
12206
+ init_artifactsBucket();
12207
+ init_commandRegistry();
12208
+ defineCommand2 = commandDefiner();
12209
+ lifecycleOptions = z5.object({
12210
+ dryRun: flag.optional().describe("print the rules that would be applied and touch nothing")
12211
+ });
12212
+ artifactsLifecycle = defineCommand2({
12213
+ id: "artifacts.lifecycle",
12214
+ options: lifecycleOptions,
12215
+ action: "deploy:write",
12216
+ effect: "write",
12217
+ surfaces: { chat: false, mcp: false, http: false },
12218
+ describe: "Apply the artifacts bucket's lifecycle rules from config.yaml \u2014 objects expire after `artifacts.retentionDays` (default 30), incomplete multipart uploads abort after one day \u2014 and read them back; `--dry-run` prints the rules and touches nothing. Operator-side: CLOUDFLARE_API_TOKEN with Workers R2 Storage: Edit, never the bot's token.",
12219
+ render: (output) => {
12220
+ const o = output;
12221
+ const lines = describeRules(o.rules).map((l) => ` - ${l}`);
12222
+ const head = o.applied ? `applied ${o.rules.length} lifecycle rule(s) to ${o.bucket} (account ${o.account}) and read them back equal:` : `dry run \u2014 would apply ${o.rules.length} lifecycle rule(s) to ${o.bucket} (account ${o.account}); nothing touched:`;
12223
+ return [head, ...lines].join("\n");
12224
+ },
12225
+ handler: async ({ options, deps }) => {
12226
+ const cfg = await configured2(deps);
12227
+ const retentionDays = cfg.retentionDays ?? ARTIFACT_DEFAULTS.retentionDays;
12228
+ const rules = lifecycleRulesFor(retentionDays);
12229
+ const { accountId: account2, bucket } = cfg.r2;
12230
+ const out = { account: account2, bucket, retentionDays, rules, applied: false, readBack: false };
12231
+ if (options.dryRun) return out;
12232
+ const put = await deps.artifacts.bucket.putLifecycle(account2, bucket, rules);
12233
+ if (!put.ok)
12234
+ throw new CommandError("unavailable", `applying the lifecycle rules to ${bucket} failed \u2014 ${put.problem}`);
12235
+ const read = await deps.artifacts.bucket.getLifecycle(account2, bucket);
12236
+ if (!read.ok)
12237
+ throw new CommandError(
12238
+ "unavailable",
12239
+ `the rules were applied to ${bucket} but reading them back failed \u2014 ${read.problem}; run \`artifacts lifecycle\` again to confirm`
12240
+ );
12241
+ const match = rulesMatch(read.value, rules);
12242
+ if (!match.ok)
12243
+ throw new CommandError(
12244
+ "unavailable",
12245
+ `the rules were applied to ${bucket} but the read-back differs \u2014 ${match.problem}; the bucket's configuration is not what config.yaml says`
12246
+ );
12247
+ return { ...out, applied: true, readBack: true };
12248
+ }
12249
+ });
12250
+ artifactsCheck = defineCommand2({
12251
+ id: "artifacts.check",
12252
+ action: "deploy:write",
12253
+ effect: "read",
12254
+ surfaces: { chat: false, mcp: false, http: false },
12255
+ describe: "Report whether the artifacts bucket is private: its managed r2.dev domain must be disabled and no custom domain enabled \u2014 the two ways R2 serves a bucket without a signature. Operator-side: CLOUDFLARE_API_TOKEN with Workers R2 Storage: Read, never the bot's token.",
12256
+ render: (output) => {
12257
+ const o = output;
12258
+ if (o.private) {
12259
+ return `${o.bucket} (account ${o.account}) is private: the managed domain ${o.managedDomain.domain} is disabled and ${o.customDomains.length === 0 ? "no custom domain is attached" : `none of its ${o.customDomains.length} custom domain(s) is enabled`}`;
12260
+ }
12261
+ return [`${o.bucket} (account ${o.account}) is NOT private:`, ...o.open.map((l) => ` - ${l}`)].join("\n");
12262
+ },
12263
+ handler: async ({ deps }) => {
12264
+ const cfg = await configured2(deps);
12265
+ const { accountId: account2, bucket } = cfg.r2;
12266
+ const managed = await deps.artifacts.bucket.managedDomain(account2, bucket);
12267
+ if (!managed.ok)
12268
+ throw new CommandError("unavailable", `reading ${bucket}'s managed domain failed \u2014 ${managed.problem}`);
12269
+ const custom = await deps.artifacts.bucket.customDomains(account2, bucket);
12270
+ if (!custom.ok)
12271
+ throw new CommandError("unavailable", `reading ${bucket}'s custom domains failed \u2014 ${custom.problem}`);
12272
+ const verdict = privacyOf(managed.value, custom.value);
12273
+ const out = {
12274
+ account: account2,
12275
+ bucket,
12276
+ private: verdict.private,
12277
+ open: [...verdict.open],
12278
+ managedDomain: managed.value,
12279
+ customDomains: custom.value
12280
+ };
12281
+ return out;
12282
+ }
12283
+ });
12284
+ artifactsCommands = [artifactsLifecycle, artifactsCheck];
12285
+ }
12286
+ });
12287
+
12288
+ // ../../src/core/commands/config.ts
12289
+ import { z as z6 } from "zod";
12002
12290
  function targetChannel(caller, channel) {
12003
12291
  const target = channel ?? caller.origin?.channelId;
12004
12292
  if (!target) throw new CommandError("invalid_input", "channel: required on this surface \u2014 pass --channel <id>");
@@ -12018,7 +12306,7 @@ function summarizeScope(s) {
12018
12306
  function registerConfigCommands(registry) {
12019
12307
  for (const cmd of configCommands) registry.register(cmd);
12020
12308
  }
12021
- var defineCommand2, scopeArg, channelOption, effort, effortLevels, modelRef, boundaryOption, who, configShow, configSet, configClear, quote, configInstructions, configCommands;
12309
+ var defineCommand3, scopeArg, channelOption, effort, effortLevels, modelRef, boundaryOption, who, configShow, configSet, configClear, quote, configInstructions, configCommands;
12022
12310
  var init_config4 = __esm({
12023
12311
  "../../src/core/commands/config.ts"() {
12024
12312
  "use strict";
@@ -12028,25 +12316,25 @@ var init_config4 = __esm({
12028
12316
  init_effort();
12029
12317
  init_authorize();
12030
12318
  init_commandRegistry();
12031
- defineCommand2 = commandDefiner();
12319
+ defineCommand3 = commandDefiner();
12032
12320
  scopeArg = {
12033
12321
  name: "scope",
12034
- schema: z5.enum(["channel", "me"]),
12322
+ schema: z6.enum(["channel", "me"]),
12035
12323
  describe: "`channel` (everyone here) or `me` (your own runs)"
12036
12324
  };
12037
- channelOption = z5.string().optional().describe("target channel (default: the channel you are speaking in)");
12038
- effort = z5.enum(EFFORT_LEVELS);
12325
+ channelOption = z6.string().optional().describe("target channel (default: the channel you are speaking in)");
12326
+ effort = z6.enum(EFFORT_LEVELS);
12039
12327
  effortLevels = `<${EFFORT_LEVELS.join("|")}>`;
12040
- modelRef = z5.string().min(1);
12041
- boundaryOption = z5.object({
12042
- maxMinutes: z5.coerce.number().int().min(MIN_BOUNDARY_MINUTES).optional().describe(`cap the wall-clock budget of every run in this scope (minutes, >= ${MIN_BOUNDARY_MINUTES})`),
12043
- maxIdentity: z5.enum(IDENTITIES).optional().describe(`cap the identity runs may act as <${IDENTITIES.join("|")}> \u2014 a preset above it is refused`),
12044
- machines: z5.string().optional().describe(`the machine classes runs may execute on, comma-separated from ${MACHINE_CLASSES.join(", ")}`)
12328
+ modelRef = z6.string().min(1);
12329
+ boundaryOption = z6.object({
12330
+ maxMinutes: z6.coerce.number().int().min(MIN_BOUNDARY_MINUTES).optional().describe(`cap the wall-clock budget of every run in this scope (minutes, >= ${MIN_BOUNDARY_MINUTES})`),
12331
+ maxIdentity: z6.enum(IDENTITIES).optional().describe(`cap the identity runs may act as <${IDENTITIES.join("|")}> \u2014 a preset above it is refused`),
12332
+ machines: z6.string().optional().describe(`the machine classes runs may execute on, comma-separated from ${MACHINE_CLASSES.join(", ")}`)
12045
12333
  }).optional();
12046
12334
  who = (scope) => scope === "channel" ? "channel" : "your";
12047
- configShow = defineCommand2({
12335
+ configShow = defineCommand3({
12048
12336
  id: "config.show",
12049
- options: z5.object({ channel: channelOption }),
12337
+ options: z6.object({ channel: channelOption }),
12050
12338
  action: "config:read",
12051
12339
  effect: "read",
12052
12340
  describe: "The effective agent/model/effort for you in this channel, the defaults, both scopes, and what is restricted.",
@@ -12060,15 +12348,15 @@ var init_config4 = __esm({
12060
12348
  return description;
12061
12349
  }
12062
12350
  });
12063
- configSet = defineCommand2({
12351
+ configSet = defineCommand3({
12064
12352
  id: "config.set",
12065
12353
  args: [scopeArg],
12066
- options: z5.object({
12067
- agent: z5.string().min(1).optional().describe("force which agent handles requests in this scope"),
12354
+ options: z6.object({
12355
+ agent: z6.string().min(1).optional().describe("force which agent handles requests in this scope"),
12068
12356
  model: modelRef.optional().describe("force a model (provider/model) regardless of agent"),
12069
- models: z5.record(z5.string(), modelRef).optional().describe("per-agent model: --models.<agent> provider/model"),
12357
+ models: z6.record(z6.string(), modelRef).optional().describe("per-agent model: --models.<agent> provider/model"),
12070
12358
  effort: effort.optional().describe(`force a model effort ${effortLevels}`),
12071
- efforts: z5.record(z5.string(), effort).optional().describe(`per-agent effort: --efforts.<agent> ${effortLevels}`),
12359
+ efforts: z6.record(z6.string(), effort).optional().describe(`per-agent effort: --efforts.<agent> ${effortLevels}`),
12072
12360
  boundary: boundaryOption,
12073
12361
  channel: channelOption
12074
12362
  }),
@@ -12135,10 +12423,10 @@ var init_config4 = __esm({
12135
12423
  return { scope: args.scope, effective: summarizeScope(effective) };
12136
12424
  }
12137
12425
  });
12138
- configClear = defineCommand2({
12426
+ configClear = defineCommand3({
12139
12427
  id: "config.clear",
12140
12428
  args: [scopeArg],
12141
- options: z5.object({ channel: channelOption }),
12429
+ options: z6.object({ channel: channelOption }),
12142
12430
  action: "config:write",
12143
12431
  effect: "write",
12144
12432
  describe: "Drop every runtime override of a channel (gated) or of yourself; static config.yaml values show through again.",
@@ -12155,18 +12443,18 @@ var init_config4 = __esm({
12155
12443
  }
12156
12444
  });
12157
12445
  quote = (text) => `> ${text.replace(/\n/g, "\n> ")}`;
12158
- configInstructions = defineCommand2({
12446
+ configInstructions = defineCommand3({
12159
12447
  id: "config.instructions",
12160
12448
  args: [
12161
12449
  scopeArg,
12162
12450
  {
12163
12451
  name: "text",
12164
- schema: z5.string().optional(),
12452
+ schema: z6.string().optional(),
12165
12453
  describe: 'the instructions; omit to show the current text, pass "" to clear',
12166
12454
  rest: true
12167
12455
  }
12168
12456
  ],
12169
- options: z5.object({ channel: channelOption }),
12457
+ options: z6.object({ channel: channelOption }),
12170
12458
  action: "config:write",
12171
12459
  effect: "write",
12172
12460
  describe: "Custom instructions for a channel (gated) or for yourself \u2014 advisory prompt content that never changes agent, model, or permissions.",
@@ -12226,8 +12514,8 @@ ${quote(current ?? "")}`;
12226
12514
  });
12227
12515
 
12228
12516
  // ../../src/core/commands/contract.ts
12229
- import { basename, dirname as dirname8, join as join10 } from "node:path";
12230
- import { z as z6 } from "zod";
12517
+ import { basename, dirname as dirname8, join as join9 } from "node:path";
12518
+ import { z as z7 } from "zod";
12231
12519
  function repoRootOfPlan(planPath) {
12232
12520
  const plans = dirname8(planPath);
12233
12521
  const docs = dirname8(plans);
@@ -12245,24 +12533,24 @@ function renderOutput(output) {
12245
12533
  function registerContractCommands(registry) {
12246
12534
  for (const cmd of contractCommands) registry.register(cmd);
12247
12535
  }
12248
- var defineCommand3, SPECS_DIR, RULES_FILES, contractRender, contractCommands;
12536
+ var defineCommand4, SPECS_DIR, RULES_FILES, contractRender, contractCommands;
12249
12537
  var init_contract2 = __esm({
12250
12538
  "../../src/core/commands/contract.ts"() {
12251
12539
  "use strict";
12252
12540
  init_commandRegistry();
12253
12541
  init_contract();
12254
- defineCommand3 = commandDefiner();
12255
- SPECS_DIR = join10("docs", "reference", "specs");
12542
+ defineCommand4 = commandDefiner();
12543
+ SPECS_DIR = join9("docs", "reference", "specs");
12256
12544
  RULES_FILES = ["AGENTS.md", "CLAUDE.md"];
12257
- contractRender = defineCommand3({
12545
+ contractRender = defineCommand4({
12258
12546
  id: "contract.render",
12259
- options: z6.object({
12260
- plan: z6.string().min(1).describe("path of the plan record (docs/plans/<file>.md)"),
12261
- unit: z6.string().regex(/^U\d+$/, "expected a unit id like U<n>").describe("the unit to render, as its heading spells it (U<n>)"),
12262
- root: z6.string().min(1).optional().describe("the repository root the specs and rules are read under (default: the root the plan path implies)"),
12263
- branch: z6.string().min(1).optional().describe("the unit's branch, named in the first instruction"),
12264
- onto: z6.string().min(1).optional().describe("the merged parent the branch is rebased onto"),
12265
- maxChars: z6.coerce.number().int().positive().optional().describe(`the rendered block's budget in characters (default ${DEFAULT_CONTRACT_MAX_CHARS})`)
12547
+ options: z7.object({
12548
+ plan: z7.string().min(1).describe("path of the plan record (docs/plans/<file>.md)"),
12549
+ unit: z7.string().regex(/^U\d+$/, "expected a unit id like U<n>").describe("the unit to render, as its heading spells it (U<n>)"),
12550
+ root: z7.string().min(1).optional().describe("the repository root the specs and rules are read under (default: the root the plan path implies)"),
12551
+ branch: z7.string().min(1).optional().describe("the unit's branch, named in the first instruction"),
12552
+ onto: z7.string().min(1).optional().describe("the merged parent the branch is rebased onto"),
12553
+ maxChars: z7.coerce.number().int().positive().optional().describe(`the rendered block's budget in characters (default ${DEFAULT_CONTRACT_MAX_CHARS})`)
12266
12554
  }),
12267
12555
  action: "contract:read",
12268
12556
  effect: "read",
@@ -12276,10 +12564,10 @@ var init_contract2 = __esm({
12276
12564
  const unit = parsePlanUnit(planMarkdown, options.unit);
12277
12565
  const specs = /* @__PURE__ */ new Map();
12278
12566
  for (const spec of new Set(unit ? specItemRefs(unit.section).map((r) => r.spec) : []))
12279
- specs.set(spec, await deps.contract.readFile(join10(root, SPECS_DIR, spec)));
12567
+ specs.set(spec, await deps.contract.readFile(join9(root, SPECS_DIR, spec)));
12280
12568
  let agentRules;
12281
12569
  for (const file of RULES_FILES) {
12282
- const text = await deps.contract.readFile(join10(root, file));
12570
+ const text = await deps.contract.readFile(join9(root, file));
12283
12571
  if (text !== void 0) {
12284
12572
  agentRules = { file, text };
12285
12573
  break;
@@ -13204,7 +13492,7 @@ function isSetupInstallCommand(summary) {
13204
13492
  return INSTALL_SEGMENT.test(cmd.replace(QUOTED, '""'));
13205
13493
  }
13206
13494
  function isNarrative(ev) {
13207
- return ev.type === "input" || ev.type === "context" || ev.type === "assistant" || ev.type === "answer" || ev.type === "run_meta";
13495
+ return ev.type === "input" || ev.type === "context" || ev.type === "assistant" || ev.type === "answer" || ev.type === "notes" || ev.type === "run_meta";
13208
13496
  }
13209
13497
  function spanSet(events) {
13210
13498
  const normalized = normalizeSpans(events);
@@ -13909,7 +14197,7 @@ var init_runRecord = __esm({
13909
14197
  RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
13910
14198
  RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
13911
14199
  REVIEW_HEAD_PATTERN = /^[0-9a-f]{7,40}$/;
13912
- RUN_SEEDS = ["channel", "parent"];
14200
+ RUN_SEEDS = ["channel", "parent", "session"];
13913
14201
  SESSION_KEY_PATTERN = /^[A-Za-z0-9_.:@+/-]{1,512}$/;
13914
14202
  isIndex = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
13915
14203
  MACHINE_CLASSES_IN_RECORD = {
@@ -13954,11 +14242,11 @@ var init_runRecord = __esm({
13954
14242
  });
13955
14243
 
13956
14244
  // ../../src/core/commands/delivery.ts
13957
- import { z as z7 } from "zod";
14245
+ import { z as z8 } from "zod";
13958
14246
  function registerDeliveryCommands(registry) {
13959
14247
  for (const cmd of deliveryCommands) registry.register(cmd);
13960
14248
  }
13961
- var defineCommand4, REPO_SLUG3, ISO_DAY3, asJson, deliveryReport, deliveryCommands;
14249
+ var defineCommand5, REPO_SLUG3, ISO_DAY3, asJson, deliveryReport, deliveryCommands;
13962
14250
  var init_delivery2 = __esm({
13963
14251
  "../../src/core/commands/delivery.ts"() {
13964
14252
  "use strict";
@@ -13966,16 +14254,16 @@ var init_delivery2 = __esm({
13966
14254
  init_commandRegistry();
13967
14255
  init_delivery();
13968
14256
  init_runRecord();
13969
- defineCommand4 = commandDefiner();
14257
+ defineCommand5 = commandDefiner();
13970
14258
  REPO_SLUG3 = /^(?!\.+\/)[\w.-]+\/(?!\.+$)[\w.-]+$/;
13971
14259
  ISO_DAY3 = /^\d{4}-\d{2}-\d{2}$/;
13972
14260
  asJson = (v) => v;
13973
- deliveryReport = defineCommand4({
14261
+ deliveryReport = defineCommand5({
13974
14262
  id: "delivery.report",
13975
- options: z7.object({
13976
- repo: z7.string().regex(REPO_SLUG3).optional().describe("the repository (`owner/name`); default: the first under `delivery.repos`"),
13977
- since: z7.string().regex(ISO_DAY3).optional().describe("the first day of the range (`YYYY-MM-DD`, UTC); the range ends today"),
13978
- weeks: z7.coerce.number().int().min(1).max(MAX_WEEKS).optional().describe(
14263
+ options: z8.object({
14264
+ repo: z8.string().regex(REPO_SLUG3).optional().describe("the repository (`owner/name`); default: the first under `delivery.repos`"),
14265
+ since: z8.string().regex(ISO_DAY3).optional().describe("the first day of the range (`YYYY-MM-DD`, UTC); the range ends today"),
14266
+ weeks: z8.coerce.number().int().min(1).max(MAX_WEEKS).optional().describe(
13979
14267
  `Monday-start weeks back from this one (default 4, at most ${MAX_WEEKS}); --since, when given, decides instead`
13980
14268
  ),
13981
14269
  fresh: flag.optional().describe("read GitHub now instead of the repository's snapshot, and refresh the snapshot")
@@ -14020,7 +14308,7 @@ var init_delivery2 = __esm({
14020
14308
  });
14021
14309
 
14022
14310
  // ../../src/core/commands/deploy.ts
14023
- import { z as z8 } from "zod";
14311
+ import { z as z9 } from "zod";
14024
14312
  function toOptions(o, dryRun, affected) {
14025
14313
  return {
14026
14314
  only: o.only,
@@ -14130,7 +14418,7 @@ async function copyImages(copies, account2, deps) {
14130
14418
  function registerDeployCommands(registry) {
14131
14419
  for (const cmd of deployCommands) registry.register(cmd);
14132
14420
  }
14133
- var defineCommand5, workerList, positiveInt, gitRef, deployOptions, planJson, deployPlan, allOptions, copiedImage, listCopies, deployAll, restartOptions, deployRestart, initOptions, deployInit, configOptions, deployConfig, secretNames, secretsOptions, deploySecrets, imagesOptions, deployImages, deployCommands;
14421
+ var defineCommand6, workerList, positiveInt, gitRef, deployOptions, planJson, deployPlan, allOptions, copiedImage, listCopies, deployAll, restartOptions, deployRestart, initOptions, deployInit, configOptions, deployConfig, secretNames, secretsOptions, deploySecrets, imagesOptions, deployImages, deployCommands;
14134
14422
  var init_deploy = __esm({
14135
14423
  "../../src/core/commands/deploy.ts"() {
14136
14424
  "use strict";
@@ -14143,16 +14431,16 @@ var init_deploy = __esm({
14143
14431
  init_wranglerTemplate();
14144
14432
  init_secrets();
14145
14433
  init_commandRegistry();
14146
- defineCommand5 = commandDefiner();
14147
- workerList = z8.string().transform(
14434
+ defineCommand6 = commandDefiner();
14435
+ workerList = z9.string().transform(
14148
14436
  (s) => s.split(",").map((x) => x.trim()).filter(Boolean)
14149
14437
  ).refine(
14150
14438
  (names) => names.every((n2) => DEPLOY_ORDER.includes(n2)),
14151
14439
  `expected a comma list of Workers (${DEPLOY_ORDER.join(", ")})`
14152
14440
  ).transform((names) => [...new Set(names)]);
14153
- positiveInt = z8.coerce.number().int().positive();
14154
- gitRef = z8.string().regex(/^[A-Za-z0-9_./^~@{}][A-Za-z0-9_./^~@{}-]{0,199}$/, "expected a git revision (a sha, tag, branch, or HEAD^)");
14155
- deployOptions = z8.object({
14441
+ positiveInt = z9.coerce.number().int().positive();
14442
+ gitRef = z9.string().regex(/^[A-Za-z0-9_./^~@{}][A-Za-z0-9_./^~@{}-]{0,199}$/, "expected a git revision (a sha, tag, branch, or HEAD^)");
14443
+ deployOptions = z9.object({
14156
14444
  only: workerList.optional().describe(
14157
14445
  `deploy only these Workers (comma list of ${DEPLOY_ORDER.join(", ")}); with --affected, only those of them that are affected`
14158
14446
  ),
@@ -14171,7 +14459,7 @@ var init_deploy = __esm({
14171
14459
  poll: positiveInt.optional().describe("seconds between preflight retries (default 60)")
14172
14460
  });
14173
14461
  planJson = (plan3) => plan3;
14174
- deployPlan = defineCommand5({
14462
+ deployPlan = defineCommand6({
14175
14463
  id: "deploy.plan",
14176
14464
  options: deployOptions,
14177
14465
  action: "deploy:read",
@@ -14187,7 +14475,7 @@ var init_deploy = __esm({
14187
14475
  });
14188
14476
  copiedImage = (c) => ({ kind: c.kind, source: c.source, target: c.target });
14189
14477
  listCopies = (copies) => copies.map((c) => `${c.kind} \u2190 ${c.source}`).join(", ");
14190
- deployAll = defineCommand5({
14478
+ deployAll = defineCommand6({
14191
14479
  id: "deploy.all",
14192
14480
  options: allOptions,
14193
14481
  action: "deploy:write",
@@ -14238,15 +14526,15 @@ ${formatDeployResults(result.results, result.notAttempted)}`
14238
14526
  return output(result.results, copies, []);
14239
14527
  }
14240
14528
  });
14241
- restartOptions = z8.object({
14242
- only: z8.enum(["bot"]).optional().describe("the Worker to restart \u2014 only `bot` has a long-lived container (default bot)"),
14529
+ restartOptions = z9.object({
14530
+ only: z9.enum(["bot"]).optional().describe("the Worker to restart \u2014 only `bot` has a long-lived container (default bot)"),
14243
14531
  force: flag.optional().describe(
14244
14532
  "restart even when the bot cannot be consulted (no JSON on /healthz) \u2014 the fail-closed cases; runs in flight never refuse, they hand off"
14245
14533
  ),
14246
14534
  waitMax: positiveInt.optional().describe("minutes to wait out a refusal (the bot not answering with JSON) before giving up (default 10)"),
14247
14535
  poll: positiveInt.optional().describe("seconds between retries while refused (default 60)")
14248
14536
  });
14249
- deployRestart = defineCommand5({
14537
+ deployRestart = defineCommand6({
14250
14538
  id: "deploy.restart",
14251
14539
  options: restartOptions,
14252
14540
  action: "deploy:write",
@@ -14287,12 +14575,12 @@ ${formatDeployResults(result.results, result.notAttempted)}`
14287
14575
  };
14288
14576
  }
14289
14577
  });
14290
- initOptions = z8.object({
14578
+ initOptions = z9.object({
14291
14579
  check: flag.optional().describe(
14292
14580
  "compare only: report each rendered file that differs from the one on disk and fail when any does (the `deploy:check` gate); nothing is written"
14293
14581
  )
14294
14582
  });
14295
- deployInit = defineCommand5({
14583
+ deployInit = defineCommand6({
14296
14584
  id: "deploy.init",
14297
14585
  options: initOptions,
14298
14586
  action: "deploy:write",
@@ -14339,12 +14627,12 @@ ${formatDeployResults(result.results, result.notAttempted)}`
14339
14627
  return output;
14340
14628
  }
14341
14629
  });
14342
- configOptions = z8.object({
14343
- source: z8.string().min(1).optional().describe(
14630
+ configOptions = z9.object({
14631
+ source: z9.string().min(1).optional().describe(
14344
14632
  "read the config from this source instead of the profile's configSource (a path, github://owner/repo/path@ref, or op://Vault/Item/field)"
14345
14633
  )
14346
14634
  });
14347
- deployConfig = defineCommand5({
14635
+ deployConfig = defineCommand6({
14348
14636
  id: "deploy.config",
14349
14637
  options: configOptions,
14350
14638
  action: "deploy:write",
@@ -14379,21 +14667,21 @@ the bot reads it on its next start: \`deploy restart\``;
14379
14667
  return output;
14380
14668
  }
14381
14669
  });
14382
- secretNames = z8.string().transform(
14670
+ secretNames = z9.string().transform(
14383
14671
  (s) => s.split(",").map((x) => x.trim()).filter(Boolean)
14384
14672
  ).refine(
14385
14673
  (names) => names.length > 0 && names.every((n2) => /^[A-Z][A-Z0-9_]*$/.test(n2)),
14386
14674
  "expected a comma list of secret names (SCREAMING_SNAKE)"
14387
14675
  ).transform((names) => [...new Set(names)]);
14388
- secretsOptions = z8.object({
14676
+ secretsOptions = z9.object({
14389
14677
  only: secretNames.optional().describe("put only these secrets (comma list of names from deploy/secrets.manifest.json)")
14390
14678
  });
14391
- deploySecrets = defineCommand5({
14679
+ deploySecrets = defineCommand6({
14392
14680
  id: "deploy.secrets",
14393
14681
  args: [
14394
14682
  {
14395
14683
  name: "worker",
14396
- schema: z8.enum(DEPLOY_ORDER),
14684
+ schema: z9.enum(DEPLOY_ORDER),
14397
14685
  describe: `the Worker to provision (${DEPLOY_ORDER.join(", ")})`
14398
14686
  }
14399
14687
  ],
@@ -14455,12 +14743,12 @@ the bot reads it on its next start: \`deploy restart\``;
14455
14743
  return output;
14456
14744
  }
14457
14745
  });
14458
- imagesOptions = z8.object({
14746
+ imagesOptions = z9.object({
14459
14747
  dryRun: flag.optional().describe(
14460
14748
  "say which images the account registry already holds at the version and which would be copied; nothing is copied"
14461
14749
  )
14462
14750
  });
14463
- deployImages = defineCommand5({
14751
+ deployImages = defineCommand6({
14464
14752
  id: "deploy.images",
14465
14753
  options: imagesOptions,
14466
14754
  action: "deploy:write",
@@ -14511,25 +14799,25 @@ the bot reads it on its next start: \`deploy restart\``;
14511
14799
  });
14512
14800
 
14513
14801
  // ../../src/core/commands/env.ts
14514
- import { z as z9 } from "zod";
14802
+ import { z as z10 } from "zod";
14515
14803
  function registerEnvCommands(registry) {
14516
14804
  for (const cmd of envCommands) registry.register(cmd);
14517
14805
  }
14518
- var defineCommand6, envBootstrap, envCommands;
14806
+ var defineCommand7, envBootstrap, envCommands;
14519
14807
  var init_env = __esm({
14520
14808
  "../../src/core/commands/env.ts"() {
14521
14809
  "use strict";
14522
14810
  init_bootstrap();
14523
14811
  init_commandRegistry();
14524
- defineCommand6 = commandDefiner();
14525
- envBootstrap = defineCommand6({
14812
+ defineCommand7 = commandDefiner();
14813
+ envBootstrap = defineCommand7({
14526
14814
  id: "env.bootstrap",
14527
- options: z9.object({
14528
- env: z9.string().min(1).describe("environment to materialize (UAT only; anything else is refused)"),
14529
- service: z9.string().min(1).describe("which downstream service's toolchain env to fill"),
14815
+ options: z10.object({
14816
+ env: z10.string().min(1).describe("environment to materialize (UAT only; anything else is refused)"),
14817
+ service: z10.string().min(1).describe("which downstream service's toolchain env to fill"),
14530
14818
  apply: flag.optional().describe("resolve the refs and WRITE the chmod-600 env file (default: dry-run)"),
14531
- out: z9.string().min(1).optional().describe("env-file path (default .agent-env/<service>.<env>.env)"),
14532
- manifest: z9.string().min(1).optional().describe(`manifest path (default ${DEFAULT_MANIFEST_PATH})`)
14819
+ out: z10.string().min(1).optional().describe("env-file path (default .agent-env/<service>.<env>.env)"),
14820
+ manifest: z10.string().min(1).optional().describe(`manifest path (default ${DEFAULT_MANIFEST_PATH})`)
14533
14821
  }),
14534
14822
  action: "env:write",
14535
14823
  effect: "write",
@@ -15127,6 +15415,7 @@ function isRunEvent(v) {
15127
15415
  case "input":
15128
15416
  case "assistant":
15129
15417
  case "context":
15418
+ case "notes":
15130
15419
  return typeof o.text === "string";
15131
15420
  // the narrative events carry text, not a summary
15132
15421
  case "run_meta":
@@ -15263,7 +15552,7 @@ var init_selfImprovement = __esm({
15263
15552
  });
15264
15553
 
15265
15554
  // ../../src/core/commands/friction.ts
15266
- import { z as z10 } from "zod";
15555
+ import { z as z11 } from "zod";
15267
15556
  async function ledgerOf(deps) {
15268
15557
  let ledger;
15269
15558
  try {
@@ -15291,7 +15580,7 @@ function inProgressHint(diagnosis, finished) {
15291
15580
  function registerFrictionCommands(registry) {
15292
15581
  for (const cmd of frictionCommands) registry.register(cmd);
15293
15582
  }
15294
- var defineCommand7, positiveInt2, repoSlug, NO_LEDGER_MESSAGE, NO_REPO_MESSAGE, asJson2, render, visibleRuns, frictionReport, frictionPropose, frictionAnalyze, frictionCommands;
15583
+ var defineCommand8, positiveInt2, repoSlug, NO_LEDGER_MESSAGE, NO_REPO_MESSAGE, asJson2, render, visibleRuns, frictionReport, frictionPropose, frictionAnalyze, frictionCommands;
15295
15584
  var init_friction = __esm({
15296
15585
  "../../src/core/commands/friction.ts"() {
15297
15586
  "use strict";
@@ -15302,19 +15591,19 @@ var init_friction = __esm({
15302
15591
  init_runEventLines();
15303
15592
  init_runFriction();
15304
15593
  init_selfImprovement();
15305
- defineCommand7 = commandDefiner();
15306
- positiveInt2 = z10.coerce.number().int().positive();
15307
- repoSlug = z10.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug");
15594
+ defineCommand8 = commandDefiner();
15595
+ positiveInt2 = z11.coerce.number().int().positive();
15596
+ repoSlug = z11.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug");
15308
15597
  NO_LEDGER_MESSAGE = "Run history is not configured in this deployment (`runHistory`), so there are no recent runs to analyze.";
15309
15598
  NO_REPO_MESSAGE = "Set `selfImprovement.repo` (an `owner/name`) in config.yaml to tell `friction propose` where to file issues.";
15310
15599
  asJson2 = (r) => r;
15311
15600
  render = (output) => formatSelfImprovementReport(output);
15312
15601
  visibleRuns = (caller) => predicateFor(caller.actor, "runs:read", "run");
15313
- frictionReport = defineCommand7({
15602
+ frictionReport = defineCommand8({
15314
15603
  id: "friction.report",
15315
15604
  enabledWhen: (caps) => caps.runHistory,
15316
- options: z10.object({
15317
- sinceMs: z10.coerce.number().int().nonnegative().optional().describe("only runs finished at or after this epoch ms"),
15605
+ options: z11.object({
15606
+ sinceMs: z11.coerce.number().int().nonnegative().optional().describe("only runs finished at or after this epoch ms"),
15318
15607
  limit: positiveInt2.optional().describe("newest n runs (default: the ledger's retained window)"),
15319
15608
  minRuns: positiveInt2.optional().describe("distinct runs a pattern must recur in (default selfImprovement.minRuns, else 2)")
15320
15609
  }),
@@ -15340,10 +15629,10 @@ var init_friction = __esm({
15340
15629
  });
15341
15630
  }
15342
15631
  });
15343
- frictionPropose = defineCommand7({
15632
+ frictionPropose = defineCommand8({
15344
15633
  id: "friction.propose",
15345
15634
  enabledWhen: (caps) => caps.runHistory,
15346
- options: z10.object({
15635
+ options: z11.object({
15347
15636
  dryRun: flag.optional().describe("compute and report everything, file nothing"),
15348
15637
  top: positiveInt2.optional().describe("proposals filed per pass (default selfImprovement.top, else 3)"),
15349
15638
  minRuns: positiveInt2.optional().describe("distinct runs a pattern must recur in (default selfImprovement.minRuns, else 2)"),
@@ -15375,17 +15664,17 @@ var init_friction = __esm({
15375
15664
  }
15376
15665
  }
15377
15666
  });
15378
- frictionAnalyze = defineCommand7({
15667
+ frictionAnalyze = defineCommand8({
15379
15668
  id: "friction.analyze",
15380
15669
  args: [
15381
15670
  {
15382
15671
  name: "source",
15383
- schema: z10.string().optional(),
15672
+ schema: z11.string().optional(),
15384
15673
  describe: "a saved run stream: JSON lines of run events or a curl'ed /runs/:id/events SSE capture (default: stdin)"
15385
15674
  }
15386
15675
  ],
15387
- options: z10.object({
15388
- slowMs: z10.coerce.number().nonnegative().optional().describe("a tool call slower than this many ms is a slow-tool finding"),
15676
+ options: z11.object({
15677
+ slowMs: z11.coerce.number().nonnegative().optional().describe("a tool call slower than this many ms is a slow-tool finding"),
15389
15678
  inProgress: flag.optional().describe("the capture was taken mid-run: a trailing call without a result is still executing, not a dead run")
15390
15679
  }),
15391
15680
  action: "friction:read",
@@ -15438,7 +15727,7 @@ var init_friction = __esm({
15438
15727
  });
15439
15728
 
15440
15729
  // ../../src/core/commandSurface.ts
15441
- import { z as z11 } from "zod";
15730
+ import { z as z12 } from "zod";
15442
15731
  function camelToKebab(s) {
15443
15732
  return s.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
15444
15733
  }
@@ -15510,7 +15799,7 @@ function isBooleanSchema(schema) {
15510
15799
  return false;
15511
15800
  }
15512
15801
  function jsonSchemaOf(schema) {
15513
- const out = z11.toJSONSchema(unwrap(schema), { io: "input" });
15802
+ const out = z12.toJSONSchema(unwrap(schema), { io: "input" });
15514
15803
  delete out.$schema;
15515
15804
  return out;
15516
15805
  }
@@ -15522,7 +15811,7 @@ function jsonSchemaFor(cmd) {
15522
15811
  if (!acceptsUndefined(arg.schema)) required.push(arg.name);
15523
15812
  }
15524
15813
  if (cmd.options) {
15525
- const opts = z11.toJSONSchema(cmd.options, { io: "input" });
15814
+ const opts = z12.toJSONSchema(cmd.options, { io: "input" });
15526
15815
  Object.assign(properties, opts.properties ?? {});
15527
15816
  required.push(...opts.required ?? []);
15528
15817
  }
@@ -15762,17 +16051,17 @@ function chatCommandList(commands) {
15762
16051
  function registerHelpCommands(registry) {
15763
16052
  for (const cmd of helpCommands) registry.register(cmd);
15764
16053
  }
15765
- var defineCommand8, COMMANDS_REFERENCE_URL, DIRECTIVES_HELP, helpShow, helpCommandsList, helpCommands;
16054
+ var defineCommand9, COMMANDS_REFERENCE_URL, DIRECTIVES_HELP, helpShow, helpCommandsList, helpCommands;
15766
16055
  var init_help = __esm({
15767
16056
  "../../src/core/commands/help.ts"() {
15768
16057
  "use strict";
15769
16058
  init_commandRegistry();
15770
16059
  init_commandSurface();
15771
16060
  init_docsLink();
15772
- defineCommand8 = commandDefiner();
16061
+ defineCommand9 = commandDefiner();
15773
16062
  COMMANDS_REFERENCE_URL = `${PROJECT_DOCS_URL}/reference/slack-commands`;
15774
16063
  DIRECTIVES_HELP = "`agent:review model:anthropic/claude-opus-5 effort:low budget:20 look at PR 42` (effort: low | medium | high \u2014 lower = faster turns; budget: whole minutes, at least 2 \u2014 narrows this run's wall clock, never widens it)";
15775
- helpShow = defineCommand8({
16064
+ helpShow = defineCommand9({
15776
16065
  id: "help.show",
15777
16066
  action: "help:read",
15778
16067
  effect: "read",
@@ -15783,7 +16072,7 @@ var init_help = __esm({
15783
16072
  commandsReference: COMMANDS_REFERENCE_URL
15784
16073
  })
15785
16074
  });
15786
- helpCommandsList = defineCommand8({
16075
+ helpCommandsList = defineCommand9({
15787
16076
  id: "help.commands",
15788
16077
  action: "help:read",
15789
16078
  effect: "read",
@@ -16975,7 +17264,11 @@ var init_service = __esm({
16975
17264
  /** A fresh connect link for a runtime bearer/oauth server (first connect, or a re-key). */
16976
17265
  async connect(actor, target, name) {
16977
17266
  const { scopeKey, entry, source: source2 } = this.owned(target, name);
16978
- if (entry.auth === "none") throw new McpServiceError("invalid_input", `"${name}" needs no credential (auth: none)`);
17267
+ if (entry.auth === "none")
17268
+ throw new McpServiceError(
17269
+ "invalid_input",
17270
+ entry.headersEnv ? `"${name}" is pinned in config.yaml with headersEnv \u2014 its headers are environment variables on the bot, not a stored credential` : `"${name}" needs no credential (auth: none)`
17271
+ );
16979
17272
  if (entry.tokenEnv)
16980
17273
  throw new McpServiceError(
16981
17274
  "invalid_input",
@@ -17452,6 +17745,15 @@ var init_service = __esm({
17452
17745
  url: r.entry.url,
17453
17746
  agents: r.entry.agents ?? [...MCP_SELF_SERVE_AGENTS]
17454
17747
  };
17748
+ if (r.entry.headersEnv) {
17749
+ const headers = {};
17750
+ for (const [header4, envVar] of Object.entries(r.entry.headersEnv)) {
17751
+ const value = this.opts.bearers.named(envVar);
17752
+ if (!value) return { name: r.name, unavailable: `${envVar} is not set on the bot` };
17753
+ headers[header4] = value.reveal();
17754
+ }
17755
+ base.headers = headers;
17756
+ }
17455
17757
  if (r.entry.auth === "none") return { spec: base };
17456
17758
  if (r.entry.tokenEnv) {
17457
17759
  const token2 = this.opts.bearers.named(r.entry.tokenEnv);
@@ -17520,7 +17822,7 @@ var init_service = __esm({
17520
17822
  });
17521
17823
 
17522
17824
  // ../../src/core/commands/mcp.ts
17523
- import { z as z12 } from "zod";
17825
+ import { z as z13 } from "zod";
17524
17826
  async function serviceOf(deps) {
17525
17827
  const svc = await deps.mcp.service();
17526
17828
  if ("unavailable" in svc) throw new CommandError("unavailable", svc.unavailable);
@@ -17571,7 +17873,7 @@ async function settleConnect(deps, output) {
17571
17873
  }
17572
17874
  }
17573
17875
  function renderServerLine(r) {
17574
- const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (tokenEnv)" : "\u23F3 awaiting credential";
17876
+ const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (bot env)" : "\u23F3 awaiting credential";
17575
17877
  const agents = Array.isArray(r.agents) ? r.agents.join(", ") : "";
17576
17878
  const pinned = r.source === "config" ? " \xB7 pinned in config.yaml" : "";
17577
17879
  return `\u2022 \`${String(r.name)}\` (${String(r.scope)}) ${state} \u2014 ${String(r.url)} \xB7 agents: ${agents} \xB7 auth: ${String(r.auth)}${pinned}`;
@@ -17614,7 +17916,7 @@ function renderShow(output) {
17614
17916
  function registerMcpCommands(registry) {
17615
17917
  for (const cmd of MCP_COMMANDS) registry.register(cmd);
17616
17918
  }
17617
- var defineCommand9, serverName, serverUrl, scopeOption, channelOption2, nameArg, mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
17919
+ var defineCommand10, serverName, serverUrl, scopeOption, channelOption2, nameArg, mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
17618
17920
  var init_mcp = __esm({
17619
17921
  "../../src/core/commands/mcp.ts"() {
17620
17922
  "use strict";
@@ -17622,18 +17924,18 @@ var init_mcp = __esm({
17622
17924
  init_service();
17623
17925
  init_authorize();
17624
17926
  init_commandRegistry();
17625
- defineCommand9 = commandDefiner();
17626
- serverName = z12.string().refine((s) => MCP_SERVER_NAME_PATTERN.test(s), "expected a slug: lowercase letters, digits, dashes (\u2264 32 chars)");
17627
- serverUrl = z12.string().refine((s) => /^https?:\/\/\S+$/.test(s), "expected an http(s) URL");
17628
- scopeOption = z12.enum(["me", "channel", "org"]).optional().describe(
17927
+ defineCommand10 = commandDefiner();
17928
+ serverName = z13.string().refine((s) => MCP_SERVER_NAME_PATTERN.test(s), "expected a slug: lowercase letters, digits, dashes (\u2264 32 chars)");
17929
+ serverUrl = z13.string().refine((s) => /^https?:\/\/\S+$/.test(s), "expected an http(s) URL");
17930
+ scopeOption = z13.enum(["me", "channel", "org"]).optional().describe(
17629
17931
  "whose server: yours (`me`, default), this channel's (`channel`, channel-config rights), or org-wide (`org`, admins)"
17630
17932
  );
17631
- channelOption2 = z12.string().optional().describe("target channel for `--scope channel` and for `list` (default: the channel you are speaking in)");
17933
+ channelOption2 = z13.string().optional().describe("target channel for `--scope channel` and for `list` (default: the channel you are speaking in)");
17632
17934
  nameArg = { name: "name", schema: serverName, describe: "the server's name (see `mcp list`)" };
17633
- mcpList = defineCommand9({
17935
+ mcpList = defineCommand10({
17634
17936
  id: "mcp.list",
17635
17937
  enabledWhen: (caps) => caps.mcp,
17636
- options: z12.object({ channel: channelOption2 }),
17938
+ options: z13.object({ channel: channelOption2 }),
17637
17939
  action: "mcp:read",
17638
17940
  effect: "read",
17639
17941
  describe: "External MCP servers your runs in this channel can use \u2014 org-wide, this channel's, and your own \u2014 with state and agents; never a credential.",
@@ -17645,7 +17947,7 @@ var init_mcp = __esm({
17645
17947
  return { servers };
17646
17948
  }
17647
17949
  });
17648
- mcpAdd = defineCommand9({
17950
+ mcpAdd = defineCommand10({
17649
17951
  id: "mcp.add",
17650
17952
  enabledWhen: (caps) => caps.mcp,
17651
17953
  args: [
@@ -17655,15 +17957,15 @@ var init_mcp = __esm({
17655
17957
  describe: "a short slug for the server (becomes the tool prefix mcp__<name>__)"
17656
17958
  }
17657
17959
  ],
17658
- options: z12.object({
17960
+ options: z13.object({
17659
17961
  url: serverUrl.describe("the server's Streamable-HTTP endpoint (https://\u2026/mcp)"),
17660
17962
  scope: scopeOption,
17661
- agents: z12.string().transform(
17963
+ agents: z13.string().transform(
17662
17964
  (s) => s.split(",").map((a) => a.trim()).filter(Boolean)
17663
17965
  ).optional().describe(
17664
17966
  "comma-separated agents that may use it (default general,research; only org servers may name coding/review/ship)"
17665
17967
  ),
17666
- auth: z12.enum(["oauth", "bearer", "none"]).optional().describe(
17968
+ auth: z13.enum(["oauth", "bearer", "none"]).optional().describe(
17667
17969
  "how the server authenticates: `oauth` (you sign in on a one-time link), `bearer` (you paste a token on a one-time link), or `none`; omit to detect it from the server"
17668
17970
  ),
17669
17971
  channel: channelOption2
@@ -17688,11 +17990,11 @@ var init_mcp = __esm({
17688
17990
  );
17689
17991
  }
17690
17992
  });
17691
- mcpConnect = defineCommand9({
17993
+ mcpConnect = defineCommand10({
17692
17994
  id: "mcp.connect",
17693
17995
  enabledWhen: (caps) => caps.mcp,
17694
17996
  args: [nameArg],
17695
- options: z12.object({ scope: scopeOption, channel: channelOption2 }),
17997
+ options: z13.object({ scope: scopeOption, channel: channelOption2 }),
17696
17998
  action: "mcp:write",
17697
17999
  effect: "write",
17698
18000
  describe: "A fresh one-time link to sign in to an OAuth server or enter (or replace) a bearer server's token \u2014 only you can complete it; it expires in 10 minutes.",
@@ -17706,11 +18008,11 @@ var init_mcp = __esm({
17706
18008
  return await via(() => svc.connect(actor, target, args.name));
17707
18009
  }
17708
18010
  });
17709
- mcpShow = defineCommand9({
18011
+ mcpShow = defineCommand10({
17710
18012
  id: "mcp.show",
17711
18013
  enabledWhen: (caps) => caps.mcp,
17712
18014
  args: [nameArg],
17713
- options: z12.object({ scope: scopeOption, channel: channelOption2 }),
18015
+ options: z13.object({ scope: scopeOption, channel: channelOption2 }),
17714
18016
  action: "mcp:read",
17715
18017
  effect: "read",
17716
18018
  describe: "One MCP server's entry plus a live probe of the tools it offers (names, read-only flags); never a credential.",
@@ -17726,11 +18028,11 @@ var init_mcp = __esm({
17726
18028
  return await via(() => svc.show(actor, target, args.name));
17727
18029
  }
17728
18030
  });
17729
- mcpRemove = defineCommand9({
18031
+ mcpRemove = defineCommand10({
17730
18032
  id: "mcp.remove",
17731
18033
  enabledWhen: (caps) => caps.mcp,
17732
18034
  args: [nameArg],
17733
- options: z12.object({ scope: scopeOption, channel: channelOption2 }),
18035
+ options: z13.object({ scope: scopeOption, channel: channelOption2 }),
17734
18036
  action: "mcp:write",
17735
18037
  effect: "write",
17736
18038
  describe: "Remove an MCP server you added and its stored credential (yours freely; channel ones need channel-config rights, org-wide ones admin rights).",
@@ -18168,7 +18470,7 @@ var init_llmOutput = __esm({
18168
18470
  });
18169
18471
 
18170
18472
  // ../../src/core/memory/reflection.ts
18171
- import { z as z13 } from "zod";
18473
+ import { z as z14 } from "zod";
18172
18474
  function shouldReflect(input) {
18173
18475
  if (input.agentName !== void 0 && NO_REFLECT_AGENTS.has(input.agentName)) return false;
18174
18476
  return input.toolCalls > 0 || input.historyTurns >= REFLECT_MIN_TURNS;
@@ -18387,7 +18689,7 @@ var init_reflection = __esm({
18387
18689
  "Output raw JSON with no code fence and no prose."
18388
18690
  ].join("\n");
18389
18691
  SUMMARY_INHERITANCE = ["user", "repo", "channel"];
18390
- REFLECTION_ENVELOPE = jsonOutput(z13.object({ facts: z13.array(z13.unknown()), summary: z13.unknown().optional() }), {
18692
+ REFLECTION_ENVELOPE = jsonOutput(z14.object({ facts: z14.array(z14.unknown()), summary: z14.unknown().optional() }), {
18391
18693
  name: "reflection-envelope"
18392
18694
  });
18393
18695
  SCOPE_KINDS = ["org", "user", "repo", "channel"];
@@ -18507,11 +18809,11 @@ var init_workerStore = __esm({
18507
18809
  });
18508
18810
 
18509
18811
  // ../../src/core/memory/buildStore.ts
18510
- function resolveScopeCap(configured2, warn) {
18511
- if (configured2 === void 0) return DEFAULT_SCOPE_CAP;
18512
- if (Number.isInteger(configured2) && configured2 >= 1 && configured2 <= MAX_SCOPE_CAP) return configured2;
18812
+ function resolveScopeCap(configured3, warn) {
18813
+ if (configured3 === void 0) return DEFAULT_SCOPE_CAP;
18814
+ if (Number.isInteger(configured3) && configured3 >= 1 && configured3 <= MAX_SCOPE_CAP) return configured3;
18513
18815
  warn(
18514
- `memory.maxRecordsPerScope must be an integer between 1 and ${MAX_SCOPE_CAP} (got ${String(configured2)}) \u2014 using the default ${DEFAULT_SCOPE_CAP}.`
18816
+ `memory.maxRecordsPerScope must be an integer between 1 and ${MAX_SCOPE_CAP} (got ${String(configured3)}) \u2014 using the default ${DEFAULT_SCOPE_CAP}.`
18515
18817
  );
18516
18818
  return DEFAULT_SCOPE_CAP;
18517
18819
  }
@@ -18619,7 +18921,7 @@ var init_memory = __esm({
18619
18921
  });
18620
18922
 
18621
18923
  // ../../src/core/commands/memory.ts
18622
- import { z as z14 } from "zod";
18924
+ import { z as z15 } from "zod";
18623
18925
  function scopeKeyOfMemoryId(id) {
18624
18926
  const m = /^mem:(.+):\d+$/.exec(id);
18625
18927
  return m ? m[1] : void 0;
@@ -18673,18 +18975,18 @@ _(limit ${limit} reached \u2014 there may be more; narrow with words or raise \`
18673
18975
  function registerMemoryCommands(registry) {
18674
18976
  for (const cmd of memoryCommands) registry.register(cmd);
18675
18977
  }
18676
- var defineCommand10, MEMORY_LIST_LIMIT, MEMORY_LIST_MAX_LIMIT, MEMORY_OFF_MESSAGE, memoryId, repoScopeKey, channelScopeKey, isSharedScope, LIST_SCOPES, memoryList, memoryForget, memoryCommands;
18978
+ var defineCommand11, MEMORY_LIST_LIMIT, MEMORY_LIST_MAX_LIMIT, MEMORY_OFF_MESSAGE, memoryId, repoScopeKey, channelScopeKey, isSharedScope, LIST_SCOPES, memoryList, memoryForget, memoryCommands;
18677
18979
  var init_memory2 = __esm({
18678
18980
  "../../src/core/commands/memory.ts"() {
18679
18981
  "use strict";
18680
18982
  init_authorize();
18681
18983
  init_commandRegistry();
18682
18984
  init_memory();
18683
- defineCommand10 = commandDefiner();
18985
+ defineCommand11 = commandDefiner();
18684
18986
  MEMORY_LIST_LIMIT = 20;
18685
18987
  MEMORY_LIST_MAX_LIMIT = 50;
18686
18988
  MEMORY_OFF_MESSAGE = "Memory is off in this deployment (`memory.enabled` is not set), so there is nothing to list or forget.";
18687
- memoryId = z14.string().refine(
18989
+ memoryId = z15.string().refine(
18688
18990
  (id) => scopeKeyOfMemoryId(id) !== void 0,
18689
18991
  "expected a memory id like mem:<scope>:<n> (see `memory list`)"
18690
18992
  );
@@ -18692,21 +18994,21 @@ var init_memory2 = __esm({
18692
18994
  channelScopeKey = (channelId) => deriveScopeKey("channel", { channelId });
18693
18995
  isSharedScope = (key, keys) => key === keys.org || key.startsWith("repo:") || key.startsWith("channel:");
18694
18996
  LIST_SCOPES = ["me", "org", "repo", "channel", "all"];
18695
- memoryList = defineCommand10({
18997
+ memoryList = defineCommand11({
18696
18998
  id: "memory.list",
18697
18999
  enabledWhen: (caps) => caps.memory,
18698
19000
  args: [
18699
19001
  {
18700
19002
  name: "query",
18701
- schema: z14.string().optional(),
19003
+ schema: z15.string().optional(),
18702
19004
  describe: "words a record must mention (whole-token match)",
18703
19005
  rest: true
18704
19006
  }
18705
19007
  ],
18706
- options: z14.object({
18707
- scope: z14.enum(LIST_SCOPES).optional().describe("which scopes to list (default all: yours, this repo's, this channel's, and the shared org scope)"),
18708
- limit: z14.coerce.number().int().min(1).max(MEMORY_LIST_MAX_LIMIT).optional().describe(`records per scope (default ${MEMORY_LIST_LIMIT}, max ${MEMORY_LIST_MAX_LIMIT})`),
18709
- repo: z14.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug").optional().describe("the repo whose scope to list (default: the repo this thread is bound to)")
19008
+ options: z15.object({
19009
+ scope: z15.enum(LIST_SCOPES).optional().describe("which scopes to list (default all: yours, this repo's, this channel's, and the shared org scope)"),
19010
+ limit: z15.coerce.number().int().min(1).max(MEMORY_LIST_MAX_LIMIT).optional().describe(`records per scope (default ${MEMORY_LIST_LIMIT}, max ${MEMORY_LIST_MAX_LIMIT})`),
19011
+ repo: z15.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug").optional().describe("the repo whose scope to list (default: the repo this thread is bound to)")
18710
19012
  }),
18711
19013
  action: "memory:read",
18712
19014
  effect: "read",
@@ -18760,7 +19062,7 @@ var init_memory2 = __esm({
18760
19062
  };
18761
19063
  }
18762
19064
  });
18763
- memoryForget = defineCommand10({
19065
+ memoryForget = defineCommand11({
18764
19066
  id: "memory.forget",
18765
19067
  enabledWhen: (caps) => caps.memory,
18766
19068
  args: [{ name: "id", schema: memoryId, describe: "the record id (`mem:<scope>:<n>`, from `memory list`)" }],
@@ -18895,7 +19197,7 @@ var init_residentDiskBudget = __esm({
18895
19197
  });
18896
19198
 
18897
19199
  // ../../src/core/commands/repo.ts
18898
- import { z as z15 } from "zod";
19200
+ import { z as z16 } from "zod";
18899
19201
  async function adminOf(deps, span) {
18900
19202
  const api = await deps.repo.admin();
18901
19203
  if ("unavailable" in api) throw new CommandError("unavailable", api.unavailable);
@@ -19003,7 +19305,7 @@ ${clipOpOutput(text)}
19003
19305
  \`\`\`` : `${icon} ${str4(o.summary)}`;
19004
19306
  }
19005
19307
  function defineOp(op) {
19006
- return defineCommand11({
19308
+ return defineCommand12({
19007
19309
  id: `repo.${op}`,
19008
19310
  // The op needs a backend: the resident service, or the local workspace (`defaultOperations`).
19009
19311
  enabledWhen: (caps) => caps.residents || caps.execution === "local",
@@ -19061,7 +19363,7 @@ function defineOp(op) {
19061
19363
  function registerRepoCommands(registry) {
19062
19364
  for (const cmd of repoCommands) registry.register(cmd);
19063
19365
  }
19064
- var defineCommand11, DEFAULT_COMMANDS, DEFAULT_REF, SETTLE_POLL_MS, SETTLE_MAX_MS, repoSlug2, gitRef2, command, slugArg, n, obj, str4, repoList, repoOnboard, COMMAND_KEYS, dryRunOptions, repoOffboard, repoRebuild, repoReconfigure, NO_OPS_BACKEND_MESSAGE, repoTest, repoBuild, repoCommands;
19366
+ var defineCommand12, DEFAULT_COMMANDS, DEFAULT_REF, SETTLE_POLL_MS, SETTLE_MAX_MS, repoSlug2, gitRef2, command, slugArg, n, obj, str4, repoList, repoOnboard, COMMAND_KEYS, dryRunOptions, repoOffboard, repoRebuild, repoReconfigure, NO_OPS_BACKEND_MESSAGE, repoTest, repoBuild, repoCommands;
19065
19367
  var init_repo = __esm({
19066
19368
  "../../src/core/commands/repo.ts"() {
19067
19369
  "use strict";
@@ -19069,19 +19371,19 @@ var init_repo = __esm({
19069
19371
  init_residentAdmin();
19070
19372
  init_repoToolchain();
19071
19373
  init_residentDiskBudget();
19072
- defineCommand11 = commandDefiner();
19374
+ defineCommand12 = commandDefiner();
19073
19375
  DEFAULT_COMMANDS = NPM_FALLBACK_COMMANDS;
19074
19376
  DEFAULT_REF = "main";
19075
19377
  SETTLE_POLL_MS = 1e4;
19076
19378
  SETTLE_MAX_MS = 12 * 6e4;
19077
- repoSlug2 = z15.string().refine((s) => parseSlug(s) !== void 0, "expected a GitHub owner/name slug").transform((s) => parseSlug(s));
19078
- gitRef2 = z15.string().refine((s) => validRef(s) !== void 0, "expected a plausible git branch ref (e.g. main)");
19079
- command = z15.string().min(1);
19379
+ repoSlug2 = z16.string().refine((s) => parseSlug(s) !== void 0, "expected a GitHub owner/name slug").transform((s) => parseSlug(s));
19380
+ gitRef2 = z16.string().refine((s) => validRef(s) !== void 0, "expected a plausible git branch ref (e.g. main)");
19381
+ command = z16.string().min(1);
19080
19382
  slugArg = { name: "slug", schema: repoSlug2, describe: "GitHub owner/name of the repo" };
19081
19383
  n = (v) => String(typeof v === "number" ? v : v ?? "?");
19082
19384
  obj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
19083
19385
  str4 = (v) => typeof v === "string" ? v : String(v ?? "?");
19084
- repoList = defineCommand11({
19386
+ repoList = defineCommand12({
19085
19387
  id: "repo.list",
19086
19388
  enabledWhen: (caps) => caps.residents,
19087
19389
  action: "repo:read",
@@ -19098,11 +19400,11 @@ var init_repo = __esm({
19098
19400
  return res.data;
19099
19401
  }
19100
19402
  });
19101
- repoOnboard = defineCommand11({
19403
+ repoOnboard = defineCommand12({
19102
19404
  id: "repo.onboard",
19103
19405
  enabledWhen: (caps) => caps.residents,
19104
19406
  args: [slugArg],
19105
- options: z15.object({
19407
+ options: z16.object({
19106
19408
  ref: gitRef2.optional().describe(`default branch to keep warm (default ${DEFAULT_REF})`),
19107
19409
  test: command.optional().describe(
19108
19410
  "the repo's test command (default: detected from the repo root \u2014 `<pm> test`, or a no-op without a test script)"
@@ -19189,10 +19491,10 @@ var init_repo = __esm({
19189
19491
  settle: (output, { deps }) => settleProvisioning(deps, str4(output.slug))
19190
19492
  });
19191
19493
  COMMAND_KEYS = ["install", "build", "test"];
19192
- dryRunOptions = z15.object({
19494
+ dryRunOptions = z16.object({
19193
19495
  dryRun: flag.optional().describe("compute and return the itemized plan without executing")
19194
19496
  });
19195
- repoOffboard = defineCommand11({
19497
+ repoOffboard = defineCommand12({
19196
19498
  id: "repo.offboard",
19197
19499
  enabledWhen: (caps) => caps.residents,
19198
19500
  args: [slugArg],
@@ -19226,7 +19528,7 @@ var init_repo = __esm({
19226
19528
  return { ...r.data, slug: args.slug, dryRun };
19227
19529
  }
19228
19530
  });
19229
- repoRebuild = defineCommand11({
19531
+ repoRebuild = defineCommand12({
19230
19532
  id: "repo.rebuild",
19231
19533
  enabledWhen: (caps) => caps.residents,
19232
19534
  args: [slugArg],
@@ -19265,11 +19567,11 @@ var init_repo = __esm({
19265
19567
  return { ...r.data, slug: args.slug, dryRun };
19266
19568
  }
19267
19569
  });
19268
- repoReconfigure = defineCommand11({
19570
+ repoReconfigure = defineCommand12({
19269
19571
  id: "repo.reconfigure",
19270
19572
  enabledWhen: (caps) => caps.residents,
19271
19573
  args: [slugArg],
19272
- options: z15.object({
19574
+ options: z16.object({
19273
19575
  ref: gitRef2.optional().describe("new default branch"),
19274
19576
  test: command.optional().describe("new test command"),
19275
19577
  build: command.optional().describe("new build command"),
@@ -20239,7 +20541,7 @@ var init_runsService = __esm({
20239
20541
  });
20240
20542
 
20241
20543
  // ../../src/core/commands/review.ts
20242
- import { z as z16 } from "zod";
20544
+ import { z as z17 } from "zod";
20243
20545
  function artifactJson(a) {
20244
20546
  return {
20245
20547
  ...a.model !== void 0 ? { model: a.model } : {},
@@ -20308,7 +20610,7 @@ function refused2(err2) {
20308
20610
  function registerReviewCommands(registry) {
20309
20611
  for (const cmd of reviewCommands) registry.register(cmd);
20310
20612
  }
20311
- var defineCommand12, runId, ABRIDGE_OFF_MESSAGE, reviewAbridge, reviewCommands;
20613
+ var defineCommand13, runId, ABRIDGE_OFF_MESSAGE, reviewAbridge, reviewCommands;
20312
20614
  var init_review = __esm({
20313
20615
  "../../src/core/commands/review.ts"() {
20314
20616
  "use strict";
@@ -20317,17 +20619,17 @@ var init_review = __esm({
20317
20619
  init_reviewAbridge();
20318
20620
  init_runRecord();
20319
20621
  init_runsService();
20320
- defineCommand12 = commandDefiner();
20321
- runId = z16.string().regex(RUN_ID_PATTERN);
20622
+ defineCommand13 = commandDefiner();
20623
+ runId = z17.string().regex(RUN_ID_PATTERN);
20322
20624
  ABRIDGE_OFF_MESSAGE = "The abridged reading diff is off in this deployment: it needs the `meat` binary on the bot host, the Anthropic provider's credential, `review.readingDiff.provider` not `off`, and run history to store it.";
20323
- reviewAbridge = defineCommand12({
20625
+ reviewAbridge = defineCommand13({
20324
20626
  id: "review.abridge",
20325
20627
  // Hidden unless the abridging can happen here (the binary, the credential,
20326
20628
  // the switch) AND there is a record to append to.
20327
20629
  enabledWhen: (caps) => caps.runHistory && caps.readingDiffAbridge,
20328
20630
  args: [{ name: "id", schema: runId, describe: "run id of a finished PR review" }],
20329
- options: z16.object({
20330
- model: z16.string().min(1).optional().describe("meat's model (default review.readingDiff.meatModel, else claude-opus-5)"),
20631
+ options: z17.object({
20632
+ model: z17.string().min(1).optional().describe("meat's model (default review.readingDiff.meatModel, else claude-opus-5)"),
20331
20633
  force: flag.optional().describe("recompute even when an abridged diff is already stored, or retry a failed one"),
20332
20634
  wait: flag.optional().describe("block until the abridging finishes and answer the outcome instead of `running`")
20333
20635
  }),
@@ -20359,7 +20661,7 @@ var init_review = __esm({
20359
20661
  });
20360
20662
 
20361
20663
  // ../../src/core/commands/runs.ts
20362
- import { z as z17 } from "zod";
20664
+ import { z as z18 } from "zod";
20363
20665
  function unwrap2(res) {
20364
20666
  if (res.ok) return res.value;
20365
20667
  throw new CommandError(res.error, res.error === "not_found" ? "run not found" : "run already finished");
@@ -20400,7 +20702,7 @@ function wrapEvent(e) {
20400
20702
  function registerRunsCommands(registry) {
20401
20703
  for (const cmd of runsCommands) registry.register(cmd);
20402
20704
  }
20403
- var defineCommand13, runId2, positiveInt3, idArg, logDenied, asJson3, runsList, runsGet, runsEvents, runsFriction, runsStop, runsCommands;
20705
+ var defineCommand14, runId2, positiveInt3, idArg, logDenied, asJson3, runsList, runsGet, runsEvents, runsFriction, runsStop, runsCommands;
20404
20706
  var init_runs = __esm({
20405
20707
  "../../src/core/commands/runs.ts"() {
20406
20708
  "use strict";
@@ -20409,21 +20711,24 @@ var init_runs = __esm({
20409
20711
  init_commandRegistry();
20410
20712
  init_runRecord();
20411
20713
  init_runsService();
20412
- defineCommand13 = commandDefiner();
20413
- runId2 = z17.string().regex(RUN_ID_PATTERN);
20414
- positiveInt3 = z17.coerce.number().int().positive();
20714
+ defineCommand14 = commandDefiner();
20715
+ runId2 = z18.string().regex(RUN_ID_PATTERN);
20716
+ positiveInt3 = z18.coerce.number().int().positive();
20415
20717
  idArg = { name: "id", schema: runId2, describe: "run id" };
20416
20718
  logDenied = (entry) => console.log(JSON.stringify({ audit: "authz", ...entry }));
20417
20719
  asJson3 = (v) => v;
20418
- runsList = defineCommand13({
20720
+ runsList = defineCommand14({
20419
20721
  id: "runs.list",
20420
- options: z17.object({
20421
- status: z17.enum(["active", "finished", "all"]).default("active").describe("which runs: live (default), persisted, or both"),
20422
- agent: z17.string().min(1).optional().describe("only runs of this agent"),
20423
- channel: z17.string().min(1).optional().describe("only runs in this platform-namespaced channel (`slack:C0123`, `http:ops`)"),
20424
- sinceMs: z17.coerce.number().int().nonnegative().optional().describe("only runs started at or after this epoch ms"),
20722
+ options: z18.object({
20723
+ status: z18.enum(["active", "finished", "all"]).default("active").describe("which runs: live (default), persisted, or both"),
20724
+ agent: z18.string().min(1).optional().describe("only runs of this agent"),
20725
+ channel: z18.string().min(1).optional().describe("only runs in this platform-namespaced channel (`slack:C0123`, `http:ops`)"),
20726
+ thread: z18.string().min(1).optional().describe(
20727
+ "only runs in this thread, newest first (`slack:C0123:1712.34` \u2014 a thread's story, its sessions' runs)"
20728
+ ),
20729
+ sinceMs: z18.coerce.number().int().nonnegative().optional().describe("only runs started at or after this epoch ms"),
20425
20730
  limit: positiveInt3.max(RUN_LIST_MAX_LIMIT).optional().describe(`page size (max ${RUN_LIST_MAX_LIMIT})`),
20426
- before: z17.coerce.number().int().nonnegative().optional().describe("page cursor: runs finished before this epoch ms"),
20731
+ before: z18.coerce.number().int().nonnegative().optional().describe("page cursor: runs finished before this epoch ms"),
20427
20732
  beforeId: runId2.optional().describe("page cursor tie-breaker: the last id of the previous page")
20428
20733
  }),
20429
20734
  action: "runs:read",
@@ -20431,14 +20736,17 @@ var init_runs = __esm({
20431
20736
  describe: "List runs (live and persisted, newest first) \u2014 metadata only, never message text.",
20432
20737
  handler: async ({ options, caller, deps }) => {
20433
20738
  const visibleTo = predicateFor(caller.actor, "runs:read", "run");
20434
- return asJson3(await (await deps.runs()).listRuns({ ...options, visibleTo }));
20739
+ const { thread, ...rest } = options;
20740
+ return asJson3(
20741
+ await (await deps.runs()).listRuns({ ...rest, ...thread !== void 0 ? { threadKey: thread } : {}, visibleTo })
20742
+ );
20435
20743
  }
20436
20744
  });
20437
- runsGet = defineCommand13({
20745
+ runsGet = defineCommand14({
20438
20746
  id: "runs.get",
20439
20747
  args: [idArg],
20440
- options: z17.object({
20441
- include: z17.enum(["messages"]).optional().describe("add the run's events, free text wrapped as untrusted content")
20748
+ options: z18.object({
20749
+ include: z18.enum(["messages"]).optional().describe("add the run's events, free text wrapped as untrusted content")
20442
20750
  }),
20443
20751
  action: "runs:read",
20444
20752
  effect: "read",
@@ -20458,11 +20766,11 @@ var init_runs = __esm({
20458
20766
  return asJson3(view);
20459
20767
  }
20460
20768
  });
20461
- runsEvents = defineCommand13({
20769
+ runsEvents = defineCommand14({
20462
20770
  id: "runs.events",
20463
20771
  args: [idArg],
20464
- options: z17.object({
20465
- afterSeq: z17.coerce.number().int().nonnegative().optional().describe("events with seq greater than this"),
20772
+ options: z18.object({
20773
+ afterSeq: z18.coerce.number().int().nonnegative().optional().describe("events with seq greater than this"),
20466
20774
  limit: positiveInt3.max(MAX_EVENTS_PAGE).optional().describe(`page size (max ${MAX_EVENTS_PAGE})`)
20467
20775
  }),
20468
20776
  action: "runs:read",
@@ -20476,7 +20784,7 @@ var init_runs = __esm({
20476
20784
  return asJson3({ ...page2, events: page2.events.map(wrapEvent) });
20477
20785
  }
20478
20786
  });
20479
- runsFriction = defineCommand13({
20787
+ runsFriction = defineCommand14({
20480
20788
  id: "runs.friction",
20481
20789
  args: [idArg],
20482
20790
  action: "runs:read",
@@ -20489,10 +20797,10 @@ var init_runs = __esm({
20489
20797
  return asJson3(unwrap2(await runs.getRunFriction(args.id)));
20490
20798
  }
20491
20799
  });
20492
- runsStop = defineCommand13({
20800
+ runsStop = defineCommand14({
20493
20801
  id: "runs.stop",
20494
20802
  args: [idArg],
20495
- options: z17.object({ mode: z17.enum(["soft", "hard"]).describe("soft = finish the current step; hard = abort now") }),
20803
+ options: z18.object({ mode: z18.enum(["soft", "hard"]).describe("soft = finish the current step; hard = abort now") }),
20496
20804
  action: "runs:write",
20497
20805
  effect: "write",
20498
20806
  describe: "Request a live run to stop (`--mode soft` = finish the current step; `hard` = abort now). Records the caller as the actor.",
@@ -20516,16 +20824,16 @@ var init_runs = __esm({
20516
20824
  function registerScheduleCommands(registry) {
20517
20825
  for (const cmd of scheduleCommands) registry.register(cmd);
20518
20826
  }
20519
- var defineCommand14, ACTION_LABEL, utc, scheduleList, scheduleCommands;
20827
+ var defineCommand15, ACTION_LABEL, utc, scheduleList, scheduleCommands;
20520
20828
  var init_schedule = __esm({
20521
20829
  "../../src/core/commands/schedule.ts"() {
20522
20830
  "use strict";
20523
20831
  init_commandRegistry();
20524
20832
  init_schedules();
20525
- defineCommand14 = commandDefiner();
20833
+ defineCommand15 = commandDefiner();
20526
20834
  ACTION_LABEL = { healthz: "keep-alive", watchdog: "resident watchdog" };
20527
20835
  utc = (ms) => `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
20528
- scheduleList = defineCommand14({
20836
+ scheduleList = defineCommand15({
20529
20837
  id: "schedule.list",
20530
20838
  enabledWhen: (caps) => caps.schedules,
20531
20839
  action: "schedule:read",
@@ -20789,7 +21097,7 @@ var init_plan2 = __esm({
20789
21097
  });
20790
21098
 
20791
21099
  // ../../src/core/commands/setup.ts
20792
- import { z as z18 } from "zod";
21100
+ import { z as z19 } from "zod";
20793
21101
  async function gatherAnswers(o, setup) {
20794
21102
  const ask = setup.prompt;
20795
21103
  const given = (v) => v !== void 0;
@@ -20859,33 +21167,33 @@ function capabilityLine(c) {
20859
21167
  function registerSetupCommands(registry) {
20860
21168
  for (const cmd of setupCommands) registry.register(cmd);
20861
21169
  }
20862
- var defineCommand15, secret, digits, workerName, hostname2, httpUrl, account, initOptions2, DEFAULT_NAME, setupInit, ASK, setupCommands;
21170
+ var defineCommand16, secret, digits, workerName, hostname2, httpUrl, account, initOptions2, DEFAULT_NAME, setupInit, ASK, setupCommands;
20863
21171
  var init_setup = __esm({
20864
21172
  "../../src/core/commands/setup.ts"() {
20865
21173
  "use strict";
20866
21174
  init_plan2();
20867
21175
  init_commandRegistry();
20868
21176
  init_deploy();
20869
- defineCommand15 = commandDefiner();
20870
- secret = z18.string().min(1);
20871
- digits = z18.string().regex(/^\d+$/, "expected a number");
20872
- workerName = z18.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "expected a Worker name: lowercase letters, digits, hyphens");
20873
- hostname2 = z18.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/, "expected a bare DNS name (example.com)");
20874
- httpUrl = z18.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL");
20875
- account = z18.string().regex(/^[0-9a-f]{32}$/, "expected a Cloudflare account id: 32 hex characters");
20876
- initOptions2 = z18.object({
20877
- organization: z18.string().min(1).optional().describe("the GitHub organization (or user) this installation serves \u2014 config.yaml's `organization`"),
21177
+ defineCommand16 = commandDefiner();
21178
+ secret = z19.string().min(1);
21179
+ digits = z19.string().regex(/^\d+$/, "expected a number");
21180
+ workerName = z19.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "expected a Worker name: lowercase letters, digits, hyphens");
21181
+ hostname2 = z19.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/, "expected a bare DNS name (example.com)");
21182
+ httpUrl = z19.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL");
21183
+ account = z19.string().regex(/^[0-9a-f]{32}$/, "expected a Cloudflare account id: 32 hex characters");
21184
+ initOptions2 = z19.object({
21185
+ organization: z19.string().min(1).optional().describe("the GitHub organization (or user) this installation serves \u2014 config.yaml's `organization`"),
20878
21186
  anthropicKey: secret.optional().describe("an Anthropic API key \u2192 ANTHROPIC_API_KEY and the `anthropic` provider"),
20879
21187
  openaiCompatible: httpUrl.optional().describe(
20880
21188
  "an OpenAI-compatible endpoint's base URL (https://api.openai.com/v1, http://localhost:11434/v1) \u2192 the `openai` provider"
20881
21189
  ),
20882
- model: z18.string().min(1).optional().describe("with --openai-compatible: the model every agent runs on at that endpoint (openai/<model>)"),
21190
+ model: z19.string().min(1).optional().describe("with --openai-compatible: the model every agent runs on at that endpoint (openai/<model>)"),
20883
21191
  modelKey: secret.optional().describe("with --openai-compatible: the endpoint's key \u2192 OPENAI_API_KEY (a local endpoint needs none)"),
20884
21192
  slackAppToken: secret.optional().describe("the Slack app-level token (xapp-\u2026) \u2192 SLACK_APP_TOKEN"),
20885
21193
  slackBotToken: secret.optional().describe("the Slack bot token (xoxb-\u2026) \u2192 SLACK_BOT_TOKEN"),
20886
21194
  githubAppId: digits.optional().describe("the GitHub App's id \u2192 GITHUB_APP_ID"),
20887
21195
  githubInstallationId: digits.optional().describe("the GitHub App's installation id \u2192 GITHUB_APP_INSTALLATION_ID"),
20888
- githubPrivateKeyFile: z18.string().min(1).optional().describe("path to the GitHub App's private key (PEM) \u2192 GITHUB_APP_PRIVATE_KEY, one quoted line"),
21196
+ githubPrivateKeyFile: z19.string().min(1).optional().describe("path to the GitHub App's private key (PEM) \u2192 GITHUB_APP_PRIVATE_KEY, one quoted line"),
20889
21197
  cloudflare: account.optional().describe(
20890
21198
  "a Cloudflare account id: also write deploy/profile.json and render the Worker configs (needs --zone; from a checkout's root, or anywhere when this CLI is the published package)"
20891
21199
  ),
@@ -20895,7 +21203,7 @@ var init_setup = __esm({
20895
21203
  dryRun: flag.optional().describe("write nothing; print each file as it would be written, secrets masked")
20896
21204
  });
20897
21205
  DEFAULT_NAME = "switchboard";
20898
- setupInit = defineCommand15({
21206
+ setupInit = defineCommand16({
20899
21207
  id: "setup.init",
20900
21208
  options: initOptions2,
20901
21209
  action: "setup:write",
@@ -20995,13 +21303,13 @@ function renderStatus(output) {
20995
21303
  function registerStatusCommands(registry) {
20996
21304
  for (const cmd of statusCommands) registry.register(cmd);
20997
21305
  }
20998
- var defineCommand16, statusShow, statusCommands;
21306
+ var defineCommand17, statusShow, statusCommands;
20999
21307
  var init_status = __esm({
21000
21308
  "../../src/core/commands/status.ts"() {
21001
21309
  "use strict";
21002
21310
  init_commandRegistry();
21003
- defineCommand16 = commandDefiner();
21004
- statusShow = defineCommand16({
21311
+ defineCommand17 = commandDefiner();
21312
+ statusShow = defineCommand17({
21005
21313
  id: "status.show",
21006
21314
  action: "status:read",
21007
21315
  effect: "read",
@@ -21040,6 +21348,7 @@ function registerCoreCommands(registry) {
21040
21348
  registerSetupCommands(registry);
21041
21349
  registerContractCommands(registry);
21042
21350
  registerDeliveryCommands(registry);
21351
+ registerArtifactsCommands(registry);
21043
21352
  }
21044
21353
  function coreCommandGroups() {
21045
21354
  const registry = new CommandRegistry({ audit: () => {
@@ -21051,6 +21360,7 @@ var init_all = __esm({
21051
21360
  "../../src/core/commands/all.ts"() {
21052
21361
  "use strict";
21053
21362
  init_commandRegistry();
21363
+ init_artifacts();
21054
21364
  init_config4();
21055
21365
  init_contract2();
21056
21366
  init_delivery2();
@@ -21334,6 +21644,11 @@ function buildCoreCommands(config, store, wiring) {
21334
21644
  images: imagesHostIO({ log: (l) => console.log(l), now: wiring.now ?? systemClock }),
21335
21645
  cliVersion: cliVersionOnHost
21336
21646
  },
21647
+ // `artifacts lifecycle` / `artifacts check`: the config's section and the operator's Cloudflare calls.
21648
+ artifacts: {
21649
+ config: async () => (await cfg()).config.artifacts,
21650
+ bucket: artifactsBucketHost()
21651
+ },
21337
21652
  env: { bootstrap: bootstrapOnHost },
21338
21653
  status: { snapshot: () => (wiring.status ?? unstampedStatus)() },
21339
21654
  // `setup init` writes the operator's working directory — the one the CLI runs in.
@@ -21367,6 +21682,7 @@ var init_commandCatalogue = __esm({
21367
21682
  init_clock();
21368
21683
  init_run();
21369
21684
  init_imagesHost();
21685
+ init_artifactsBucket();
21370
21686
  init_secretsHost();
21371
21687
  init_host3();
21372
21688
  init_executor();
@@ -21585,6 +21901,12 @@ function runPageLink(id) {
21585
21901
  if (!base) return void 0;
21586
21902
  return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
21587
21903
  }
21904
+ function artifactLink(id, key, token2) {
21905
+ const page2 = runPageLink(id);
21906
+ if (!page2) return void 0;
21907
+ const path = key.split("/").map(encodeURIComponent).join("/");
21908
+ return `${page2}/artifacts/${path}${token2 ? `?t=${encodeURIComponent(token2)}` : ""}`;
21909
+ }
21588
21910
  function stripPlatformPrefix(id) {
21589
21911
  const i = id.indexOf(":");
21590
21912
  return i === -1 ? id : id.slice(i + 1);
@@ -21653,6 +21975,8 @@ function activityLine(e) {
21653
21975
  return "request received";
21654
21976
  case "context":
21655
21977
  return "context recorded";
21978
+ case "notes":
21979
+ return "\u{1F4DD} notes saved";
21656
21980
  case "answer":
21657
21981
  return "answer ready";
21658
21982
  case "run_meta":
@@ -24257,7 +24581,8 @@ async function readRequest(ctx) {
24257
24581
  }
24258
24582
  function resolveRun(deps, ctx) {
24259
24583
  const { msg, directives, history } = ctx;
24260
- const sticky = lastThreadDirectives(history);
24584
+ const fromTurns = lastThreadDirectives(history);
24585
+ const sticky = ctx.stickyAgent !== void 0 ? { ...fromTurns, agent: ctx.stickyAgent } : fromTurns;
24261
24586
  const resolved = deps.config.resolve({
24262
24587
  channelId: msg.channelId,
24263
24588
  userId: msg.userId,
@@ -25082,7 +25407,7 @@ var init_tracingExecutor = __esm({
25082
25407
  });
25083
25408
 
25084
25409
  // ../../src/core/prDescription.ts
25085
- import { z as z19 } from "zod";
25410
+ import { z as z20 } from "zod";
25086
25411
  function parsePrDescription(input) {
25087
25412
  return PrDescriptionSchema.parse(input);
25088
25413
  }
@@ -25370,34 +25695,34 @@ var init_prDescription = __esm({
25370
25695
  "../../src/core/prDescription.ts"() {
25371
25696
  "use strict";
25372
25697
  init_redact();
25373
- line2 = z19.string().trim().min(1).refine((s) => !/[\r\n]/.test(s), "must be a single line (no embedded newlines)");
25374
- prose = z19.string().trim().min(1);
25375
- TourAnchorSchema = z19.object({
25698
+ line2 = z20.string().trim().min(1).refine((s) => !/[\r\n]/.test(s), "must be a single line (no embedded newlines)");
25699
+ prose = z20.string().trim().min(1);
25700
+ TourAnchorSchema = z20.object({
25376
25701
  // Repo-relative: no leading `/`, no `..` SEGMENT (a filename like `a..b.ts` is fine).
25377
- path: z19.string().trim().min(1).refine((p) => !p.startsWith("/") && !p.split("/").includes(".."), "path must be repo-relative"),
25378
- from: z19.number().int().positive(),
25379
- to: z19.number().int().positive()
25702
+ path: z20.string().trim().min(1).refine((p) => !p.startsWith("/") && !p.split("/").includes(".."), "path must be repo-relative"),
25703
+ from: z20.number().int().positive(),
25704
+ to: z20.number().int().positive()
25380
25705
  }).refine((a) => a.to >= a.from, "to must be >= from");
25381
- TourStepSchema = z19.object({
25706
+ TourStepSchema = z20.object({
25382
25707
  title: line2,
25383
25708
  description: prose,
25384
25709
  lookFor: prose.optional(),
25385
25710
  anchor: TourAnchorSchema
25386
25711
  });
25387
- PrDescriptionSchema = z19.object({
25712
+ PrDescriptionSchema = z20.object({
25388
25713
  /** The PR title's single source. Metadata for the PR's own title field —
25389
25714
  * never rendered into the body (GitHub shows the title itself). */
25390
25715
  title: line2,
25391
25716
  tldr: prose,
25392
25717
  whatWhy: prose,
25393
- tour: z19.array(TourStepSchema).min(1),
25718
+ tour: z20.array(TourStepSchema).min(1),
25394
25719
  /** Every touched file the Tour steps did not cover, one line each. */
25395
- remaining: z19.array(z19.object({ path: line2, note: prose })),
25396
- decisions: z19.array(z19.object({ title: line2, rationale: prose })).min(1),
25720
+ remaining: z20.array(z20.object({ path: line2, note: prose })),
25721
+ decisions: z20.array(z20.object({ title: line2, rationale: prose })).min(1),
25397
25722
  risks: prose,
25398
- validation: z19.object({
25723
+ validation: z20.object({
25399
25724
  summary: prose.optional(),
25400
- criteria: z19.array(z19.object({ criterion: prose, proof: prose })).min(1)
25725
+ criteria: z20.array(z20.object({ criterion: prose, proof: prose })).min(1)
25401
25726
  })
25402
25727
  });
25403
25728
  REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
@@ -26169,10 +26494,11 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
26169
26494
  }
26170
26495
  ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
26171
26496
  if (!ctx.uploadTicket) {
26172
- const where = artifacts.runUrl ? ` \u2014 ${artifacts.runUrl}` : "";
26497
+ const link = artifacts.artifactUrl?.(key);
26498
+ const where = link ? ` \u2014 ${link}` : ` is on the run page as ${key}`;
26173
26499
  await artifacts.reply(`${lead}
26174
- \u{1F4CE} ${name} (${size} bytes) is on the run page${where}`);
26175
- 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`;
26500
+ \u{1F4CE} ${name} (${size} bytes)${where}`);
26501
+ return `attached ${name} (${size} bytes) to the run page as ${key}; this conversation's channel takes no file uploads, so the lead and the file's link were posted instead`;
26176
26502
  }
26177
26503
  const kept = "the file is kept on the run page";
26178
26504
  let ticket;
@@ -26638,8 +26964,136 @@ var init_runs2 = __esm({
26638
26964
  }
26639
26965
  });
26640
26966
 
26967
+ // ../../src/core/runLedger/sessionLog.ts
26968
+ function sessionKey(threadKey, agent) {
26969
+ return `${threadKey}:${agent ?? "-"}`;
26970
+ }
26971
+ function requestIndex(seed) {
26972
+ for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
26973
+ return 0;
26974
+ }
26975
+ var DEFAULT_SESSION_LOG_MAX_BYTES, NOTEPAD_MAX_BYTES, GAP_MARKER;
26976
+ var init_sessionLog = __esm({
26977
+ "../../src/core/runLedger/sessionLog.ts"() {
26978
+ "use strict";
26979
+ init_runRecord();
26980
+ init_runRecord();
26981
+ DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
26982
+ NOTEPAD_MAX_BYTES = 8192;
26983
+ GAP_MARKER = "[The log of this conversation ends short of what the previous run saw: its connection to the ledger broke, so its later turns and its final reply are not here.]";
26984
+ }
26985
+ });
26986
+
26987
+ // ../../src/tools/session.ts
26988
+ function sessionCapabilityFor(run2, ledger) {
26989
+ const session = run2?.session;
26990
+ if (!session) return void 0;
26991
+ return {
26992
+ session,
26993
+ search: (query, limit) => ledger.searchSession(session.key, query, limit),
26994
+ readTurn: async (idx) => (await ledger.readSession(session.key, idx, idx)).messages[0],
26995
+ readNotepad: () => ledger.readNotepad(session.key),
26996
+ writeNotepad: (text) => ledger.writeNotepad(session.key, text)
26997
+ };
26998
+ }
26999
+ async function mayRead(ctx) {
27000
+ if (!ctx.runs?.runId) return false;
27001
+ const res = await ctx.runs.service.getRun(ctx.runs.runId);
27002
+ return res.ok && authorize(ctx.runs.actor, "runs:read", runResource(res.value)).allow;
27003
+ }
27004
+ var UNAVAILABLE3, DEFAULT_HITS, MAX_HITS, SNIPPET_CHARS, snippetOf, recallTool, notesTool, SESSION_TOOLS;
27005
+ var init_session = __esm({
27006
+ "../../src/tools/session.ts"() {
27007
+ "use strict";
27008
+ init_authorize();
27009
+ init_runEvents();
27010
+ init_sessionLog();
27011
+ init_runRecord();
27012
+ init_runsService();
27013
+ UNAVAILABLE3 = "the session tools are not available in this context: this run has no session log.";
27014
+ DEFAULT_HITS = 5;
27015
+ MAX_HITS = 50;
27016
+ SNIPPET_CHARS = 300;
27017
+ snippetOf = (text) => {
27018
+ const line3 = text.replace(/\s+/g, " ").trim();
27019
+ return line3.length > SNIPPET_CHARS ? `${line3.slice(0, SNIPPET_CHARS - 1)}\u2026` : line3;
27020
+ };
27021
+ recallTool = {
27022
+ name: "recall",
27023
+ description: "Search this conversation's whole log \u2014 every turn of this thread on this agent, earlier runs' included and everything a compaction removed from your window \u2014 for words, and get the matching turns in relevance order with their turn numbers and a snippet; or pass `turn` to read one turn whole (a tool result, a command, a message). Use it when you need something said or seen earlier that is not in front of you: a failing test's name, a command's output, a decision. `limit` defaults to 5 (at most 50).",
27024
+ inputSchema: {
27025
+ type: "object",
27026
+ properties: {
27027
+ query: { type: "string", description: "Words to search for; matching is by word, in relevance order" },
27028
+ limit: { type: "integer", description: "How many turns to return (default 5, at most 50)" },
27029
+ turn: {
27030
+ type: "integer",
27031
+ description: "A turn number from an earlier recall: returns that turn whole instead of searching"
27032
+ }
27033
+ }
27034
+ },
27035
+ sideEffectFree: true,
27036
+ async run(input, ctx) {
27037
+ if (!ctx.session) return UNAVAILABLE3;
27038
+ const turn = typeof input.turn === "number" ? Math.trunc(input.turn) : void 0;
27039
+ const query = typeof input.query === "string" ? input.query.trim() : "";
27040
+ if (turn === void 0 && query.length === 0) return "recall needs a query or a turn number.";
27041
+ if (!await mayRead(ctx)) return JSON.stringify(turn !== void 0 ? { turn, content: null } : { hits: [] });
27042
+ if (turn !== void 0) {
27043
+ const message2 = turn >= 0 ? await ctx.session.readTurn(turn) : void 0;
27044
+ if (!message2) return `no turn ${turn} in this session's log.`;
27045
+ return JSON.stringify({ turn, role: message2.role, content: message2.content });
27046
+ }
27047
+ const limit = Math.min(
27048
+ MAX_HITS,
27049
+ Math.max(1, typeof input.limit === "number" ? Math.trunc(input.limit) : DEFAULT_HITS)
27050
+ );
27051
+ const { hits, gaps } = await ctx.session.search(query, limit);
27052
+ const notes = [];
27053
+ if (hits.length === 0)
27054
+ notes.push("no turn of this session's log matches; your notes (notes {}) may hold what you look for");
27055
+ if (ctx.session.session.seedFrom === 0) notes.push("this is the first run of its session: the log begins with it");
27056
+ return JSON.stringify({
27057
+ hits: hits.map((h) => ({
27058
+ turn: h.idx,
27059
+ ...h.role !== void 0 ? { role: h.role } : {},
27060
+ snippet: snippetOf(h.text)
27061
+ })),
27062
+ ...gaps.length ? {
27063
+ gaps: `the log has a gap at ${gaps.map((g) => `turn ${g}`).join(", ")}: a run before it detached from the ledger, so turns it saw after that point are not here`
27064
+ } : {},
27065
+ ...notes.length ? { note: notes.join("; ") } : {}
27066
+ });
27067
+ }
27068
+ };
27069
+ notesTool = {
27070
+ name: "notes",
27071
+ description: "Your notes for this thread \u2014 one document, replaced whole on every write, at most 8 KiB. Keep in it what must survive a compaction and reach the next run in this thread: decisions and their reasons, the names of things you found (files, tests, commits), what is not yet proven. They ride your system prompt at the start of every later run and reach you again right after a compaction. Call with `text` to write; with nothing to read.",
27072
+ inputSchema: {
27073
+ type: "object",
27074
+ properties: { text: { type: "string", description: "The whole notepad as it should read from now on" } }
27075
+ },
27076
+ async run(input, ctx) {
27077
+ if (!ctx.session) return UNAVAILABLE3;
27078
+ if (typeof input.text !== "string") {
27079
+ const current = await ctx.session.readNotepad();
27080
+ return current && current.text.length > 0 ? current.text : "(your notes for this thread are empty)";
27081
+ }
27082
+ const bytes = utf8ByteLength(input.text);
27083
+ if (bytes > NOTEPAD_MAX_BYTES)
27084
+ return `your notes are ${bytes} bytes; the notepad holds at most ${NOTEPAD_MAX_BYTES} \u2014 shorten them and write again.`;
27085
+ const written = await ctx.session.writeNotepad(input.text);
27086
+ if (!written.ok) return `notes not saved (${written.reason}): another generation drives this run now.`;
27087
+ ctx.publish?.({ type: "notes", text: redactSecrets(input.text) });
27088
+ return `notes saved (${bytes} bytes); they ride your system prompt at the next run in this thread and reach you again after a compaction.`;
27089
+ }
27090
+ };
27091
+ SESSION_TOOLS = [recallTool, notesTool];
27092
+ }
27093
+ });
27094
+
26641
27095
  // ../../src/tools/workspace.ts
26642
- import { z as z20 } from "zod";
27096
+ import { z as z21 } from "zod";
26643
27097
  var bashTool, readFileTool, writeFileTool, NAME_STATUS_MARK, NAME_STATUS_MARK_LINE, OUTPUT_CUT_RE, diffDigestTool, submitVerdictTool, submitDispositionsTool, submitPrDescriptionTool, handoffEntry, submitHandoffTool, updateStatusTool, TOOLSETS;
26644
27098
  var init_workspace = __esm({
26645
27099
  "../../src/tools/workspace.ts"() {
@@ -26656,6 +27110,7 @@ var init_workspace = __esm({
26656
27110
  init_skills();
26657
27111
  init_attach();
26658
27112
  init_runs2();
27113
+ init_session();
26659
27114
  bashTool = {
26660
27115
  name: "bash",
26661
27116
  description: "Run a bash command in the workspace directory. Use for git, gh, tests, builds, and inspecting files. Commands time out after 5 minutes (300000 ms) by default; pass timeoutMs when a command legitimately needs longer \u2014 a full test suite, a large build \u2014 up to the 1200000 ms (20 minute) maximum; a command is also clipped to the run's own remaining time, so it can never outlive the run. Output is truncated at 30k characters.",
@@ -26938,7 +27393,7 @@ ${raw.trim()}`;
26938
27393
  try {
26939
27394
  desc = parsePrDescription(input);
26940
27395
  } catch (err2) {
26941
- const detail = err2 instanceof z20.ZodError ? err2.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ") : String(err2);
27396
+ const detail = err2 instanceof z21.ZodError ? err2.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ") : String(err2);
26942
27397
  return `error: invalid PR description \u2014 ${detail}`;
26943
27398
  }
26944
27399
  ctx.onPrDescription?.(desc);
@@ -27025,7 +27480,8 @@ ${raw.trim()}`;
27025
27480
  listSkillsTool,
27026
27481
  useSkillTool,
27027
27482
  ...GITHUB_READ_TOOLS,
27028
- ...GITHUB_ISSUE_WRITE_TOOLS
27483
+ ...GITHUB_ISSUE_WRITE_TOOLS,
27484
+ ...SESSION_TOOLS
27029
27485
  ],
27030
27486
  readonly: [
27031
27487
  bashTool,
@@ -27036,7 +27492,8 @@ ${raw.trim()}`;
27036
27492
  diffDigestTool,
27037
27493
  listSkillsTool,
27038
27494
  useSkillTool,
27039
- ...GITHUB_READ_TOOLS
27495
+ ...GITHUB_READ_TOOLS,
27496
+ ...SESSION_TOOLS
27040
27497
  ],
27041
27498
  web: [webFetchTool, webSearchTool, updateStatusTool, ...GITHUB_READ_TOOLS],
27042
27499
  /** The general agent: no workspace, no shell — GitHub reads + issue writes
@@ -27056,7 +27513,8 @@ ${raw.trim()}`;
27056
27513
  webSearchTool,
27057
27514
  listSkillsTool,
27058
27515
  useSkillTool,
27059
- ...GITHUB_READ_TOOLS
27516
+ ...GITHUB_READ_TOOLS,
27517
+ ...SESSION_TOOLS
27060
27518
  ],
27061
27519
  /** The conductor (docs/reference/specs/agent-conductor.md): the five run
27062
27520
  * tools — the only toolset that holds them, so no other preset can start,
@@ -27934,7 +28392,7 @@ Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the wor
27934
28392
  const agentSystem = (head) => {
27935
28393
  return [residentSystem ?? agent.system, targetBlock(head), input.contract, blocks.skills, blocks.mcp].filter((b) => Boolean(b)).join("\n\n");
27936
28394
  };
27937
- return (head) => [blocks.memory, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
28395
+ return (head) => [blocks.memory, blocks.notes, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
27938
28396
  }
27939
28397
  async function settleReviewedHead(input) {
27940
28398
  return input.span ? input.span.span("run.settle_reviewed_head", (span) => settle(input, span)) : settle(input, void 0);
@@ -28777,13 +29235,13 @@ var init_frontmatter = __esm({
28777
29235
 
28778
29236
  // ../../src/skills/stores.ts
28779
29237
  import { existsSync as existsSync11, readFileSync as readFileSync13, readdirSync as readdirSync2 } from "node:fs";
28780
- import { join as join11 } from "node:path";
29238
+ import { join as join10 } from "node:path";
28781
29239
  function loadBundledSkills(dir) {
28782
29240
  if (!existsSync11(dir)) return [];
28783
29241
  const skills = [];
28784
29242
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
28785
29243
  if (!entry.isDirectory()) continue;
28786
- const file = join11(dir, entry.name, "SKILL.md");
29244
+ const file = join10(dir, entry.name, "SKILL.md");
28787
29245
  if (!existsSync11(file)) continue;
28788
29246
  try {
28789
29247
  skills.push(parseSkillMarkdown(readFileSync13(file, "utf8")));
@@ -29340,6 +29798,20 @@ function mintRunBearer(deps, ctx) {
29340
29798
  publish: (event) => registry.publish(runId3, event)
29341
29799
  });
29342
29800
  }
29801
+ function sessionNotesBlock(session) {
29802
+ const parts = [];
29803
+ const notepad = session.notepad?.trim();
29804
+ const summary = session.summary?.trim();
29805
+ if (notepad)
29806
+ parts.push(
29807
+ "YOUR NOTES FOR THIS THREAD (kept with the `notes` tool \u2014 the one thing sure to survive a compaction and reach the next run here; replace them whole when they change):\n" + notepad
29808
+ );
29809
+ if (summary)
29810
+ parts.push(
29811
+ "SUMMARY OF THE EARLIER CONVERSATION (written when the context was compacted; every earlier turn is still reachable with `recall`):\n" + summary
29812
+ );
29813
+ return parts.length ? parts.join("\n\n") : void 0;
29814
+ }
29343
29815
  async function composePrompt(deps, ctx) {
29344
29816
  const {
29345
29817
  msg,
@@ -29402,6 +29874,7 @@ async function composePrompt(deps, ctx) {
29402
29874
  );
29403
29875
  const instructionsBlock = customInstructionsBlock(scopes);
29404
29876
  const memoryBlock = await root.span("dispatch.compose", () => memoryBlockP);
29877
+ const notesBlock = ctx.session ? sessionNotesBlock(ctx.session) : void 0;
29405
29878
  const composeSystem = makeSystemComposer({
29406
29879
  agent,
29407
29880
  resident: resident === true,
@@ -29417,6 +29890,7 @@ async function composePrompt(deps, ctx) {
29417
29890
  ...ctx.contract !== void 0 ? { contract: ctx.contract } : {},
29418
29891
  blocks: {
29419
29892
  memory: memoryBlock,
29893
+ notes: notesBlock,
29420
29894
  config: configBlock,
29421
29895
  about: aboutBlock,
29422
29896
  instructions: instructionsBlock,
@@ -29483,7 +29957,8 @@ async function claimRun(deps, ctx) {
29483
29957
  parentRunId,
29484
29958
  coordinator,
29485
29959
  seed,
29486
- route: route3
29960
+ route: route3,
29961
+ seedLog
29487
29962
  } = ctx;
29488
29963
  const { resident, binding } = selection;
29489
29964
  let ledgerRun = ctx.ledgerRun;
@@ -29528,8 +30003,9 @@ async function claimRun(deps, ctx) {
29528
30003
  ({ name, description, inputSchema }) => ({ name, description, inputSchema })
29529
30004
  ),
29530
30005
  // The seed carries the EFFECTIVE budget, so a resume runs on what
29531
- // this run was admitted with, not on the preset's own number.
29532
- seed: { messages, budgetMs: profile.minutes * 6e4 },
30006
+ // this run was admitted with, not on the preset's own number — and,
30007
+ // for a seed read from the log, the rows it reuses (session-log item 9).
30008
+ seed: { messages, budgetMs: profile.minutes * 6e4, ...seedLog ? { log: seedLog } : {} },
29533
30009
  // A stop asked of another container (`/runs/stop` there) reaches this
29534
30010
  // run through its heartbeat and is honored like a local one; a fence
29535
30011
  // (another generation took the run) is a hard stop — nothing more may
@@ -29599,7 +30075,7 @@ function writeFileScripts(path, content) {
29599
30075
  chunks.push(content.slice(i, i + WRITE_CHUNK_CHARS));
29600
30076
  const dir = path.slice(0, path.lastIndexOf("/")) || ".";
29601
30077
  return chunks.map(
29602
- (chunk, i) => i === 0 ? `mkdir -p ${shellQuote(dir)} && umask 077 && printf '%s' ${shellQuote(chunk)} > ${shellQuote(path)}` : `printf '%s' ${shellQuote(chunk)} >> ${shellQuote(path)}`
30078
+ (chunk, i) => i === 0 ? `umask 077 && mkdir -p ${shellQuote(dir)} && printf '%s' ${shellQuote(chunk)} > ${shellQuote(path)}` : `printf '%s' ${shellQuote(chunk)} >> ${shellQuote(path)}`
29603
30079
  );
29604
30080
  }
29605
30081
  function startScript(start3) {
@@ -29610,7 +30086,7 @@ function startScript(start3) {
29610
30086
  `pi ${args.map(shellQuote).join(" ")} <&3 2>>${shellQuote(paths.errLog)} | ${LOG_FILTER} >> ${shellQuote(paths.log)}`
29611
30087
  ].join("; ");
29612
30088
  return [
29613
- `mkdir -p ${shellQuote(paths.dir)} ${shellQuote(paths.sessionDir)} ${shellQuote(paths.commandDir)}`,
30089
+ `(umask 077 && mkdir -p ${shellQuote(paths.dir)} ${shellQuote(paths.sessionDir)} ${shellQuote(paths.commandDir)})`,
29614
30090
  `rm -f ${shellQuote(paths.fifo)}`,
29615
30091
  `mkfifo -m 600 ${shellQuote(paths.fifo)}`,
29616
30092
  `: > ${shellQuote(paths.log)}`,
@@ -29638,6 +30114,9 @@ function killScript(pid) {
29638
30114
  function tailScript(path, bytes) {
29639
30115
  return `tail -c ${bytes} ${shellQuote(path)} 2>/dev/null || true`;
29640
30116
  }
30117
+ function removeScript(dir) {
30118
+ return `rm -rf ${shellQuote(dir)}`;
30119
+ }
29641
30120
  function stdoutOf(operation, out) {
29642
30121
  const exit = parseExitPrefix(out);
29643
30122
  if (exit.failed) throw new PiContainerError(operation, out);
@@ -29706,6 +30185,9 @@ var init_container = __esm({
29706
30185
  return "";
29707
30186
  }
29708
30187
  }
30188
+ async remove(paths) {
30189
+ stdoutOf("remove", await this.exec(removeScript(paths.dir)));
30190
+ }
29709
30191
  exec(script, env) {
29710
30192
  return this.executor.exec(script, { timeoutMs: OP_TIMEOUT_MS, ...env ? { env } : {} });
29711
30193
  }
@@ -30382,9 +30864,8 @@ function piBuiltinToolsFor(identity2) {
30382
30864
  function piThinkingLevel(effort2) {
30383
30865
  return effort2;
30384
30866
  }
30385
- function piRunPaths(runId3, user) {
30386
- const root = user ? `/tmp/switchboard-pi-${user}` : "/tmp/switchboard-pi";
30387
- const dir = `${root}/${runId3}`;
30867
+ function piRunPaths(runId3) {
30868
+ const dir = `/tmp/switchboard-pi-${runId3}`;
30388
30869
  return {
30389
30870
  dir,
30390
30871
  agentDir: `${dir}/agent`,
@@ -30587,6 +31068,14 @@ var init_transport = __esm({
30587
31068
  function relayedTools(tools) {
30588
31069
  return tools.filter((t) => !NATIVE_WORKSPACE_TOOLS.has(t.name));
30589
31070
  }
31071
+ function compactionSteer(notepad, opts = {}) {
31072
+ const notes = notepad?.trim();
31073
+ const reach = "Your context was just compacted: the summary now standing in for the earlier turns is pi's, and every one of those turns is still reachable with the `recall` tool (search by words, or read a turn by its number).\n\n";
31074
+ if (opts.unavailable)
31075
+ return reach + "Your notes for this thread could not be read just now; `notes {}` reads them on demand, and `notes { text }` still writes them.";
31076
+ return reach + (notes ? `YOUR NOTES FOR THIS THREAD, as you last wrote them with \`notes\`:
31077
+ ${notes}` : "Your notes for this thread are empty. Write them with the `notes` tool when you have decisions, the names of things you found and what is not yet proven worth keeping \u2014 they survive every compaction and reach the next run in this thread.");
31078
+ }
30590
31079
  function splitSeed(seed) {
30591
31080
  for (let i = seed.length - 1; i >= 0; i--) {
30592
31081
  if (seed[i].role === "user") return { session: seed.slice(0, i), prompt: seed[i] };
@@ -30631,7 +31120,7 @@ async function runPiHarness(deps, run2) {
30631
31120
  emit({ type: "run_note", kind, summary, ...mode ? { mode } : {} });
30632
31121
  };
30633
31122
  const bridge = new PiBridge({ emit, onProgress: run2.onProgress, agentSpan, clock: clock3 });
30634
- const paths = piRunPaths(run2.runId, run2.user);
31123
+ const paths = piRunPaths(run2.runId);
30635
31124
  const remainingMs = run2.resume?.remainingMs ?? run2.agent.maxMinutes * 6e4;
30636
31125
  const deadline = now() + remainingMs;
30637
31126
  const warnAt = deadline - Math.min(3 * 6e4, run2.agent.maxMinutes * 15e3);
@@ -30865,7 +31354,23 @@ async function runPiHarness(deps, run2) {
30865
31354
  }
30866
31355
  }
30867
31356
  if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
30868
- if (obs.compaction) await mirror.onCompaction(obs.compaction, bridge.turns);
31357
+ if (obs.compaction) {
31358
+ await mirror.onCompaction(obs.compaction, bridge.turns);
31359
+ if (!writeUp && !hardStopped && !catchingUp) {
31360
+ let notepad;
31361
+ let unavailable = false;
31362
+ try {
31363
+ notepad = run2.notepad ? (await run2.notepad())?.text : void 0;
31364
+ } catch (err2) {
31365
+ unavailable = true;
31366
+ note(
31367
+ "harness_error",
31368
+ `the notepad could not be read for the compaction steer (${err2 instanceof Error ? err2.message : String(err2)}); steered without it`
31369
+ );
31370
+ }
31371
+ transport.send({ type: "steer", message: compactionSteer(notepad, { unavailable }) });
31372
+ }
31373
+ }
30869
31374
  if (obs.providerError !== void 0) {
30870
31375
  if (catchingUp)
30871
31376
  note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
@@ -30903,6 +31408,8 @@ async function runPiHarness(deps, run2) {
30903
31408
  forget();
30904
31409
  if (pid !== void 0) await container.kill(pid).catch(() => {
30905
31410
  });
31411
+ await container.remove(paths).catch(() => {
31412
+ });
30906
31413
  agentSpan?.end(hardStopped || bypass ? "error" : "ok");
30907
31414
  }
30908
31415
  }
@@ -31451,6 +31958,7 @@ async function runLoop2(deps, ctx) {
31451
31958
  runs,
31452
31959
  steer,
31453
31960
  wait,
31961
+ session,
31454
31962
  parentRunId,
31455
31963
  coordinator,
31456
31964
  seed
@@ -31577,12 +32085,11 @@ async function runLoop2(deps, ctx) {
31577
32085
  const uploadTicket = io.uploadTicket?.bind(io);
31578
32086
  const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
31579
32087
  let artifactSeq = 0;
31580
- const runUrl = runPageLink(run2.id);
31581
32088
  const artifacts = deps.artifacts ? {
31582
32089
  store: deps.artifacts,
31583
32090
  runId: run2.id,
31584
32091
  nextSeq: () => ++artifactSeq,
31585
- ...runUrl ? { runUrl } : {},
32092
+ artifactUrl: (key) => artifactLink(run2.id, key, run2.token),
31586
32093
  reply: (text) => io.reply(text)
31587
32094
  } : void 0;
31588
32095
  const toolContext = {
@@ -31599,6 +32106,7 @@ async function runLoop2(deps, ctx) {
31599
32106
  ...runs ? { runs } : {},
31600
32107
  ...steer ? { steer } : {},
31601
32108
  ...wait ? { wait } : {},
32109
+ ...session ? { session } : {},
31602
32110
  onVerdict,
31603
32111
  onDigest,
31604
32112
  onPrDescription,
@@ -31646,15 +32154,12 @@ async function runLoop2(deps, ctx) {
31646
32154
  messages,
31647
32155
  tools: relayedTools(mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools)),
31648
32156
  toolContext,
32157
+ ...session ? { notepad: () => session.readNotepad() } : {},
31649
32158
  rules: {
31650
32159
  checkout: binding?.workspace ?? "/workspace",
31651
32160
  ...ownBranch !== void 0 ? { branch: ownBranch } : {},
31652
32161
  protectedBranches
31653
32162
  },
31654
- // The resident runs this thread's commands as its pool user: pi's
31655
- // files go under that user's own root, never one another thread's
31656
- // user created (harness-pi.md item 4).
31657
- ...binding?.user ? { user: binding.user } : {},
31658
32163
  ...round2.selection.backend ? { backend: round2.selection.backend } : {},
31659
32164
  span: root,
31660
32165
  control: run2.control,
@@ -33326,15 +33831,7 @@ var init_settle = __esm({
33326
33831
  });
33327
33832
 
33328
33833
  // ../../src/core/dispatch/lineage.ts
33329
- async function threadLineage(service, threadKey) {
33330
- let newest;
33331
- try {
33332
- const page2 = await service.listRuns({ status: "all", visibleTo: { kind: "all" }, threadKey, limit: 1 });
33333
- newest = page2.runs[0];
33334
- } catch (err2) {
33335
- console.warn(`[lineage] ${threadKey}: thread read failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`);
33336
- return void 0;
33337
- }
33834
+ function lineageOf(newest) {
33338
33835
  if (newest === void 0 || newest.parentRunId === void 0) return void 0;
33339
33836
  return {
33340
33837
  parentRunId: newest.parentRunId,
@@ -33391,6 +33888,150 @@ var init_lineage = __esm({
33391
33888
  }
33392
33889
  });
33393
33890
 
33891
+ // ../../src/core/dispatch/thread.ts
33892
+ async function readThread(service, threadKey) {
33893
+ try {
33894
+ const page2 = await service.listRuns({
33895
+ status: "all",
33896
+ visibleTo: { kind: "all" },
33897
+ threadKey,
33898
+ limit: THREAD_READ_LIMIT
33899
+ });
33900
+ return page2.runs;
33901
+ } catch (err2) {
33902
+ console.warn(`[thread] ${threadKey}: thread read failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`);
33903
+ return void 0;
33904
+ }
33905
+ }
33906
+ function continuable(run2) {
33907
+ return run2.finished && run2.session !== void 0 && run2.agent !== void 0;
33908
+ }
33909
+ function stickyAgentOf(runs, onPi) {
33910
+ const newest = runs[0];
33911
+ if (newest === void 0 || !continuable(newest) || !onPi(newest.agent)) return void 0;
33912
+ return newest.agent;
33913
+ }
33914
+ function previousRunOf(runs, agent) {
33915
+ const run2 = runs.find((r) => r.agent === agent && continuable(r));
33916
+ if (run2 === void 0) return void 0;
33917
+ return {
33918
+ ...run2.finishedAt !== void 0 ? { finishedAt: run2.finishedAt } : {},
33919
+ broken: run2.session.range === "broken"
33920
+ };
33921
+ }
33922
+ var THREAD_READ_LIMIT;
33923
+ var init_thread = __esm({
33924
+ "../../src/core/dispatch/thread.ts"() {
33925
+ "use strict";
33926
+ THREAD_READ_LIMIT = 8;
33927
+ }
33928
+ });
33929
+
33930
+ // ../../src/core/dispatch/seed.ts
33931
+ function sessionSeed(input) {
33932
+ const { tail, previous, history, request } = input;
33933
+ const { from, transcript } = tail;
33934
+ if (from === 0 && transcript.turns === 0) return void 0;
33935
+ const notes = [];
33936
+ const messages = [];
33937
+ const compactions = transcript.compactions;
33938
+ const afterCompaction = compactions.reduce((n2, c) => Math.max(n2, c.before), 0);
33939
+ let start3 = -1;
33940
+ for (let i = afterCompaction; i < transcript.messages.length; i++) {
33941
+ const m = transcript.messages[i];
33942
+ if (m.role === "user" && m.content.some((p) => p.type === "text")) {
33943
+ start3 = i;
33944
+ break;
33945
+ }
33946
+ }
33947
+ const summary = compactions.length ? compactions[compactions.length - 1].entry.summary : void 0;
33948
+ const logFrom = start3 < 0 ? from + transcript.turns : from + start3 + compactions.length;
33949
+ const kept = start3 < 0 ? [] : transcript.messages.slice(start3).map(withoutThinking);
33950
+ if (kept.length === 0) {
33951
+ notes.push(
33952
+ transcript.turns === 0 ? "session seed: the log's newest turn alone is over the seed budget \u2014 the run starts from the request" : "session seed: no user text turn in the tail within the seed budget \u2014 the run starts from the request"
33953
+ );
33954
+ }
33955
+ messages.push(...kept);
33956
+ const last = kept[kept.length - 1];
33957
+ if (last?.role === "assistant") {
33958
+ const calls = last.content.filter((p) => p.type === "tool_use");
33959
+ if (calls.length > 0) {
33960
+ messages.push({
33961
+ role: "user",
33962
+ content: calls.map((c) => ({
33963
+ type: "tool_result",
33964
+ toolUseId: c.id,
33965
+ content: settledResult(c.name),
33966
+ isError: true
33967
+ }))
33968
+ });
33969
+ }
33970
+ }
33971
+ if (previous?.broken) messages.push({ role: "user", content: [{ type: "text", text: GAP_MARKER }] });
33972
+ if (previous?.finishedAt === void 0) {
33973
+ notes.push(
33974
+ "session seed: the previous run's end is unknown, so the lines written since could not be told apart \u2014 only the request follows the tail"
33975
+ );
33976
+ } else {
33977
+ const end = previous.finishedAt;
33978
+ for (const h of history) {
33979
+ if (h.role !== "user" || h.at === void 0 || h.at <= end) continue;
33980
+ messages.push({ role: "user", content: turnContent(h.text, h.images, h.documents) });
33981
+ }
33982
+ }
33983
+ messages.push({ role: "user", content: turnContent(request.text, request.images, request.documents) });
33984
+ return {
33985
+ messages,
33986
+ log: { from: logFrom, turns: kept.length },
33987
+ ...summary !== void 0 ? { summary } : {},
33988
+ notes
33989
+ };
33990
+ }
33991
+ async function sessionSeedFor(input) {
33992
+ const key = sessionKey(input.threadKey, input.agent);
33993
+ let tail;
33994
+ try {
33995
+ tail = await input.ledger.readSessionTail(key, SEED_BUDGET_BYTES);
33996
+ } catch (err2) {
33997
+ const why = err2 instanceof Error ? err2.message : String(err2);
33998
+ return { notes: [`session seed: the log ${key} could not be read (${why}) \u2014 the run seeds from the channel`] };
33999
+ }
34000
+ const seed = sessionSeed({
34001
+ tail,
34002
+ previous: previousRunOf(input.thread, input.agent),
34003
+ history: input.history,
34004
+ request: input.request
34005
+ });
34006
+ if (!seed) return { notes: [] };
34007
+ try {
34008
+ const notepad = await input.ledger.readNotepad(key);
34009
+ if (notepad && notepad.text.trim().length > 0) seed.notepad = notepad.text;
34010
+ } catch (err2) {
34011
+ const why = err2 instanceof Error ? err2.message : String(err2);
34012
+ seed.notes.push(`session seed: the notepad of ${key} could not be read (${why}) \u2014 the run starts without it`);
34013
+ }
34014
+ return { seed, notes: seed.notes };
34015
+ }
34016
+ function withoutThinking(message2) {
34017
+ if (message2.role !== "assistant") return message2;
34018
+ const content = message2.content.filter((p) => p.type !== "thinking" && p.type !== "redacted_thinking");
34019
+ if (content.length === message2.content.length) return message2;
34020
+ return { ...message2, content: content.length ? content : [{ type: "text", text: "(reasoning omitted)" }] };
34021
+ }
34022
+ var SEED_BUDGET_TOKENS, SEED_BUDGET_BYTES, settledResult;
34023
+ var init_seed = __esm({
34024
+ "../../src/core/dispatch/seed.ts"() {
34025
+ "use strict";
34026
+ init_sessionLog();
34027
+ init_messages();
34028
+ init_thread();
34029
+ SEED_BUDGET_TOKENS = 6e4;
34030
+ SEED_BUDGET_BYTES = SEED_BUDGET_TOKENS * 4;
34031
+ settledResult = (toolName) => `The run that made this call ended before this ${toolName} call's result was recorded; its effects are unknown \u2014 re-check them before repeating it.`;
34032
+ }
34033
+ });
34034
+
33394
34035
  // ../../src/core/runEnding.ts
33395
34036
  function createRunEnding(deps) {
33396
34037
  const log = deps.log ?? console.error;
@@ -33528,7 +34169,8 @@ async function dispatch(deps, msg, io, opts = {}) {
33528
34169
  const { directives, history } = await readRequest({ msg, io, root });
33529
34170
  if (await answerOperation(deps, { msg, io, ending, trace, directives, history })) return ended;
33530
34171
  const runsService = deps.runs ?? createRunsService({ registry, store: deps.runStore });
33531
- const lineage = opts.parent || opts.coordinator || resume || restart ? void 0 : await threadLineage(runsService, msg.threadKey);
34172
+ const thread = opts.parent || opts.coordinator || resume || restart || history.length === 0 ? void 0 : await readThread(runsService, msg.threadKey);
34173
+ const lineage = lineageOf(thread?.[0]);
33532
34174
  const parent = opts.parent ?? (lineage ? lineageParent(lineage) : void 0);
33533
34175
  const tellLineage = async (heard) => {
33534
34176
  if (!lineage) return;
@@ -33539,7 +34181,18 @@ async function dispatch(deps, msg, io, opts = {}) {
33539
34181
  heard
33540
34182
  );
33541
34183
  };
33542
- const settled = resolveRun(deps, { msg, directives, history });
34184
+ const harnessBlock = deps.config.config.harness;
34185
+ const onPi = (name) => {
34186
+ const def = AGENTS[name];
34187
+ return def !== void 0 && effectiveHarness(def, harnessBlock) === "pi";
34188
+ };
34189
+ const stickyAgent = thread ? stickyAgentOf(thread, onPi) : void 0;
34190
+ const settled = resolveRun(deps, {
34191
+ msg,
34192
+ directives,
34193
+ history,
34194
+ ...stickyAgent !== void 0 ? { stickyAgent } : {}
34195
+ });
33543
34196
  const { sticky } = settled;
33544
34197
  let { resolved, agentSource } = settled;
33545
34198
  let route3;
@@ -33677,8 +34330,23 @@ async function dispatch(deps, msg, io, opts = {}) {
33677
34330
  }
33678
34331
  const contractBlock = opts.contract ? renderContract(opts.contract, { maxChars: DEFAULT_CONTRACT_MAX_CHARS }).text : void 0;
33679
34332
  const requestText = route3?.parts ? compoundBrief(directives.text, route3.parts) : directives.text;
33680
- const seed = opts.seed ? "parent" : "channel";
33681
- const built = buildMessages(opts.seed ?? history, requestText, msg.images, msg.documents);
34333
+ const fromSession = !resume && !opts.seed && thread && effectiveHarness(agent, harnessBlock) === "pi" ? await sessionSeedFor({
34334
+ ledger: deps.runLedger,
34335
+ threadKey: msg.threadKey,
34336
+ agent: agent.name,
34337
+ thread,
34338
+ history,
34339
+ request: {
34340
+ text: requestText,
34341
+ ...msg.images ? { images: msg.images } : {},
34342
+ ...msg.documents ? { documents: msg.documents } : {}
34343
+ }
34344
+ }) : void 0;
34345
+ const session = fromSession?.seed;
34346
+ const seedNotes = fromSession?.notes ?? [];
34347
+ const seed = opts.seed ? "parent" : session ? "session" : "channel";
34348
+ const seedTurns = opts.seed ?? (session ? textTurnsOf(session.messages.slice(0, -1)) : void 0);
34349
+ const built = session ? session.messages : buildMessages(opts.seed ?? history, requestText, msg.images, msg.documents);
33682
34350
  const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(built, contractBlock) : built;
33683
34351
  const headPreflight = await authorizePrHead({
33684
34352
  msg,
@@ -33716,12 +34384,14 @@ async function dispatch(deps, msg, io, opts = {}) {
33716
34384
  parentRunId,
33717
34385
  coordinator,
33718
34386
  seed,
33719
- ...opts.seed ? { seedTurns: opts.seed } : {},
34387
+ ...seedTurns ? { seedTurns } : {},
33720
34388
  agentSource,
33721
34389
  ...routeEvent ? { route: routeEvent } : {}
33722
34390
  });
33723
34391
  const { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta } = registration;
33724
34392
  registered = run2;
34393
+ for (const summary of seedNotes)
34394
+ registry.publish(run2.id, { type: "run_note", kind: "seed", summary: oneLine(summary), at: clock3() });
33725
34395
  await tellLineage({ kind: "started", runId: runId3 });
33726
34396
  const staged = !resume && deps.artifacts && msg.staged && msg.staged.length > 0 ? msg.staged : [];
33727
34397
  const nextStagedIndex = stagingIndex();
@@ -33831,7 +34501,15 @@ async function dispatch(deps, msg, io, opts = {}) {
33831
34501
  verifiedAtAttach,
33832
34502
  resume,
33833
34503
  root,
33834
- ...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {}
34504
+ ...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {},
34505
+ // What the session already knows (session-log item 10): the notepad and
34506
+ // the newest compaction's summary the seed brought back, for the prompt.
34507
+ ...session && (session.notepad !== void 0 || session.summary !== void 0) ? {
34508
+ session: {
34509
+ ...session.notepad !== void 0 ? { notepad: session.notepad } : {},
34510
+ ...session.summary !== void 0 ? { summary: session.summary } : {}
34511
+ }
34512
+ } : {}
33835
34513
  });
33836
34514
  const { mcpForRun, composeSystem, system } = prompt;
33837
34515
  const reviewHead = prompt.reviewHead;
@@ -33886,8 +34564,10 @@ async function dispatch(deps, msg, io, opts = {}) {
33886
34564
  root,
33887
34565
  parentRunId,
33888
34566
  coordinator,
33889
- seed
34567
+ seed,
34568
+ ...session ? { seedLog: session.log } : {}
33890
34569
  });
34570
+ const sessionTools = sessionCapabilityFor(ledgerRun, deps.runLedger);
33891
34571
  const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
33892
34572
  { core: deps, dispatch, registry, clock: clock3 },
33893
34573
  {
@@ -33938,6 +34618,7 @@ async function dispatch(deps, msg, io, opts = {}) {
33938
34618
  runs,
33939
34619
  steer,
33940
34620
  wait,
34621
+ ...sessionTools ? { session: sessionTools } : {},
33941
34622
  parentRunId,
33942
34623
  coordinator,
33943
34624
  seed,
@@ -34067,6 +34748,10 @@ var init_dispatcher = __esm({
34067
34748
  init_codingChild();
34068
34749
  init_settle();
34069
34750
  init_lineage();
34751
+ init_seed();
34752
+ init_session();
34753
+ init_thread();
34754
+ init_select();
34070
34755
  init_spawn();
34071
34756
  init_runsService();
34072
34757
  init_runRegistry();
@@ -34183,7 +34868,7 @@ var init_runHistoryWriter = __esm({
34183
34868
 
34184
34869
  // ../../src/core/runStore.ts
34185
34870
  import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
34186
- import { join as join12, resolve as resolve9 } from "node:path";
34871
+ import { join as join11, resolve as resolve9 } from "node:path";
34187
34872
  function isValidRunId(id) {
34188
34873
  return typeof id === "string" && RUN_ID_PATTERN.test(id);
34189
34874
  }
@@ -34231,7 +34916,7 @@ function buildRunStore(cfg, secrets, deps) {
34231
34916
  const now = deps.now ?? Date.now;
34232
34917
  const policy = retentionPolicyOf(cfg);
34233
34918
  if (cfg.store === "file") {
34234
- const store = new FileRunStore(join12(deps.dataDir, RUNS_DIR), { policy, now });
34919
+ const store = new FileRunStore(join11(deps.dataDir, RUNS_DIR), { policy, now });
34235
34920
  store.sweep();
34236
34921
  const timer = (deps.setInterval ?? setInterval)(() => store.sweep(), SWEEP_INTERVAL_MS);
34237
34922
  timer.unref?.();
@@ -34298,7 +34983,7 @@ var init_runStore = __esm({
34298
34983
  this.now = opts.now ?? Date.now;
34299
34984
  }
34300
34985
  recordPath(id) {
34301
- return join12(this.dir, `${id}.json`);
34986
+ return join11(this.dir, `${id}.json`);
34302
34987
  }
34303
34988
  ensureDir() {
34304
34989
  mkdirSync8(this.dir, { recursive: true, mode: DIR_MODE });
@@ -34320,7 +35005,7 @@ var init_runStore = __esm({
34320
35005
  }
34321
35006
  /** Every valid index line, a repeated id resolved to its last line. */
34322
35007
  readIndex() {
34323
- const path = join12(this.dir, INDEX_FILE);
35008
+ const path = join11(this.dir, INDEX_FILE);
34324
35009
  if (!existsSync12(path)) return [];
34325
35010
  const byId = /* @__PURE__ */ new Map();
34326
35011
  for (const line3 of readFileSync14(path, "utf8").split("\n")) {
@@ -34352,7 +35037,7 @@ var init_runStore = __esm({
34352
35037
  const keptIds = new Set(kept.map((r) => r.id));
34353
35038
  for (const item of index) if (!keptIds.has(item.id)) rmSync3(this.recordPath(item.id), { force: true });
34354
35039
  this.writeAtomic(
34355
- join12(this.dir, INDEX_FILE),
35040
+ join11(this.dir, INDEX_FILE),
34356
35041
  kept.map((r) => JSON.stringify(r)).join("\n") + (kept.length ? "\n" : "")
34357
35042
  );
34358
35043
  return kept;
@@ -34468,24 +35153,6 @@ var init_flusher = __esm({
34468
35153
  }
34469
35154
  });
34470
35155
 
34471
- // ../../src/core/runLedger/sessionLog.ts
34472
- function sessionKey(threadKey, agent) {
34473
- return `${threadKey}:${agent ?? "-"}`;
34474
- }
34475
- function requestIndex(seed) {
34476
- for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
34477
- return 0;
34478
- }
34479
- var DEFAULT_SESSION_LOG_MAX_BYTES;
34480
- var init_sessionLog = __esm({
34481
- "../../src/core/runLedger/sessionLog.ts"() {
34482
- "use strict";
34483
- init_runRecord();
34484
- init_runRecord();
34485
- DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
34486
- }
34487
- });
34488
-
34489
35156
  // ../../src/core/runLedger/types.ts
34490
35157
  var LEASE_MS, HEARTBEAT_MS, APPEND_FLUSH_MS, APPEND_FLUSH_EVENTS, TRANSCRIPT_REQUEST_BYTES, TRANSCRIPT_PART_BYTES, ATTACHMENT_REF_BYTES, GEN_PATTERN;
34491
35158
  var init_types5 = __esm({
@@ -34538,7 +35205,15 @@ function createLedgerWriteThrough(opts) {
34538
35205
  if (opts2.seed) {
34539
35206
  const key = sessionKey(req.threadKey, req.meta.agent);
34540
35207
  const next = await ledger.sessionTail(key);
34541
- session = { key, seedFrom: next, request: next + requestIndex(opts2.seed), range: { from: next } };
35208
+ let seedFrom = next;
35209
+ if (opts2.log) {
35210
+ if (opts2.log.from + opts2.log.turns === next) seedFrom = opts2.log.from;
35211
+ else
35212
+ warn(
35213
+ `[ledger] ${req.threadKey} run ${req.runId}: the seed names log rows ${opts2.log.from}..${opts2.log.from + opts2.log.turns - 1} but the log's tail is ${next} \u2014 the whole seed is written as new rows`
35214
+ );
35215
+ }
35216
+ session = { key, seedFrom, request: seedFrom + requestIndex(opts2.seed), range: { from: next } };
34542
35217
  }
34543
35218
  const result = await ledger.claim({
34544
35219
  runId: req.runId,
@@ -34592,7 +35267,10 @@ function createLedgerWriteThrough(opts) {
34592
35267
  /** The run's place in its session log (session-log item 2); undefined for
34593
35268
  * a run without a conversation of its own and for an adopted row claimed
34594
35269
  * before the log existed. */
34595
- session;
35270
+ sessionRow;
35271
+ get session() {
35272
+ return this.sessionRow;
35273
+ }
34596
35274
  /** Turns of the run's conversation on the ledger, counted from its seed —
34597
35275
  * the last step record's `turnIndex` — so the finish can close the range. */
34598
35276
  turnsWritten;
@@ -34630,11 +35308,11 @@ function createLedgerWriteThrough(opts) {
34630
35308
  this.stepNo = from.stepNo;
34631
35309
  this.lastSeq = from.lastSeq;
34632
35310
  this.adopted = from.resumable === true;
34633
- this.session = from.session;
35311
+ this.sessionRow = from.session;
34634
35312
  }
34635
35313
  /** A reservation learns its session when the prompt's claim promotes it. */
34636
35314
  bindSession(session) {
34637
- this.session = session;
35315
+ this.sessionRow = session;
34638
35316
  }
34639
35317
  get resumable() {
34640
35318
  return !this.detached && (this.seeded || this.adopted);
@@ -34647,11 +35325,11 @@ function createLedgerWriteThrough(opts) {
34647
35325
  * ends short of what the model saw, and the next run in the session must
34648
35326
  * know. A run that wrote no turn leaves the range open. */
34649
35327
  sessionOnRecord() {
34650
- if (!this.session) return void 0;
34651
- if (this.broken) return { ...this.session, range: "broken" };
34652
- const from = this.session.range === "broken" ? this.session.seedFrom : this.session.range.from;
34653
- const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.session.seedFrom + this.turnsWritten - 1 : void 0;
34654
- return { ...this.session, range: to !== void 0 && to >= from ? { from, to } : { from } };
35328
+ if (!this.sessionRow) return void 0;
35329
+ if (this.broken) return { ...this.sessionRow, range: "broken" };
35330
+ const from = this.sessionRow.range === "broken" ? this.sessionRow.seedFrom : this.sessionRow.range.from;
35331
+ const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.sessionRow.seedFrom + this.turnsWritten - 1 : void 0;
35332
+ return { ...this.sessionRow, range: to !== void 0 && to >= from ? { from, to } : { from } };
34655
35333
  }
34656
35334
  sink = {
34657
35335
  put: async (plain4) => {
@@ -34698,10 +35376,11 @@ function createLedgerWriteThrough(opts) {
34698
35376
  * no record at all was killed before its conversation was stored and
34699
35377
  * closes `interrupted`. */
34700
35378
  async seed(messages, budgetMs) {
34701
- const base = this.session?.seedFrom ?? 0;
34702
- const turns = messages.map((message2, i) => ({ idx: base + i, message: message2 }));
35379
+ const base = this.sessionRow?.seedFrom ?? 0;
35380
+ const reused = this.sessionRow && this.sessionRow.range !== "broken" ? this.sessionRow.range.from - this.sessionRow.seedFrom : 0;
35381
+ const turns = messages.slice(reused).map((message2, i) => ({ idx: base + reused + i, message: message2 }));
34703
35382
  try {
34704
- const seeded = await ledger.seed(this.runId, gen, turns, this.session?.key);
35383
+ const seeded = await ledger.seed(this.runId, gen, turns, this.sessionRow?.key);
34705
35384
  if (!seeded.ok) {
34706
35385
  this.detach(`seed refused (${seeded.reason})`);
34707
35386
  return;
@@ -34721,7 +35400,7 @@ function createLedgerWriteThrough(opts) {
34721
35400
  iteration: 0
34722
35401
  },
34723
35402
  [],
34724
- this.session?.key
35403
+ this.sessionRow?.key
34725
35404
  );
34726
35405
  if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
34727
35406
  else this.seeded = true;
@@ -34731,7 +35410,7 @@ function createLedgerWriteThrough(opts) {
34731
35410
  }
34732
35411
  async step(report2) {
34733
35412
  if (this.detached) return;
34734
- const base = this.session?.seedFrom ?? 0;
35413
+ const base = this.sessionRow?.seedFrom ?? 0;
34735
35414
  const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
34736
35415
  if (report2.compaction)
34737
35416
  turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
@@ -34747,7 +35426,7 @@ function createLedgerWriteThrough(opts) {
34747
35426
  };
34748
35427
  for (let attempt = 1; ; attempt++) {
34749
35428
  try {
34750
- const result = await ledger.step(this.runId, gen, record2, turns, this.session?.key);
35429
+ const result = await ledger.step(this.runId, gen, record2, turns, this.sessionRow?.key);
34751
35430
  if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
34752
35431
  else this.turnsWritten = record2.turnIndex;
34753
35432
  return;
@@ -34869,7 +35548,7 @@ function createLedgerWriteThrough(opts) {
34869
35548
  const seed = req.seed?.messages;
34870
35549
  if (reserved instanceof TrackedRun) {
34871
35550
  if (!reserved.tracked()) return void 0;
34872
- const claimed3 = await claim(req, seed ? { seed } : {});
35551
+ const claimed3 = await claim(req, seed ? { seed, ...req.seed?.log ? { log: req.seed.log } : {} } : {});
34873
35552
  if (claimed3.outcome === "fenced") {
34874
35553
  reserved.detach("promotion refused (fenced)");
34875
35554
  return void 0;
@@ -34883,7 +35562,7 @@ function createLedgerWriteThrough(opts) {
34883
35562
  if (req.seed) await reserved.seed(req.seed.messages, req.seed.budgetMs);
34884
35563
  return reserved;
34885
35564
  }
34886
- const claimed2 = await claim(req, seed ? { seed } : {});
35565
+ const claimed2 = await claim(req, seed ? { seed, ...req.seed?.log ? { log: req.seed.log } : {} } : {});
34887
35566
  if (claimed2.outcome === "fenced") {
34888
35567
  warn(`[ledger] ${req.threadKey} not tracked: run ${req.runId} is live under another generation`);
34889
35568
  return void 0;
@@ -34932,6 +35611,11 @@ function createLedgerWriteThrough(opts) {
34932
35611
  return [];
34933
35612
  }
34934
35613
  },
35614
+ readSessionTail: (key, maxBytes) => ledger.readSessionTail(key, maxBytes),
35615
+ readSession: (key, from, to) => ledger.readSession(key, from, to),
35616
+ searchSession: (key, query, limit) => ledger.searchSession(key, query, limit),
35617
+ readNotepad: (key) => ledger.readNotepad(key),
35618
+ writeNotepad: (key, text) => ledger.writeNotepad(key, gen, text),
34935
35619
  async handoff() {
34936
35620
  const candidates = [...live].filter((r) => r.resumable && r.tracked() && !r.handedOff);
34937
35621
  if (candidates.length === 0) return { marked: [] };
@@ -34982,6 +35666,21 @@ var init_writeThrough = __esm({
34982
35666
  async readInbox(_runId, _afterSeq) {
34983
35667
  return [];
34984
35668
  }
35669
+ async readSessionTail(_key, _maxBytes) {
35670
+ return { from: 0, transcript: { complete: true, turns: 0, messages: [], compactions: [] } };
35671
+ }
35672
+ async readSession(_key, _from, _to) {
35673
+ return { complete: true, turns: 0, messages: [], compactions: [] };
35674
+ }
35675
+ async searchSession(_key, _query, _limit) {
35676
+ return { hits: [], gaps: [] };
35677
+ }
35678
+ async readNotepad(_key) {
35679
+ return null;
35680
+ }
35681
+ async writeNotepad(_key, _text) {
35682
+ return { ok: false, reason: "unknown-run" };
35683
+ }
34985
35684
  async handoff() {
34986
35685
  return { marked: [] };
34987
35686
  }
@@ -34995,6 +35694,7 @@ var init_writeThrough = __esm({
34995
35694
  sink;
34996
35695
  resumable = false;
34997
35696
  handedOff = false;
35697
+ session = void 0;
34998
35698
  tracked() {
34999
35699
  return false;
35000
35700
  }
@@ -35474,7 +36174,10 @@ function httpMcpClientFactory(fetchImpl) {
35474
36174
  return (server) => new StreamableHttpMcpClient({
35475
36175
  url: server.url,
35476
36176
  fetch: fetchImpl,
35477
- ...server.auth?.type === "bearer" ? { headers: { authorization: `Bearer ${server.auth.token}` } } : {}
36177
+ headers: {
36178
+ ...server.headers,
36179
+ ...server.auth?.type === "bearer" ? { authorization: `Bearer ${server.auth.token}` } : {}
36180
+ }
35478
36181
  });
35479
36182
  }
35480
36183
  var init_mcp2 = __esm({
@@ -36895,7 +37598,8 @@ ${file.text}`));
36895
37598
  if (STATUS_PREFIXES.some((p) => text.startsWith(p))) continue;
36896
37599
  const files = mm.bot_id ? void 0 : mm.files;
36897
37600
  if (!text && !files?.length) continue;
36898
- kept.push({ role: mm.bot_id ? "assistant" : "user", text, files });
37601
+ const at = mm.ts !== void 0 && Number.isFinite(Number(mm.ts)) ? Math.round(Number(mm.ts) * 1e3) : void 0;
37602
+ kept.push({ role: mm.bot_id ? "assistant" : "user", text, ...at !== void 0 ? { at } : {}, files });
36899
37603
  }
36900
37604
  const imagesByIndex = [];
36901
37605
  const documentsByIndex = [];
@@ -36922,11 +37626,11 @@ ${file.text}`));
36922
37626
  }
36923
37627
  }
36924
37628
  for (let i = 0; i < kept.length; i++) {
36925
- const { role, text } = kept[i];
37629
+ const { role, text, at } = kept[i];
36926
37630
  const images = imagesByIndex[i];
36927
37631
  const documents = documentsByIndex[i];
36928
37632
  if (!text && !images && !documents) continue;
36929
- items.push({ role, text, images, documents });
37633
+ items.push({ role, text, ...at !== void 0 ? { at } : {}, images, documents });
36930
37634
  }
36931
37635
  } catch {
36932
37636
  }
@@ -38302,18 +39006,18 @@ var init_liveView = __esm({
38302
39006
 
38303
39007
  // ../../src/channels/webAssets.ts
38304
39008
  import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
38305
- import { join as join13 } from "node:path";
39009
+ import { join as join12 } from "node:path";
38306
39010
  function contentTypeFor2(path) {
38307
39011
  const dot = path.lastIndexOf(".");
38308
39012
  return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
38309
39013
  }
38310
39014
  function webDistDir(env, packageRoot) {
38311
- return env.SWITCHBOARD_WEB_DIST ?? join13(packageRoot, WEB_DIST_DIR);
39015
+ return env.SWITCHBOARD_WEB_DIST ?? join12(packageRoot, WEB_DIST_DIR);
38312
39016
  }
38313
39017
  function loadWebAssets(distDir) {
38314
39018
  let manifest;
38315
39019
  try {
38316
- manifest = JSON.parse(readFileSync16(join13(distDir, ".vite", "manifest.json"), "utf8"));
39020
+ manifest = JSON.parse(readFileSync16(join12(distDir, ".vite", "manifest.json"), "utf8"));
38317
39021
  } catch (err2) {
38318
39022
  throw new Error(
38319
39023
  `web app manifest not found under ${distDir} \u2014 build it first (npm run build in web/): ${err2 instanceof Error ? err2.message : String(err2)}`,
@@ -38327,9 +39031,9 @@ function loadWebAssets(distDir) {
38327
39031
  css: (entryChunk.css ?? []).map((f) => `/${f}`)
38328
39032
  };
38329
39033
  const files = /* @__PURE__ */ new Map();
38330
- const assetsDir = join13(distDir, "assets");
39034
+ const assetsDir = join12(distDir, "assets");
38331
39035
  for (const name of readdirSync3(assetsDir)) {
38332
- const full = join13(assetsDir, name);
39036
+ const full = join12(assetsDir, name);
38333
39037
  if (statSync3(full).isFile()) files.set(`/assets/${name}`, readFileSync16(full));
38334
39038
  }
38335
39039
  const serve2 = (req, res) => {
@@ -39519,6 +40223,25 @@ var init_runLedgerWorker = __esm({
39519
40223
  )
39520
40224
  };
39521
40225
  }
40226
+ async searchSession(key, query, limit) {
40227
+ this.checkSessionKey(key);
40228
+ const r = await this.post("/runs/session/search", { key, query, limit });
40229
+ return {
40230
+ hits: Array.isArray(r.data.hits) ? r.data.hits : [],
40231
+ gaps: Array.isArray(r.data.gaps) ? r.data.gaps : []
40232
+ };
40233
+ }
40234
+ async readNotepad(key) {
40235
+ this.checkSessionKey(key);
40236
+ const r = await this.post("/runs/session/notepad", { key });
40237
+ const n2 = r.data.notepad;
40238
+ return n2 && typeof n2.text === "string" && typeof n2.updatedAt === "number" ? { text: n2.text, updatedAt: n2.updatedAt } : null;
40239
+ }
40240
+ async writeNotepad(key, gen, text) {
40241
+ this.checkSessionKey(key);
40242
+ if (!GEN_PATTERN.test(gen)) throw new PermanentStoreError(`run ledger: malformed generation`);
40243
+ return this.fenceResult(await this.post("/runs/session/notepad/write", { key, gen, text }));
40244
+ }
39522
40245
  async heartbeat(runId3, gen, leaseMs) {
39523
40246
  this.checkIds(runId3, gen);
39524
40247
  const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
@@ -42376,7 +43099,7 @@ __export(index_exports, {
42376
43099
  runBot: () => runBot
42377
43100
  });
42378
43101
  import { createServer } from "node:http";
42379
- import { join as join14 } from "node:path";
43102
+ import { join as join13 } from "node:path";
42380
43103
  async function runBot() {
42381
43104
  for (const v of ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"]) {
42382
43105
  if (!processSecrets.get(v)) {
@@ -43054,7 +43777,7 @@ var init_index = __esm({
43054
43777
  init_secrets2();
43055
43778
  DATA_DIR = installationPath(OPERATOR_ROOT, "data");
43056
43779
  CONFIG_PATH2 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
43057
- OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ?? join14(DATA_DIR, "overrides.json");
43780
+ OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ?? join13(DATA_DIR, "overrides.json");
43058
43781
  PROCESS_STARTED_AT = systemClock();
43059
43782
  sampleProcessMetrics = startProcessMetrics();
43060
43783
  INTERRUPTED_WRITE_BUDGET_MS = 1e4;
@@ -43101,7 +43824,7 @@ init_secrets2();
43101
43824
  import { Console } from "node:console";
43102
43825
  import { existsSync as existsSync14, mkdtempSync, writeFileSync as writeFileSync9 } from "node:fs";
43103
43826
  import { tmpdir } from "node:os";
43104
- import { basename as basename3, join as join15 } from "node:path";
43827
+ import { basename as basename3, join as join14 } from "node:path";
43105
43828
  var CONFIG_PATH3 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
43106
43829
  var DATA_DIR2 = installationPath(OPERATOR_ROOT, "data");
43107
43830
  var CLI_CALLER = { kind: "cli", id: CLI_ACTOR.id, actor: CLI_ACTOR };
@@ -43275,8 +43998,8 @@ var ConsoleIO = class _ConsoleIO {
43275
43998
  /** The harness's file upload: the bytes written under the attachments dir,
43276
43999
  * the lead printed like a reply with the path a person can open. */
43277
44000
  async attachFile(file) {
43278
- this.attachments.dir ??= mkdtempSync(join15(tmpdir(), "switchboard-attachments-"));
43279
- const path = join15(this.attachments.dir, basename3(file.name));
44001
+ this.attachments.dir ??= mkdtempSync(join14(tmpdir(), "switchboard-attachments-"));
44002
+ const path = join14(this.attachments.dir, basename3(file.name));
43280
44003
  writeFileSync9(path, file.bytes);
43281
44004
  await this.reply(`${file.lead}
43282
44005
  \u{1F4CE} ${file.name} (${file.bytes.byteLength} bytes) \u2192 ${path}`);
@@ -43357,7 +44080,7 @@ function cliCapabilities(configPath, env, secrets, opts = {}) {
43357
44080
  }
43358
44081
  function wireCli() {
43359
44082
  const warn = (m) => console.error(m);
43360
- const botConfig = bindBotConfig(CONFIG_PATH3, join15(DATA_DIR2, "cli-overrides.json"));
44083
+ const botConfig = bindBotConfig(CONFIG_PATH3, join14(DATA_DIR2, "cli-overrides.json"));
43361
44084
  let loaded;
43362
44085
  const bot = () => loaded ??= botConfig().then((config) => ({
43363
44086
  config,