@coreplane/switchboard 1.230.0 → 1.231.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/dist/assets/deploy/cloudflare/knownLength.ts +43 -0
  2. package/dist/assets/deploy/cloudflare/worker.ts +10 -2
  3. package/dist/assets/package-lock.json +3 -3
  4. package/dist/assets/package.json +1 -1
  5. package/dist/assets/source.json +3 -3
  6. package/dist/assets/src/core/coordinator/contract.ts +22 -14
  7. package/dist/assets/src/core/coordinator/driver.ts +4 -0
  8. package/dist/assets/src/core/ship/contract.ts +35 -29
  9. package/dist/assets/src/core/ship/coordinator.ts +89 -4
  10. package/dist/assets/web/dist/.vite/manifest.json +19 -19
  11. package/dist/assets/web/dist/assets/CostsPage-D-v8am88.js +2 -0
  12. package/dist/assets/web/dist/assets/{ResidentDetailPage-DYxDruS0.js → ResidentDetailPage-V4K35SYX.js} +1 -1
  13. package/dist/assets/web/dist/assets/{ResidentsIndexPage-DIBRBmuq.js → ResidentsIndexPage-3HuYaDwD.js} +1 -1
  14. package/dist/assets/web/dist/assets/{RunRoutePage-B4s8hJlW.js → RunRoutePage-Dr867-Ha.js} +4 -4
  15. package/dist/assets/web/dist/assets/{RunsIndexPage-CaW07KOf.js → RunsIndexPage-D_lpXsr8.js} +1 -1
  16. package/dist/assets/web/dist/assets/{ScheduledPage-Cr7269s9.js → ScheduledPage-dfrjQ5E9.js} +1 -1
  17. package/dist/assets/web/dist/assets/{StatusDot-lCLi16zY.js → StatusDot-DexKtvUC.js} +1 -1
  18. package/dist/assets/web/dist/assets/{Tooltip-DKMPwNyy.js → Tooltip-DGQ2gu9M.js} +1 -1
  19. package/dist/assets/web/dist/assets/{dist-DRkTB2rt.js → dist-boTdDLF4.js} +1 -1
  20. package/dist/assets/web/dist/assets/{main-CXKOiCOK.js → main-BHhLVMQh.js} +2 -2
  21. package/dist/cli.js +398 -224
  22. package/package.json +1 -1
  23. package/dist/assets/web/dist/assets/CostsPage-B08bdD-1.js +0 -2
package/dist/cli.js CHANGED
@@ -443,8 +443,10 @@ var init_store = __esm({
443
443
  const contentType = res.headers.get("content-type") ?? "application/octet-stream";
444
444
  if (res.status === 416) {
445
445
  const total = /\/(\d+)\s*$/.exec(res.headers.get("content-range") ?? "");
446
- if (!total) throw new Error(`artifact store: GET ${key} answered HTTP 416 without the object's size`);
447
- return { unsatisfiable: true, size: Number(total[1]), contentType };
446
+ if (total) return { unsatisfiable: true, size: Number(total[1]), contentType };
447
+ const head = await this.head(key);
448
+ if (!head) return null;
449
+ return { unsatisfiable: true, size: head.size, contentType: head.contentType };
448
450
  }
449
451
  if (!res.ok || !res.body) throw new Error(`artifact store: GET ${key} answered HTTP ${res.status}`);
450
452
  if (res.status === 206) {
@@ -1616,7 +1618,18 @@ function resolveSpecRow(ref, specMarkdown) {
1616
1618
  validation: parseValidationRows(specMarkdown).filter((row) => itemNumbersNamed(row.criterion).has(ref.item))
1617
1619
  };
1618
1620
  }
1621
+ function unitTitleOf(text) {
1622
+ const firstLine2 = text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "";
1623
+ return firstLine2.length > TITLE_MAX ? `${firstLine2.slice(0, TITLE_MAX - 1).trimEnd()}\u2026` : firstLine2;
1624
+ }
1625
+ function generatedUnit(id, text) {
1626
+ const title = unitTitleOf(text);
1627
+ return { id, title, section: `### ${id}. ${title}
1628
+
1629
+ ${text}`, bullets: {} };
1630
+ }
1619
1631
  function contractFromPlan(input) {
1632
+ if ("unit" in input) return contractOf(input.unit, input);
1620
1633
  const unit = parsePlanUnit(input.planMarkdown, input.unitId);
1621
1634
  if (!unit) {
1622
1635
  const ids = planUnitIds(input.planMarkdown);
@@ -1624,6 +1637,9 @@ function contractFromPlan(input) {
1624
1637
  `the plan has no unit ${input.unitId} (its units: ${ids.length > 0 ? ids.join(", ") : "none \u2014 no `### U<n>.` heading"})`
1625
1638
  );
1626
1639
  }
1640
+ return contractOf(unit, input);
1641
+ }
1642
+ function contractOf(unit, input) {
1627
1643
  return {
1628
1644
  unit,
1629
1645
  specRows: specItemRefs(unit.section).map((ref) => resolveSpecRow(ref, input.readSpec(ref.spec))),
@@ -1633,18 +1649,6 @@ function contractFromPlan(input) {
1633
1649
  issue: input.issue
1634
1650
  };
1635
1651
  }
1636
- function contractFromTask(input) {
1637
- const firstLine2 = input.task.split("\n").find((l) => l.trim() !== "")?.trim() ?? "";
1638
- const title = firstLine2.length > TITLE_MAX ? `${firstLine2.slice(0, TITLE_MAX - 1).trimEnd()}\u2026` : firstLine2;
1639
- return {
1640
- unit: { id: "task", title, section: input.task, bullets: {} },
1641
- specRows: [],
1642
- agentRules: input.agentRules,
1643
- guards: GUARDS,
1644
- rebase: { branch: input.rebase?.branch, onto: input.rebase?.onto },
1645
- issue: input.issue
1646
- };
1647
- }
1648
1652
  function unitBody(unit, dropApproach) {
1649
1653
  const lines = unit.section.split("\n");
1650
1654
  if (dropApproach) {
@@ -12192,7 +12196,8 @@ function isCoordinatorInstance(v) {
12192
12196
  if (typeof r.repo !== "string" || !REPO_SLUG2.test(r.repo)) return false;
12193
12197
  if (!isText(r.branch) || !isOptionalText(r.base)) return false;
12194
12198
  if (!isFinite(r.createdAt)) return false;
12195
- if (r.plan !== void 0 && !(isObject(r.plan) && isText(r.plan.id) && isText(r.plan.path, 1024))) return false;
12199
+ if (r.plan !== void 0 && !(isObject(r.plan) && isText(r.plan.id) && (r.plan.path === void 0 || isText(r.plan.path, 1024))))
12200
+ return false;
12196
12201
  if (r.merge !== void 0 && r.merge !== "runner" && r.merge !== "person") return false;
12197
12202
  if (r.caps !== void 0 && !(isObject(r.caps) && isFinite(r.caps.maxRounds) && isFinite(r.caps.maxMinutes)))
12198
12203
  return false;
@@ -14159,11 +14164,10 @@ async function viewerRunUserIds(userIds, viewer, emailOfSlackUser, cache2) {
14159
14164
  const out = [];
14160
14165
  for (const id of new Set(userIds)) {
14161
14166
  if (!id.startsWith("slack:") || id.startsWith("slack:bot:")) continue;
14162
- const slackId = id.slice("slack:".length);
14163
- let known = cache2.get(slackId);
14167
+ let known = cache2.get(id);
14164
14168
  if (known === void 0) {
14165
- known = (await emailOfSlackUser(slackId))?.toLowerCase();
14166
- if (known !== void 0) cache2.set(slackId, known);
14169
+ known = (await emailOfSlackUser(id))?.toLowerCase();
14170
+ if (known !== void 0) cache2.set(id, known);
14167
14171
  }
14168
14172
  if (known === email) out.push(id);
14169
14173
  }
@@ -29741,7 +29745,7 @@ var init_statusCardFrame = __esm({
29741
29745
  SPINNER_GLYPHS = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
29742
29746
  LIVE_CARD_PREFIXES = [...SPINNER_GLYPHS, "\u{1F440}"];
29743
29747
  ROUTED_LABEL_PREFIX = "routed:";
29744
- ROUTED_CARD_FOOTER = "reply agent:<preset> to run it another way";
29748
+ ROUTED_CARD_FOOTER = "wrong preset? reply agent:<preset> to run it another way";
29745
29749
  }
29746
29750
  });
29747
29751
 
@@ -35674,7 +35678,6 @@ var init_runLoop = __esm({
35674
35678
  });
35675
35679
 
35676
35680
  // ../../src/core/ship/preflight.ts
35677
- import { createHash as createHash5 } from "node:crypto";
35678
35681
  function shipTaskText(requestText, repo) {
35679
35682
  let t = requestText.replace(/<((?:https?):\/\/[^|>\s]+)(?:\|[^>]*)?>/gi, " $1 ");
35680
35683
  t = t.replace(/https?:\/\/\S+/gi, " ");
@@ -35684,11 +35687,6 @@ function shipTaskText(requestText, repo) {
35684
35687
  t = t.replace(/\b[a-z0-9][\w.-]*\/[\w.-]+#\d+\b/gi, " ");
35685
35688
  return t.replace(/\s+/g, " ").trim().replace(/^[:,\-—.\s]+/, "").trim();
35686
35689
  }
35687
- function shipBranchName(task, threadKey) {
35688
- const slug = task.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 24).replace(/-+$/, "") || "task";
35689
- const hash = createHash5("sha256").update(threadKey).digest("hex").slice(0, 6);
35690
- return `ship/${slug}-${hash}`;
35691
- }
35692
35690
  async function shipPreflight(input) {
35693
35691
  const { repoCtx } = input;
35694
35692
  if (!input.channelId.startsWith("slack:") && !input.channelId.startsWith("cli:")) {
@@ -35812,7 +35810,7 @@ async function shipPreflight(input) {
35812
35810
  const ref = repoCtx.ref && !repoCtx.refFromPr && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
35813
35811
  return {
35814
35812
  ok: true,
35815
- entry: { repo, branch: shipBranchName(task, input.threadKey), base: resolveBaseRef([ref], info.defaultBranch) }
35813
+ entry: { repo, base: resolveBaseRef([ref], info.defaultBranch) }
35816
35814
  };
35817
35815
  }
35818
35816
  var refuse2;
@@ -35855,6 +35853,133 @@ function planInstanceId(planId, attempt = 1) {
35855
35853
  const suffix = `-${attempt}`;
35856
35854
  return `${base.slice(0, INSTANCE_ID_MAX - suffix.length)}${suffix}`;
35857
35855
  }
35856
+ function sha256Hex2(text) {
35857
+ const K = new Uint32Array([
35858
+ 1116352408,
35859
+ 1899447441,
35860
+ 3049323471,
35861
+ 3921009573,
35862
+ 961987163,
35863
+ 1508970993,
35864
+ 2453635748,
35865
+ 2870763221,
35866
+ 3624381080,
35867
+ 310598401,
35868
+ 607225278,
35869
+ 1426881987,
35870
+ 1925078388,
35871
+ 2162078206,
35872
+ 2614888103,
35873
+ 3248222580,
35874
+ 3835390401,
35875
+ 4022224774,
35876
+ 264347078,
35877
+ 604807628,
35878
+ 770255983,
35879
+ 1249150122,
35880
+ 1555081692,
35881
+ 1996064986,
35882
+ 2554220882,
35883
+ 2821834349,
35884
+ 2952996808,
35885
+ 3210313671,
35886
+ 3336571891,
35887
+ 3584528711,
35888
+ 113926993,
35889
+ 338241895,
35890
+ 666307205,
35891
+ 773529912,
35892
+ 1294757372,
35893
+ 1396182291,
35894
+ 1695183700,
35895
+ 1986661051,
35896
+ 2177026350,
35897
+ 2456956037,
35898
+ 2730485921,
35899
+ 2820302411,
35900
+ 3259730800,
35901
+ 3345764771,
35902
+ 3516065817,
35903
+ 3600352804,
35904
+ 4094571909,
35905
+ 275423344,
35906
+ 430227734,
35907
+ 506948616,
35908
+ 659060556,
35909
+ 883997877,
35910
+ 958139571,
35911
+ 1322822218,
35912
+ 1537002063,
35913
+ 1747873779,
35914
+ 1955562222,
35915
+ 2024104815,
35916
+ 2227730452,
35917
+ 2361852424,
35918
+ 2428436474,
35919
+ 2756734187,
35920
+ 3204031479,
35921
+ 3329325298
35922
+ ]);
35923
+ const h = new Uint32Array([
35924
+ 1779033703,
35925
+ 3144134277,
35926
+ 1013904242,
35927
+ 2773480762,
35928
+ 1359893119,
35929
+ 2600822924,
35930
+ 528734635,
35931
+ 1541459225
35932
+ ]);
35933
+ const bytes = new TextEncoder().encode(text);
35934
+ const padded = new Uint8Array((bytes.length + 8 >> 6 << 6) + 64);
35935
+ padded.set(bytes);
35936
+ padded[bytes.length] = 128;
35937
+ new DataView(padded.buffer).setBigUint64(padded.length - 8, BigInt(bytes.length) * 8n);
35938
+ const w = new Uint32Array(64);
35939
+ const rotr = (x, n2) => x >>> n2 | x << 32 - n2;
35940
+ const view = new DataView(padded.buffer);
35941
+ for (let i = 0; i < padded.length; i += 64) {
35942
+ for (let t = 0; t < 16; t++) w[t] = view.getUint32(i + t * 4);
35943
+ for (let t = 16; t < 64; t++) {
35944
+ const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >>> 3;
35945
+ const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >>> 10;
35946
+ w[t] = w[t - 16] + s0 + w[t - 7] + s1 >>> 0;
35947
+ }
35948
+ let [a, b, c, d, e, f, g, hh] = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]];
35949
+ for (let t = 0; t < 64; t++) {
35950
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
35951
+ const ch = e & f ^ ~e & g;
35952
+ const t1 = hh + S1 + ch + K[t] + w[t] >>> 0;
35953
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
35954
+ const maj = a & b ^ a & c ^ b & c;
35955
+ const t2 = S0 + maj >>> 0;
35956
+ hh = g;
35957
+ g = f;
35958
+ f = e;
35959
+ e = d + t1 >>> 0;
35960
+ d = c;
35961
+ c = b;
35962
+ b = a;
35963
+ a = t1 + t2 >>> 0;
35964
+ }
35965
+ h[0] = h[0] + a >>> 0;
35966
+ h[1] = h[1] + b >>> 0;
35967
+ h[2] = h[2] + c >>> 0;
35968
+ h[3] = h[3] + d >>> 0;
35969
+ h[4] = h[4] + e >>> 0;
35970
+ h[5] = h[5] + f >>> 0;
35971
+ h[6] = h[6] + g >>> 0;
35972
+ h[7] = h[7] + hh >>> 0;
35973
+ }
35974
+ return [...h].map((x) => x.toString(16).padStart(8, "0")).join("");
35975
+ }
35976
+ function generatedPlanId(text, threadKey) {
35977
+ const normalised = text.toLowerCase().replace(/\s+/g, " ").trim();
35978
+ const slug = normalised.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, SLUG_MAX).replace(/-+$/, "") || "task";
35979
+ const hash = sha256Hex2(`${threadKey}
35980
+ ${normalised}`).slice(0, 6);
35981
+ return `${slug}-${hash}`;
35982
+ }
35858
35983
  function dependenciesOf(text, self) {
35859
35984
  const out = [];
35860
35985
  const add = (id) => {
@@ -35947,141 +36072,6 @@ var init_coordinator = __esm({
35947
36072
  }
35948
36073
  });
35949
36074
 
35950
- // ../../src/core/ship/reviewChild.ts
35951
- function buildShipReviewTurn(input) {
35952
- const at = input.headSha ? ` at head \`${input.headSha}\`` : "";
35953
- if (input.round <= 1 || !input.prior) {
35954
- return `Review pull request ${input.where}${at}. Submit your verdict with findings via submit_verdict before your final message.`;
35955
- }
35956
- const findings = input.prior.findings.map(formatFinding).join("\n") || "(none recorded)";
35957
- const dispositions = input.prior.dispositions.map((d) => `${d.findingId}: ${d.disposition}${d.note ? ` \u2014 ${d.note}` : ""}`).join("\n") || "(none recorded)";
35958
- const dropped = input.prior.dropped !== void 0 && input.prior.dropped.length > 0 ? `
35959
- Dispositions naming no finding of the previous round (dropped): ${input.prior.dropped.join(", ")}` : "";
35960
- return `Re-review pull request ${input.where}${at} \u2014 review round ${input.round} of this ship pipeline. Load the \`re-review-delta\` skill: narrow your READING to the delta since the previously reviewed head and verify each prior finding's disposition, but your verdict still covers the full diff against base. Carry every unresolved prior finding forward under its existing id.
35961
-
35962
- Previous round's findings:
35963
- ${findings}
35964
-
35965
- Fix round's dispositions:
35966
- ${dispositions}${dropped}`;
35967
- }
35968
- var init_reviewChild = __esm({
35969
- "../../src/core/ship/reviewChild.ts"() {
35970
- "use strict";
35971
- init_reviewVerdict();
35972
- }
35973
- });
35974
-
35975
- // ../../src/core/coordinator/briefs.ts
35976
- async function contractFor(instance, unit, readers) {
35977
- const rebase = { branch: unit.branch, onto: instance.base ?? "main" };
35978
- const issue = unit.issue !== void 0 ? { repo: instance.repo, number: unit.issue } : void 0;
35979
- if (unit.unit === TASK_UNIT || instance.plan === void 0) {
35980
- const request = await readers.readShipRequest();
35981
- const task = request !== void 0 ? shipTaskText(parseDirectives(request).text, instance.repo) : "";
35982
- return contractFromTask({
35983
- task: task || "Implement the task this thread's ship request describes.",
35984
- rebase,
35985
- ...issue ? { issue } : {}
35986
- });
35987
- }
35988
- const planMarkdown = await readers.readRepoFile(instance.plan.path);
35989
- if (planMarkdown === void 0)
35990
- throw new Error(`the plan ${instance.plan.path} is not readable at ${rebase.onto} in ${instance.repo}`);
35991
- const section = parsePlanUnit(planMarkdown, unit.unit)?.section ?? "";
35992
- const specs = /* @__PURE__ */ new Map();
35993
- for (const spec of new Set(specItemRefs(section).map((r) => r.spec)))
35994
- specs.set(spec, await readers.readRepoFile(`${SPECS_DIR2}/${spec}`));
35995
- let agentRules;
35996
- for (const file of RULES_FILES2) {
35997
- const text = await readers.readRepoFile(file);
35998
- if (text !== void 0) {
35999
- agentRules = { file, text };
36000
- break;
36001
- }
36002
- }
36003
- return contractFromPlan({
36004
- planMarkdown,
36005
- unitId: unit.unit,
36006
- readSpec: (spec) => specs.get(spec),
36007
- ...agentRules ? { agentRules } : {},
36008
- rebase,
36009
- ...issue ? { issue } : {}
36010
- });
36011
- }
36012
- function findingsRequest(input) {
36013
- const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
36014
- return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note), squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
36015
-
36016
- Findings:
36017
- ${findings}
36018
-
36019
- Review:
36020
- ${input.review}`;
36021
- }
36022
- async function composeChild(brief, instance, unit, readers) {
36023
- const where = `${instance.repo}#${"pr" in brief ? brief.pr : ""}`;
36024
- switch (brief.kind) {
36025
- case "contract": {
36026
- const contract = await contractFor(instance, unit, readers);
36027
- const prompt = contract.unit.id === TASK_UNIT ? contract.unit.section : `Implement unit ${contract.unit.id} \u2014 ${contract.unit.title} \u2014 of ${instance.plan?.path ?? "the plan"}: the contract below is the unit. Do its first instruction first, then add every test scenario it lists, update every spec row it names and weaken no guard.`;
36028
- return { preset: "coding", prompt, ref: unit.branch, contract };
36029
- }
36030
- case "review": {
36031
- let prior;
36032
- if (brief.prior !== void 0) {
36033
- const findings = (await facts(readers, brief.prior.reviewRunId)).findings ?? [];
36034
- const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
36035
- const { matched, dropped } = matchDispositions(findings, recorded);
36036
- prior = { findings, dispositions: matched, dropped };
36037
- }
36038
- const turn = buildShipReviewTurn({
36039
- where,
36040
- round: brief.round,
36041
- ...brief.headSha !== void 0 ? { headSha: brief.headSha } : {},
36042
- ...prior ? { prior } : {}
36043
- });
36044
- const contract = await contractFor(instance, unit, readers);
36045
- return { preset: "review", prompt: `${prUrl(instance.repo, brief.pr)}
36046
-
36047
- ${turn}`, contract };
36048
- }
36049
- case "findings": {
36050
- const review = await facts(readers, brief.reviewRunId);
36051
- return {
36052
- preset: "coding",
36053
- prompt: findingsRequest({ where, findings: review.findings ?? [], review: review.finalReply ?? "" }),
36054
- ref: unit.branch
36055
- };
36056
- }
36057
- default: {
36058
- const unknown = brief;
36059
- throw new Error(`unknown brief kind ${JSON.stringify(unknown.kind)}`);
36060
- }
36061
- }
36062
- }
36063
- async function facts(readers, runId3) {
36064
- const read = await readers.readRunFacts(runId3);
36065
- if (!read) throw new Error(`run ${runId3} is not in the run history`);
36066
- return read;
36067
- }
36068
- var SPECS_DIR2, RULES_FILES2, TASK_UNIT, prUrl;
36069
- var init_briefs = __esm({
36070
- "../../src/core/coordinator/briefs.ts"() {
36071
- "use strict";
36072
- init_directives();
36073
- init_reviewVerdict();
36074
- init_coordinator();
36075
- init_contract();
36076
- init_preflight();
36077
- init_reviewChild();
36078
- SPECS_DIR2 = "docs/reference/specs";
36079
- RULES_FILES2 = ["AGENTS.md", "CLAUDE.md"];
36080
- TASK_UNIT = "task";
36081
- prUrl = (repo, pr) => `https://github.com/${repo}/pull/${pr}`;
36082
- }
36083
- });
36084
-
36085
36075
  // ../../src/core/coordinator/handOff.ts
36086
36076
  async function plan(deps, input) {
36087
36077
  const { entry, msg } = input;
@@ -36107,29 +36097,26 @@ async function plan(deps, input) {
36107
36097
  runId: input.runId,
36108
36098
  label: input.label
36109
36099
  };
36110
- const request = parseShipPlanRequest(shipTaskText(input.requestText, entry.repo));
36100
+ const taskText = shipTaskText(input.requestText, entry.repo);
36101
+ const request = parseShipPlanRequest(taskText);
36111
36102
  if (request === void 0) {
36112
- const id = `ship-${input.runId}`;
36113
- const resume = entry.resume;
36114
- const prUrl2 = resume?.url ?? (resume !== void 0 ? `https://github.com/${entry.repo}/pull/${resume.pr}` : void 0);
36103
+ const text2 = taskText || "Implement the task this thread's ship request describes.";
36104
+ const planId2 = generatedPlanId(text2, msg.threadKey);
36105
+ const graph2 = {
36106
+ planId: planId2,
36107
+ units: [{ id: "U1", title: unitTitleOf(text2), slug: "u1", branch: unitBranch(planId2, "u1"), dependsOn: [] }]
36108
+ };
36115
36109
  return {
36116
36110
  ok: true,
36117
36111
  planned: {
36118
- kind: "task",
36119
- // A task's merge is a person's, whatever the request's words say.
36120
- instance: { id, ...identity2, branch: entry.branch, merge: "person" },
36121
- units: [
36122
- {
36123
- instanceId: id,
36124
- unit: TASK_UNIT,
36125
- slug: TASK_UNIT,
36126
- branch: entry.branch,
36127
- dependsOn: [],
36128
- rounds: [],
36129
- ...resume !== void 0 ? { resume } : {}
36130
- }
36131
- ],
36132
- where: resume !== void 0 ? `the review loop of ${prUrl2} resumes at its next review round on \`${entry.branch}\` in this thread under your grants \u2014 no new coding round first; this card follows it and the report lands here.` : `the task runs on \`${entry.branch}\` in this thread under your grants; this card follows it and the report lands here.`
36112
+ planId: planId2,
36113
+ base,
36114
+ graph: graph2,
36115
+ selected: ["U1"],
36116
+ identity: identity2,
36117
+ merge: "person",
36118
+ ...entry.resume !== void 0 ? { resume: entry.resume } : {},
36119
+ ...entry.branch !== void 0 ? { entryBranch: entry.branch } : {}
36133
36120
  }
36134
36121
  };
36135
36122
  }
@@ -36160,42 +36147,55 @@ async function plan(deps, input) {
36160
36147
  } catch (err2) {
36161
36148
  return { ok: false, reply: `\u{1F6AB} ${describe6(err2)}` };
36162
36149
  }
36163
- return { ok: true, planned: { kind: "plan", planId, path: request.planPath, base, graph, selected, identity: identity2 } };
36150
+ return {
36151
+ ok: true,
36152
+ planned: { planId, path: request.planPath, base, graph, selected, identity: identity2, merge: "runner" }
36153
+ };
36164
36154
  }
36165
- function rowsFor(graph, selected, instanceId) {
36166
- return graph.units.filter((u) => selected.includes(u.id)).map((u) => ({
36155
+ function rowsFor(p, selected, instanceId) {
36156
+ return p.graph.units.filter((u) => selected.includes(u.id)).map((u) => ({
36167
36157
  instanceId,
36168
36158
  unit: u.id,
36169
36159
  slug: u.slug,
36170
36160
  title: u.title,
36171
- branch: u.branch,
36161
+ branch: p.entryBranch ?? u.branch,
36172
36162
  dependsOn: u.dependsOn,
36173
- rounds: []
36163
+ rounds: [],
36164
+ ...p.resume !== void 0 ? { resume: p.resume } : {}
36174
36165
  }));
36175
36166
  }
36176
36167
  function planWhere(p, units, mergedBefore) {
36168
+ const at = p.path !== void 0 ? ` (\`${p.path}\` at \`${p.base}\`)` : "";
36177
36169
  const count = `${units.length} unit${units.length === 1 ? "" : "s"}`;
36178
36170
  const left = mergedBefore.length > 0 ? ` left` : "";
36179
36171
  const merged = mergedBefore.length > 0 ? `; merged before: ${mergedBefore.join(", ")}` : "";
36180
- return `plan \`${p.planId}\` (\`${p.path}\` at \`${p.base}\`), ${count}${left} in dependency order \u2014 ${units.map((u) => u.unit).join(", ")}${merged}. Each unit runs in a thread of its own in this channel under your grants; this card follows the plan and its summary lands in this thread.`;
36172
+ const head = `plan \`${p.planId}\`${at}, ${count}${left} in dependency order \u2014 ${units.map((u) => u.unit).join(", ")}${merged}.`;
36173
+ if (p.path !== void 0)
36174
+ return `${head} Each unit runs in a thread of its own in this channel under your grants; this card follows the plan and its summary lands in this thread.`;
36175
+ const branch2 = units[0]?.branch ?? "";
36176
+ const prUrl2 = p.resume?.url ?? (p.resume !== void 0 ? `https://github.com/${p.identity.repo}/pull/${p.resume.pr}` : void 0);
36177
+ const runs = p.resume !== void 0 ? `the review loop of ${prUrl2} resumes at its next review round on \`${branch2}\` in this thread under your grants \u2014 no new coding round first` : `the unit runs on \`${branch2}\` in this thread under your grants`;
36178
+ return `${head} ${runs}; this card follows it and the report lands here.`;
36181
36179
  }
36182
36180
  async function handOffToCoordinator(deps, input) {
36183
36181
  const log = deps.log ?? console.log;
36184
36182
  const planned = await plan(deps, input);
36185
36183
  if (!planned.ok) return refused3(planned.reply);
36186
36184
  const p = planned.planned;
36187
- if (p.kind === "task") return start(deps, input, p.instance, p.units, p.where, "put");
36185
+ const planOf = () => ({
36186
+ id: p.planId,
36187
+ ...p.path !== void 0 ? { path: p.path } : {}
36188
+ });
36188
36189
  const firstId = planInstanceId(p.planId);
36189
36190
  const first = await deps.instances.get(firstId);
36190
36191
  if (first === null) {
36191
- const units2 = rowsFor(p.graph, p.selected, firstId);
36192
+ const units2 = rowsFor(p, p.selected, firstId);
36192
36193
  const instance2 = {
36193
36194
  id: firstId,
36194
36195
  ...p.identity,
36195
36196
  branch: units2[0].branch,
36196
- plan: { id: p.planId, path: p.path },
36197
- // A seeded plan's units are the runner's to merge (record 0031's merge grant).
36198
- merge: "runner"
36197
+ plan: planOf(),
36198
+ merge: p.merge
36199
36199
  };
36200
36200
  return start(deps, input, instance2, units2, planWhere(p, units2, []), "put");
36201
36201
  }
@@ -36232,13 +36232,13 @@ async function handOffToCoordinator(deps, input) {
36232
36232
  );
36233
36233
  const mergedBefore = p.selected.filter((u) => merged.has(u));
36234
36234
  if (status3.kind === "absent") {
36235
- const units2 = rowsFor(p.graph, remaining, latest.id);
36235
+ const units2 = rowsFor(p, remaining, latest.id);
36236
36236
  const instance2 = {
36237
36237
  id: latest.id,
36238
36238
  ...p.identity,
36239
36239
  branch: units2[0].branch,
36240
- plan: { id: p.planId, path: p.path },
36241
- merge: "runner",
36240
+ plan: planOf(),
36241
+ merge: p.merge,
36242
36242
  ...attempt > 1 ? { attempt } : {}
36243
36243
  };
36244
36244
  log(`[ship] ${input.msg.threadKey}: ${latest.id} has records but no instance \u2014 replacing the earlier attempt's`);
@@ -36246,13 +36246,13 @@ async function handOffToCoordinator(deps, input) {
36246
36246
  return start(deps, input, instance2, units2, `${prefix}${planWhere(p, units2, mergedBefore)}`, "replace");
36247
36247
  }
36248
36248
  const nextId = planInstanceId(p.planId, attempt + 1);
36249
- const units = rowsFor(p.graph, remaining, nextId);
36249
+ const units = rowsFor(p, remaining, nextId);
36250
36250
  const instance = {
36251
36251
  id: nextId,
36252
36252
  ...p.identity,
36253
36253
  branch: units[0].branch,
36254
- plan: { id: p.planId, path: p.path },
36255
- merge: "runner",
36254
+ plan: planOf(),
36255
+ merge: p.merge,
36256
36256
  attempt: attempt + 1
36257
36257
  };
36258
36258
  return start(deps, input, instance, units, `attempt ${attempt + 1} of ${planWhere(p, units, mergedBefore)}`, "put");
@@ -36304,9 +36304,9 @@ var refused3, describe6, RUNNING, ENDED;
36304
36304
  var init_handOff = __esm({
36305
36305
  "../../src/core/coordinator/handOff.ts"() {
36306
36306
  "use strict";
36307
+ init_contract();
36307
36308
  init_preflight();
36308
36309
  init_coordinator();
36309
- init_briefs();
36310
36310
  refused3 = (reply) => ({ status: "aborted", reply });
36311
36311
  describe6 = (err2) => err2 instanceof Error ? err2.message : String(err2);
36312
36312
  RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
@@ -40486,7 +40486,7 @@ var init_threadTurns = __esm({
40486
40486
  "use strict";
40487
40487
  init_reply();
40488
40488
  APP_FOOTER_RE = /(?:^|\s)(?:\*Sent using\*|Sent using)\s+<@[A-Z0-9]+(?:\|[^>]*)?>(?:\s*\[[^\]\n]*\])?\s*$/;
40489
- RELAY_FOOTER_RE = /(?:^|\s)Sent by Claude in <#([CGD][A-Z0-9_]+)(?:\|[^>]*)?>\s*(?:·|•|-|—)\s*<(https?:\/\/[^|>\s]+)(?:\|[^>]*)?>\s*$/;
40489
+ RELAY_FOOTER_RE = /(?:^|\s)Sent by Claude in <#([CGD][A-Z0-9_]+)(?:\|[^>]*)?>(?:\s+on behalf of <@([A-Z0-9]+)(?:\|[^>]*)?>)?\s*(?:·|•|-|—)\s*<(https?:\/\/[^|>\s]+)(?:\|[^>]*)?>\s*$/;
40490
40490
  }
40491
40491
  });
40492
40492
 
@@ -41257,12 +41257,35 @@ var init_lookups = __esm({
41257
41257
  });
41258
41258
 
41259
41259
  // ../../src/channels/slack/requester.ts
41260
+ function textOfBlocks(blocks) {
41261
+ if (!blocks) return "";
41262
+ const out = [];
41263
+ const walk2 = (b) => {
41264
+ if (typeof b.text === "string") out.push(b.text);
41265
+ else if (b.text && typeof b.text.text === "string") out.push(b.text.text);
41266
+ for (const e of b.elements ?? []) walk2(e);
41267
+ };
41268
+ for (const b of blocks) {
41269
+ const before = out.length;
41270
+ walk2(b);
41271
+ if (out.length > before) out.push("\n");
41272
+ }
41273
+ return out.join("").replace(/\n+$/, "");
41274
+ }
41275
+ function rawTextOf(text, blocks) {
41276
+ const base = text ?? "";
41277
+ const fromBlocks = textOfBlocks(blocks);
41278
+ if (!fromBlocks) return base;
41279
+ const extra = fromBlocks.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !base.includes(l));
41280
+ return extra.length > 0 ? `${base}
41281
+ ${extra.join("\n")}` : base;
41282
+ }
41260
41283
  function parseRelayFooter(text) {
41261
41284
  const m = RELAY_FOOTER_RE.exec(text);
41262
41285
  if (!m) return void 0;
41263
41286
  let url;
41264
41287
  try {
41265
- url = new URL(m[2].replaceAll("&amp;", "&"));
41288
+ url = new URL(m[3].replaceAll("&amp;", "&"));
41266
41289
  } catch {
41267
41290
  return void 0;
41268
41291
  }
@@ -41272,7 +41295,7 @@ function parseRelayFooter(text) {
41272
41295
  if (!ts) return void 0;
41273
41296
  const threadTs = url.searchParams.get("thread_ts") ?? `${ts[1]}.${ts[2]}`;
41274
41297
  if (!THREAD_TS.test(threadTs)) return void 0;
41275
- return { channel: parts[1], threadTs };
41298
+ return { channel: parts[1], threadTs, ...m[2] ? { onBehalfOf: m[2] } : {} };
41276
41299
  }
41277
41300
  function rememberParent(key, user) {
41278
41301
  relayParentCache.set(key, user);
@@ -41302,6 +41325,7 @@ async function resolveSlackRequester(client, ev) {
41302
41325
  const poster = ev.poster ?? {};
41303
41326
  const relay = parseRelayFooter(ev.text);
41304
41327
  if (relay) {
41328
+ if (relay.onBehalfOf) return personRequester(relay.onBehalfOf, "relay-footer", poster);
41305
41329
  const user = await threadParentUser(client, relay.channel, relay.threadTs);
41306
41330
  if (user) return personRequester(user, "relay-footer", poster);
41307
41331
  }
@@ -41552,7 +41576,7 @@ function createSlackApp(deps) {
41552
41576
  channel: event.channel,
41553
41577
  user: event.user,
41554
41578
  poster: posterOf(posted),
41555
- rawText: event.text ?? "",
41579
+ rawText: rawTextOf(event.text, posted.blocks),
41556
41580
  text: stripMention(event.text ?? "", botUserId),
41557
41581
  ts: event.ts,
41558
41582
  threadTs: event.thread_ts ?? event.ts,
@@ -41579,7 +41603,7 @@ function createSlackApp(deps) {
41579
41603
  channel: m.channel,
41580
41604
  user: m.user,
41581
41605
  poster: posterOf(m),
41582
- rawText: m.text ?? "",
41606
+ rawText: rawTextOf(m.text, m.blocks),
41583
41607
  text: stripMention(m.text ?? "", botUserId),
41584
41608
  ts: m.ts,
41585
41609
  threadTs: m.thread_ts ?? m.ts,
@@ -43751,7 +43775,7 @@ var init_accessAuth = __esm({
43751
43775
  });
43752
43776
 
43753
43777
  // ../../src/channels/dashboardAuth.ts
43754
- import { createHash as createHash6, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
43778
+ import { createHash as createHash5, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
43755
43779
  function header2(headers, name) {
43756
43780
  const raw = headers[name];
43757
43781
  return Array.isArray(raw) ? raw[0] : raw;
@@ -43769,8 +43793,8 @@ function accessVerifier(config, deps) {
43769
43793
  };
43770
43794
  }
43771
43795
  function secretsEqual(a, b) {
43772
- const ha = createHash6("sha256").update(a).digest();
43773
- const hb = createHash6("sha256").update(b).digest();
43796
+ const ha = createHash5("sha256").update(a).digest();
43797
+ const hb = createHash5("sha256").update(b).digest();
43774
43798
  return timingSafeEqual4(ha, hb);
43775
43799
  }
43776
43800
  function tokenVerifier(opts) {
@@ -44799,6 +44823,147 @@ var init_adminRestartAuthorize = __esm({
44799
44823
  }
44800
44824
  });
44801
44825
 
44826
+ // ../../src/core/ship/reviewChild.ts
44827
+ function buildShipReviewTurn(input) {
44828
+ const at = input.headSha ? ` at head \`${input.headSha}\`` : "";
44829
+ if (input.round <= 1 || !input.prior) {
44830
+ return `Review pull request ${input.where}${at}. Submit your verdict with findings via submit_verdict before your final message.`;
44831
+ }
44832
+ const findings = input.prior.findings.map(formatFinding).join("\n") || "(none recorded)";
44833
+ const dispositions = input.prior.dispositions.map((d) => `${d.findingId}: ${d.disposition}${d.note ? ` \u2014 ${d.note}` : ""}`).join("\n") || "(none recorded)";
44834
+ const dropped = input.prior.dropped !== void 0 && input.prior.dropped.length > 0 ? `
44835
+ Dispositions naming no finding of the previous round (dropped): ${input.prior.dropped.join(", ")}` : "";
44836
+ return `Re-review pull request ${input.where}${at} \u2014 review round ${input.round} of this ship pipeline. Load the \`re-review-delta\` skill: narrow your READING to the delta since the previously reviewed head and verify each prior finding's disposition, but your verdict still covers the full diff against base. Carry every unresolved prior finding forward under its existing id.
44837
+
44838
+ Previous round's findings:
44839
+ ${findings}
44840
+
44841
+ Fix round's dispositions:
44842
+ ${dispositions}${dropped}`;
44843
+ }
44844
+ var init_reviewChild = __esm({
44845
+ "../../src/core/ship/reviewChild.ts"() {
44846
+ "use strict";
44847
+ init_reviewVerdict();
44848
+ }
44849
+ });
44850
+
44851
+ // ../../src/core/coordinator/briefs.ts
44852
+ async function generatedUnitOf(instance, unit, readers) {
44853
+ const resume = unit.resume;
44854
+ if (resume !== void 0) {
44855
+ const url = resume.url ?? `https://github.com/${instance.repo}/pull/${resume.pr}`;
44856
+ return generatedUnit(unit.unit, `Resume the review loop of ${url}`);
44857
+ }
44858
+ const request = await readers.readShipRequest();
44859
+ const task = request !== void 0 ? shipTaskText(parseDirectives(request).text, instance.repo) : "";
44860
+ return generatedUnit(unit.unit, task || "Implement the task this thread's ship request describes.");
44861
+ }
44862
+ async function contractFor(instance, unit, readers) {
44863
+ const rebase = { branch: unit.branch, onto: instance.base ?? "main" };
44864
+ const issue = unit.issue !== void 0 ? { repo: instance.repo, number: unit.issue } : void 0;
44865
+ let source2;
44866
+ if (isGenerated(instance)) {
44867
+ source2 = { unit: await generatedUnitOf(instance, unit, readers) };
44868
+ } else {
44869
+ const planMarkdown = await readers.readRepoFile(instance.plan.path);
44870
+ if (planMarkdown === void 0)
44871
+ throw new Error(`the plan ${instance.plan.path} is not readable at ${rebase.onto} in ${instance.repo}`);
44872
+ source2 = { planMarkdown, unitId: unit.unit };
44873
+ }
44874
+ const section = ("unit" in source2 ? source2.unit : parsePlanUnit(source2.planMarkdown, unit.unit))?.section ?? "";
44875
+ const specs = /* @__PURE__ */ new Map();
44876
+ for (const spec of new Set(specItemRefs(section).map((r) => r.spec)))
44877
+ specs.set(spec, await readers.readRepoFile(`${SPECS_DIR2}/${spec}`));
44878
+ let agentRules;
44879
+ for (const file of RULES_FILES2) {
44880
+ const text = await readers.readRepoFile(file);
44881
+ if (text !== void 0) {
44882
+ agentRules = { file, text };
44883
+ break;
44884
+ }
44885
+ }
44886
+ return contractFromPlan({
44887
+ ...source2,
44888
+ readSpec: (spec) => specs.get(spec),
44889
+ ...agentRules ? { agentRules } : {},
44890
+ rebase,
44891
+ ...issue ? { issue } : {}
44892
+ });
44893
+ }
44894
+ function findingsRequest(input) {
44895
+ const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
44896
+ return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note), squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
44897
+
44898
+ Findings:
44899
+ ${findings}
44900
+
44901
+ Review:
44902
+ ${input.review}`;
44903
+ }
44904
+ async function composeChild(brief, instance, unit, readers) {
44905
+ const where = `${instance.repo}#${"pr" in brief ? brief.pr : ""}`;
44906
+ switch (brief.kind) {
44907
+ case "contract": {
44908
+ const contract = await contractFor(instance, unit, readers);
44909
+ const prompt = isGenerated(instance) ? contract.unit.section.split("\n").slice(1).join("\n").trim() : `Implement unit ${contract.unit.id} \u2014 ${contract.unit.title} \u2014 of ${instance.plan?.path ?? "the plan"}: the contract below is the unit. Do its first instruction first, then add every test scenario it lists, update every spec row it names and weaken no guard.`;
44910
+ return { preset: "coding", prompt, ref: unit.branch, contract };
44911
+ }
44912
+ case "review": {
44913
+ let prior;
44914
+ if (brief.prior !== void 0) {
44915
+ const findings = (await facts(readers, brief.prior.reviewRunId)).findings ?? [];
44916
+ const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
44917
+ const { matched, dropped } = matchDispositions(findings, recorded);
44918
+ prior = { findings, dispositions: matched, dropped };
44919
+ }
44920
+ const turn = buildShipReviewTurn({
44921
+ where,
44922
+ round: brief.round,
44923
+ ...brief.headSha !== void 0 ? { headSha: brief.headSha } : {},
44924
+ ...prior ? { prior } : {}
44925
+ });
44926
+ const contract = await contractFor(instance, unit, readers);
44927
+ return { preset: "review", prompt: `${prUrl(instance.repo, brief.pr)}
44928
+
44929
+ ${turn}`, contract };
44930
+ }
44931
+ case "findings": {
44932
+ const review = await facts(readers, brief.reviewRunId);
44933
+ return {
44934
+ preset: "coding",
44935
+ prompt: findingsRequest({ where, findings: review.findings ?? [], review: review.finalReply ?? "" }),
44936
+ ref: unit.branch
44937
+ };
44938
+ }
44939
+ default: {
44940
+ const unknown = brief;
44941
+ throw new Error(`unknown brief kind ${JSON.stringify(unknown.kind)}`);
44942
+ }
44943
+ }
44944
+ }
44945
+ async function facts(readers, runId3) {
44946
+ const read = await readers.readRunFacts(runId3);
44947
+ if (!read) throw new Error(`run ${runId3} is not in the run history`);
44948
+ return read;
44949
+ }
44950
+ var SPECS_DIR2, RULES_FILES2, isGenerated, prUrl;
44951
+ var init_briefs = __esm({
44952
+ "../../src/core/coordinator/briefs.ts"() {
44953
+ "use strict";
44954
+ init_directives();
44955
+ init_reviewVerdict();
44956
+ init_coordinator();
44957
+ init_contract();
44958
+ init_preflight();
44959
+ init_reviewChild();
44960
+ SPECS_DIR2 = "docs/reference/specs";
44961
+ RULES_FILES2 = ["AGENTS.md", "CLAUDE.md"];
44962
+ isGenerated = (instance) => instance.plan?.path === void 0;
44963
+ prUrl = (repo, pr) => `https://github.com/${repo}/pull/${pr}`;
44964
+ }
44965
+ });
44966
+
44802
44967
  // ../../src/channels/adminCoordinator.ts
44803
44968
  function isCoordinatorAdminPath(path) {
44804
44969
  return path.startsWith(COORDINATOR_ADMIN_PREFIX) && path.length > COORDINATOR_ADMIN_PREFIX.length;
@@ -44913,7 +45078,7 @@ async function unitRowOf(deps, instance, unit) {
44913
45078
  return { ok: true, row };
44914
45079
  }
44915
45080
  function unitThread(instance, row) {
44916
- const threadKey = row?.threadKey ?? (row === void 0 || row.unit === TASK_UNIT ? instance.threadKey : void 0);
45081
+ const threadKey = row?.threadKey ?? (row === void 0 || isGenerated2(instance) ? instance.threadKey : void 0);
44917
45082
  const sourceUrl = row?.sourceUrl ?? (threadKey === instance.threadKey ? instance.sourceUrl : void 0);
44918
45083
  return { threadKey, sourceUrl };
44919
45084
  }
@@ -45054,7 +45219,7 @@ async function spawn6(body, deps) {
45054
45219
  if (req.brief !== void 0) {
45055
45220
  if (row === void 0) return json(400, { ok: false, error: "a brief needs the unit it runs for" });
45056
45221
  try {
45057
- turn = await composeChild(req.brief, instance, row, briefReaders(deps, instance, io));
45222
+ turn = await composeChild(req.brief, instance, row, briefReaders(deps, instance));
45058
45223
  } catch (err2) {
45059
45224
  log(`[coordinator] ${instance.id} ${req.step}: the brief could not be composed: ${describe11(err2)}`);
45060
45225
  return json(502, { ok: false, error: "brief_failed", message: describe11(err2), at });
@@ -45283,6 +45448,8 @@ async function plan2(body, deps) {
45283
45448
  ...instance.plan !== void 0 ? { planId: instance.plan.id } : {},
45284
45449
  // Who merges: the instance's field; a record written before it existed is a person's merge.
45285
45450
  merge: instance.merge ?? "person",
45451
+ // The mark (item 16): the machine's report keys its re-issue line on it.
45452
+ generated: isGenerated2(instance),
45286
45453
  repo: instance.repo,
45287
45454
  base: instance.base ?? "main",
45288
45455
  caps: instance.caps ?? resolveShipCaps(void 0),
@@ -45312,7 +45479,7 @@ async function unitStart(body, deps) {
45312
45479
  if (!unit.ok) return unit.response;
45313
45480
  let row = unit.row;
45314
45481
  if (row.threadKey === void 0) {
45315
- if (row.unit === TASK_UNIT) {
45482
+ if (isGenerated2(instance)) {
45316
45483
  row = {
45317
45484
  ...row,
45318
45485
  threadKey: instance.threadKey,
@@ -45332,7 +45499,7 @@ async function unitStart(body, deps) {
45332
45499
  }
45333
45500
  row = review.row;
45334
45501
  }
45335
- if (row.issue === void 0 && row.unit !== TASK_UNIT) {
45502
+ if (row.issue === void 0 && !isGenerated2(instance)) {
45336
45503
  const issue = await unitIssueOf(deps, instance.repo, row.unit);
45337
45504
  if (issue !== void 0) row = { ...row, issue };
45338
45505
  }
@@ -45359,7 +45526,7 @@ function unitLead(instance, row) {
45359
45526
  function reviewLead(instance, row) {
45360
45527
  const who2 = instance.userName ?? instance.userId;
45361
45528
  const from = instance.sourceUrl !== void 0 ? `[the *ship* run](${instance.sourceUrl})` : "the *ship* run";
45362
- const what = row.unit === TASK_UNIT ? "the task" : `unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""}`;
45529
+ const what = isGenerated2(instance) ? "the task" : `unit ${row.unit}${row.title ? ` \u2014 ${row.title}` : ""}`;
45363
45530
  return `\u21B3 *ship* review of ${what} for ${who2}, from ${from}: \`${row.branch}\` in ${instance.repo}`;
45364
45531
  }
45365
45532
  async function branch(body, deps) {
@@ -45382,11 +45549,11 @@ async function branch(body, deps) {
45382
45549
  return json(200, { ok: false, reason: describe11(err2), at });
45383
45550
  }
45384
45551
  }
45385
- function unitLines(units) {
45552
+ function unitLines(units, generated) {
45386
45553
  return units.map((u) => {
45387
45554
  const last = u.rounds.at(-1);
45388
45555
  const state = u.ending ? u.ending.kind : last ? `${shipRoundHeader({ index: last.index, agent: last.agent })} \xB7 ${last.outcome}` : u.threadKey ? "starting" : "waiting";
45389
- return u.unit === TASK_UNIT ? state : `${u.unit} \xB7 ${state}`;
45556
+ return generated ? state : `${u.unit} \xB7 ${state}`;
45390
45557
  });
45391
45558
  }
45392
45559
  async function drawCard(deps, instance, units, close) {
@@ -45395,7 +45562,7 @@ async function drawCard(deps, instance, units, close) {
45395
45562
  if (!io) return;
45396
45563
  const clock4 = deps.clock ?? systemClock;
45397
45564
  const shell = createCardShell({ label: instance.label ?? "*ship*", startedAt: instance.createdAt, now: clock4 });
45398
- const detail = unitLines(units);
45565
+ const detail = unitLines(units, isGenerated2(instance));
45399
45566
  if (!close) {
45400
45567
  await io.status(shell.live({ detail }));
45401
45568
  return;
@@ -45613,7 +45780,7 @@ function parentRunRecord(instance, units, outcome, channelVisibility, finishedAt
45613
45780
  at: r.at
45614
45781
  }))
45615
45782
  ),
45616
- { type: "answer", text: planSummary(units), at: finishedAt }
45783
+ { type: "answer", text: planSummary(units, isGenerated2(instance)), at: finishedAt }
45617
45784
  ];
45618
45785
  events.forEach((e, i) => {
45619
45786
  e.seq = i + 1;
@@ -45639,11 +45806,11 @@ function parentRunRecord(instance, units, outcome, channelVisibility, finishedAt
45639
45806
  ...instance.sourceUrl !== void 0 ? { sourceUrl: instance.sourceUrl } : {}
45640
45807
  };
45641
45808
  }
45642
- function planSummary(units) {
45809
+ function planSummary(units, generated = false) {
45643
45810
  const lines = units.map((u) => {
45644
45811
  const how = u.ending ? u.ending.kind : u.threadKey ? "unfinished" : "not started";
45645
45812
  const pr = u.pr ? ` \u2014 ${u.pr.url}` : "";
45646
- return u.unit === TASK_UNIT ? `${ENDING_ICON[how] ?? "\u2022"} ${how}${pr}` : `${ENDING_ICON[how] ?? "\u2022"} ${u.unit} \u2014 ${how}${pr}`;
45813
+ return generated ? `${ENDING_ICON[how] ?? "\u2022"} ${how}${pr}` : `${ENDING_ICON[how] ?? "\u2022"} ${u.unit} \u2014 ${how}${pr}`;
45647
45814
  });
45648
45815
  return lines.join("\n");
45649
45816
  }
@@ -45661,8 +45828,7 @@ async function finish(body, deps) {
45661
45828
  deps.runHistoryWriter.write(record2);
45662
45829
  await drawCard(deps, instance, units, { icon: body.outcome === "completed" ? "\u2705" : "\u26A0\uFE0F" }).catch(() => {
45663
45830
  });
45664
- const isPlan = units.some((u) => u.unit !== TASK_UNIT);
45665
- if (isPlan) {
45831
+ if (!isGenerated2(instance)) {
45666
45832
  const io = deps.ioFor({ threadKey: instance.threadKey, userId: instance.userId });
45667
45833
  await io?.reply(`Plan ${instance.plan?.id ?? ""} ended (${body.outcome}):
45668
45834
  ${planSummary(units)}`).catch(() => {
@@ -45671,7 +45837,7 @@ ${planSummary(units)}`).catch(() => {
45671
45837
  (deps.log ?? console.log)(`[coordinator] ${instance.id}: finished ${body.outcome} \u2014 record ${record2.id}`);
45672
45838
  return json(200, { ok: true, runId: record2.id, at });
45673
45839
  }
45674
- function briefReaders(deps, instance, io) {
45840
+ function briefReaders(deps, instance) {
45675
45841
  const ref = instance.base ?? "main";
45676
45842
  return {
45677
45843
  readRepoFile: async (path) => {
@@ -45691,9 +45857,16 @@ function briefReaders(deps, instance, io) {
45691
45857
  ...finalReplyOf2(r.events) !== void 0 ? { finalReply: finalReplyOf2(r.events) } : {}
45692
45858
  };
45693
45859
  },
45860
+ // The generated plan's request text: the ship run's own record
45861
+ // (`instance.runId`, its `input` event) — never a scan of the thread, so a
45862
+ // routed request with no `agent:ship` turn anywhere still reads back the
45863
+ // words the person typed (agent-ship item 13).
45694
45864
  readShipRequest: async () => {
45695
- const history = await io.history().catch(() => []);
45696
- return [...history].reverse().find((h) => h.role === "user" && /\bagent:ship\b/.test(h.text))?.text;
45865
+ if (instance.runId === void 0) return void 0;
45866
+ const res = await deps.runs.getRun(instance.runId, { include: "messages" }).catch(() => void 0);
45867
+ if (res === void 0 || !res.ok) return void 0;
45868
+ const input = (res.value.events ?? []).find((e) => e.type === "input");
45869
+ return input?.type === "input" ? input.text : void 0;
45697
45870
  }
45698
45871
  };
45699
45872
  }
@@ -45769,7 +45942,7 @@ function createAdminCoordinatorHandler(deps) {
45769
45942
  })();
45770
45943
  };
45771
45944
  }
45772
- var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json, describe11, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
45945
+ var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
45773
45946
  var init_adminCoordinator = __esm({
45774
45947
  "../../src/channels/adminCoordinator.ts"() {
45775
45948
  "use strict";
@@ -45803,6 +45976,7 @@ var init_adminCoordinator = __esm({
45803
45976
  invalid = (error) => ({ ok: false, error });
45804
45977
  json = (status3, body) => ({ status: status3, body });
45805
45978
  describe11 = (err2) => err2 instanceof Error ? err2.message : String(err2);
45979
+ isGenerated2 = (instance) => instance.plan?.path === void 0;
45806
45980
  EVERY_RUN = { kind: "all" };
45807
45981
  REVIEW_POSTED_CHECKS = 3;
45808
45982
  REVIEW_POSTED_RECHECK_MS = 2e3;